{"id":2027,"date":"2025-11-28T15:58:06","date_gmt":"2025-11-28T14:58:06","guid":{"rendered":"https:\/\/www.loicmathieu.fr\/wordpress\/fr\/?p=2027"},"modified":"2025-12-12T10:26:54","modified_gmt":"2025-12-12T09:26:54","slug":"implementing-a-lock-in-elasticsearch","status":"publish","type":"post","link":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/implementing-a-lock-in-elasticsearch\/","title":{"rendered":"Implementing a lock in Elasticsearch"},"content":{"rendered":"<p>At <a href=\"https:\/\/kestra.io\">Kestra<\/a>, the workflow orchestration and scheduling platform I work for, we have two repository implementations: JDBC (with support for H2, MySQL, and Postgres) and Elasticsearch.<\/p>\n<p>In JDBC, locking a record for processing is fairly easy:<\/p>\n<ol>\n<li><code>SELECT FOR UPDATE ... WHERE ...<\/code> will place a lock on the record(s)<\/li>\n<li>We process the record(s) in the same transaction<\/li>\n<li><code>UPDATE...<\/code><\/li>\n<\/ol>\n<p>At the end of the transaction, the database will automatically release the locks obtained via <code>SELECT FOR UPDATE<\/code>.<\/p>\n<p>But Elasticsearch does not support locks or transactions. Is there a way to simulate a lock?<\/p>\n<h2>Index vs Doc API<\/h2>\n<p>Elasticsearch is both a NoSQL database and a search engine.\nLike any search engine, it relies on an asynchronous indexing phase.<\/p>\n<p>This has two consequences:<\/p>\n<ol>\n<li>A search may not return an existing document.<\/li>\n<li>Indexing will overwrite any existing documents by default.<\/li>\n<\/ol>\n<p>There are two solutions to overcome this:<\/p>\n<ol>\n<li>Use the Doc API to load a document from its ID instead of performing a search. For this to work, you must set the document ID when indexing it and not let Elasticsearch defines an ID for you.<\/li>\n<li>When creating a lock, use <code>opType: Create<\/code> to ensure that no document exists for the same ID. This prevents a record from being created between the time you check whether a lock exists and the time you create the lock document.<\/li>\n<\/ol>\n<h2>LockRepository<\/h2>\n<p>Here is a potential implementation for a Lock CRUD repository:<\/p>\n<pre>\npublic Optional findById(String category, String id)  throws IOException {\n        GetRequest getRequest = new GetRequest.Builder()\n                .index(\"locks\")\n                .id(id)\n                .build();\n\n        GetResponse getResponse = client.get(getRequest, Lock.class);\n        return Optional.ofNullable(getResponse.source());\n}\n\npublic boolean create(Lock newLock) throws IOException {\n        try {\n                \/\/ we use OptType.Create, so if a doc already exists, it will refuse to index a new one.\n                IndexRequest request = new IndexRequest.Builder()\n                        .index(\"locks\")\n                        .id(newLock.uid())\n                        .document(newLock)\n                        .opType(OpType.Create)\n                        .build();\n                client.index(request);\n                return true;\n        } catch (IOException e) {\n                \/\/ for conflicts, we return false to indicate that we were not able to create the lock\n                if (e instanceof ResponseException respException &amp;&amp; \n                                respException.getResponse().getStatusLine().getStatusCode() == 409) {\n                        return false;\n                }\n                throw e;\n        }\n}\n\npublic void deleteById(String category, String id) throws IOException {\n        DeleteRequest request = new DeleteRequest.Builder()\n                .index(\"locks\")\n                .id(IdUtils.fromParts(category, id))\n                .build();\n\n        client.delete(request);\n}\n<\/pre>\n<h2>LockService<\/h2>\n<p>We will now implement a <code>LockService<\/code> that allows the execution of a <code>Runnable<\/code> guarded by a lock to simulate a transaction.<\/p>\n<p>The principle is as follows:<\/p>\n<ul>\n<li>We lock:\n<ul>\n<li>If a lock already exists, we wait.<\/li>\n<li>Otherwise, we create a lock; if the creation returns false, it indicates a conflict, so we wait.<\/li>\n<li>Otherwise, we have successfully acquired a lock!<\/li>\n<\/ul><\/li>\n<li>We execute the <code>Runnable<\/code><\/li>\n<li>We unlock: we delete the lock<\/li>\n<\/ul>\n<p>Here is an example of an implementation; our implementation is a bit more complex, but the idea is the same:<\/p>\n<pre>\npublic void doInLock(String category, String id, Duration timeout, Runnable runnable) {\n        lock(id); \/\/ our real implementation handle a timeout\n        try {\n                runnable.run();\n        } finally {\n                unlock(id);\n        }\n}\n\nprivate void lock(String id, Duration timeout) throws LockException {\n    long deadline = System.currentTimeMillis() + timeout.toMillis();\n        do {\n                Optional existing = lockRepository.findById(category, id);\n                if (existing.isEmpty()) {\n                        \/\/ we can try to lock!\n                        Lock newLock = new Lock(id, Instant.now());\n                        if (lockRepository.create(newLock)) {\n                        \/\/ yeah, we locked!\n                                return true;\n                        }\n\n                try {\n                        Thread.sleep(1); \/\/ this is not ideal, you can also use Thread.onSpinWait()\n                } catch (InterruptedException e) {\n                        Thread.currentThread().interrupt();\n                        throw new LockException(e);\n                }\n        } while (System.currentTimeMillis() &lt; deadline));\n}\n\nprivate void unlock(String category, String id) {\n        Optional existing = lockRepository.findById(category, id);\n        if (existing.isEmpty()) {\n                \/\/ should not occur but better be safe\n                return;\n        }\n\n        if (!existing.get().getOwner().equals(ServerInstance.INSTANCE_ID)) {\n        \/\/ if the lock has been acquire by someone else in-between we didn't release it\n        return;\n    }\n\n        lockRepository.deleteById(category, id);\n}\n<\/pre>\n<p>This implementation is not a perfect implementation of a distributed lock, as explained above, the actual implementation is more complex. Among other things, Kestra has a distributed liveness check mechanism that allows us to remove locks from an instance detected as dead.<\/p>","protected":false},"excerpt":{"rendered":"<p>At Kestra, the workflow orchestration and scheduling platform I work for, we have two repository implementations: JDBC (with support for H2, MySQL, and Postgres) and Elasticsearch. In JDBC, locking a record for processing is fairly easy: SELECT FOR UPDATE &#8230; WHERE &#8230; will place a lock on the record(s) We process the record(s) in the same transaction UPDATE&#8230; At the end of the transaction, the database will automatically release the locks obtained via SELECT FOR UPDATE. But Elasticsearch does not&#8230;<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/implementing-a-lock-in-elasticsearch\/\"> Read More<span class=\"screen-reader-text\">  Read More<\/span><\/a><\/p><\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":4,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"federated","footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[9],"tags":[231],"class_list":["post-2027","post","type-post","status-publish","format-standard","hentry","category-informatique","tag-elasticsearch"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":1650,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/le-profiler-sql-de-visualvm\/","url_meta":{"origin":2027,"position":0},"title":"VisualVM SQL profiler SQL","author":"admin","date":"Tuesday April  4th, 2023","format":false,"excerpt":"A little while ago, I discovered the SQL profiler of VisualVM and I thought I should share it with you ;). VisualVM is a tool that provides a visual interface to display detailed information about applications running on a Java Virtual Machine (JVM). VisualVM is designed for use in development\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/Capture-decran-du-2023-04-03-14-17-25-1024x624.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/Capture-decran-du-2023-04-03-14-17-25-1024x624.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/Capture-decran-du-2023-04-03-14-17-25-1024x624.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":1786,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/concevoir-un-saas-multitenant\/","url_meta":{"origin":2027,"position":1},"title":"Designing a multi-tenant SaaS","author":"admin","date":"Tuesday March  5th, 2024","format":false,"excerpt":"This article is based on my talk Designing a multi-tenant SaaS given at Cloud Nord on October 12, 2023. Kestra is a highly scalable data scheduling and orchestration platform that creates, executes, schedules and monitors millions of complex pipelines. For an introduction to Kestra, you can read my article on\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/kestra-software-architecture-1024x576.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/kestra-software-architecture-1024x576.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/kestra-software-architecture-1024x576.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":1731,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/optimisation-dindex-postgresql\/","url_meta":{"origin":2027,"position":2},"title":"PostgreSQL index optimization","author":"admin","date":"Tuesday August 22nd, 2023","format":false,"excerpt":"Some time ago, I worked on query execution time optimizations for PostgreSQL, I talk about it here: The VISUALVM SQL PROFILE. Kestra is a highly scalable data orchestration and scheduling platform that creates, executes, schedules, and monitors millions of complex pipelines. It's also the company I work for! The open\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1138,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/ma-deuxieme-annee-chez-zenika\/","url_meta":{"origin":2027,"position":3},"title":"(Fran\u00e7ais) Ma deuxi\u00e8me ann\u00e9e chez Zenika","author":"admin","date":"Thursday September  3rd, 2020","format":false,"excerpt":"Sorry, this entry is only available in Fran\u00e7ais.","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":923,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/devoxx-france-2019\/","url_meta":{"origin":2027,"position":4},"title":"(Fran\u00e7ais) Devoxx France 2019","author":"admin","date":"Monday May 13th, 2019","format":false,"excerpt":"Sorry, this entry is only available in Fran\u00e7ais.","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1112,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/java-15-quoi-de-neuf\/","url_meta":{"origin":2027,"position":5},"title":"Java 15 : what&#8217;s new ?","author":"admin","date":"Thursday July  2nd, 2020","format":false,"excerpt":"Now that Java 15 is features complete (Rampdown Phase One at the day of writing), it\u2019s time to walk throught all it\u2019s functionalities that brings to us, developers, this new version. This article is part of a series on what\u2019s new on the last versions of Java, for those who\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/posts\/2027","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/comments?post=2027"}],"version-history":[{"count":21,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/posts\/2027\/revisions"}],"predecessor-version":[{"id":2077,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/posts\/2027\/revisions\/2077"}],"wp:attachment":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/media?parent=2027"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/categories?post=2027"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/tags?post=2027"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}