{"id":13507526,"url":"https://github.com/wooga/locker","last_synced_at":"2025-10-21T18:46:11.184Z","repository":{"id":2646698,"uuid":"3636695","full_name":"wooga/locker","owner":"wooga","description":"Atomic distributed \"check and set\" for short-lived keys","archived":false,"fork":false,"pushed_at":"2019-04-22T15:17:31.000Z","size":350,"stargazers_count":152,"open_issues_count":5,"forks_count":14,"subscribers_count":181,"default_branch":"master","last_synced_at":"2024-12-16T12:37:05.001Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wooga.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-03-06T10:10:59.000Z","updated_at":"2024-02-06T10:23:01.000Z","dependencies_parsed_at":"2022-08-29T12:50:57.878Z","dependency_job_id":null,"html_url":"https://github.com/wooga/locker","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wooga%2Flocker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wooga%2Flocker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wooga%2Flocker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wooga%2Flocker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wooga","download_url":"https://codeload.github.com/wooga/locker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230978215,"owners_count":18309675,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T02:00:35.721Z","updated_at":"2025-10-21T18:46:10.780Z","avatar_url":"https://github.com/wooga.png","language":"Erlang","funding_links":[],"categories":["Caching"],"sub_categories":[],"readme":"## locker - atomic distributed \"check and set\" for short-lived keys\n\n`locker` is a distributed de-centralized consistent in-memory\nkey-value store written in Erlang. An entry expires after a certain\namount of time, unless the lease is extended. This makes it a good\npractical option for locks, mutexes and leader election in a\ndistributed system.\n\nIn terms of the CAP theorem, `locker` chooses consistency by requiring\na quorum for every write. For reads, `locker` chooses availability and\nalways does a local read which can be inconsistent. Extensions of the\nlease is used as an anti-entropy mechanism to eventually propagate all\nleases.\n\nIt is designed to be used inside your application on the Erlang VM,\nusing the Erlang distribution to communicate with masters and\nreplicas.\n\nOperations:\n\n * `locker:lock/2,3,4`\n * `locker:update/3,4`\n * `locker:extend_lease/3`\n * `locker:release/2,3`\n * `locker:wait_for/2`\n * `locker:wait_for_release/2`\n\n\n### Writes\n\nTo achieve \"atomic\" updates, the write is done in two phases, voting and\ncommiting.\n\nIn the voting phase, the client asks every master node for a promise\nthat the node can later set the key. The promise is only granted if\nthe current value is what the client expects. The promise will block\nany other clients from also receiving a promise for that key.\n\nIf the majority of the master nodes gives the client the promise\n(quorum), the client can go ahead and commit the lock. If a positive\nmajority was not reached, the client will abort and delete any\npromises it received.\n\n### Reads\n\n`locker` currently only offers dirty reads from the local node. If we\nneed consistent reads, a read quorum can be used.\n\n### Failure\n\n\"So, this is all fine and good, but what happens when something\nfails?\". To make the implementation simple, there is a timeout on\nevery promise and every lock. If a promise is not converted into a\nlock in time, it is simply deleted.\n\nIf the user process fails to extend the lease of its lock, the lock\nexpires without consulting any other node. If a node is partitioned\naway from the rest of the cluster, the lock might expire too soon\nresulting in reads returning the empty value. However, a new lock\ncannot be created as a quorum cannot be reached.\n\nCalling `locker:wait_for_release/2` will block until a lock expires,\neither by manual release or from a expired lease.\n\n### Lease expiration\n\nSynchronized clocks is not required for correct expiration of a\nlease. It is only required that the clocks progress at roughly the\nsame speed. When a lock is created or extended, the node will set the\nexpiration to `now() + lease_length`, which means that the user needs\nto account for the skew when extending the lease. With leases in the\norder of minutes, the skew should be very small.\n\nWhen a lease is extended, it is replicated to the other nodes in the\ncluster which will update their local copy if they don't already have\nthe key. This is used to bring new nodes in sync.\n\n### Replication\n\nA `locker` cluster consists of masters and replicas. The masters\nparticipate in the quorum and accept writes from the clients. The\nmasters implements strong consistency. Periodically the masters send\noff their transaction log to the replicas where it is replayed to\ncreate the same state. Replication is thus asynchronous and reads on\nthe replicas might be inconsistent. Replication is done in batch to\nimprove performance by reducing the number of messages each replica\nneeds to handle. Calling `locker:wait_for/2` after a succesful write\nwill block until the key is replicated to the local node. If the local\nnode is a master, it will return immediately.\n\n### Adding new nodes\n\nNew nodes may first be added as replicas to sync up before being\npromoted to master. Every operation happening after the replica\njoined, will be also propagated to the replica. The time to catch up\nis then determined by how long it takes for all leases to be extended.\n\nNew nodes might also be set directly as masters, in which case the new\nnode might give negative votes in the quorum. As long as a quorum can\nbe reached, the out-of-sync master will still accept writes and catch\nup as fast as a replica.\n\nUsing `locker:set_nodes/3` masters and replicas can be set across the\nentire cluster in a \"send-and-pray\" operation. If something happens\nduring this operation, the locker cluster might be in an inconsistent\nstate.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwooga%2Flocker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwooga%2Flocker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwooga%2Flocker/lists"}