https://github.com/flowerinthenight/spindle Skip to content Sign up * Why GitHub? Features - + Mobile - + Actions - + Codespaces - + Packages - + Security - + Code review - + Issues - + Integrations - + GitHub Sponsors - + Customer stories- * Team * Enterprise * Explore + Explore GitHub - Learn and contribute + Topics - + Collections - + Trending - + Learning Lab - + Open source guides - Connect with others + The ReadME Project - + Events - + Community forum - + GitHub Education - + GitHub Stars program - * Marketplace * Pricing Plans - + Compare plans - + Contact Sales - + Education - [ ] * # In this repository All GitHub | Jump to | * No suggested jump to results * # In this repository All GitHub | Jump to | * # In this user All GitHub | Jump to | * # In this repository All GitHub | Jump to | Sign in Sign up {{ message }} flowerinthenight / spindle Public * Notifications * Star 23 * Fork 0 A distributed locking library built on top of Cloud Spanner and TrueTime. Apache-2.0 License 23 stars 0 forks Star Notifications * Code * Issues 0 * Pull requests 0 * Actions * Projects 0 * Wiki * Security * Insights More * Code * Issues * Pull requests * Actions * Projects * Wiki * Security * Insights main Switch branches/tags [ ] Branches Tags Could not load branches Nothing to show {{ refName }} default View all branches Could not load tags Nothing to show {{ refName }} default View all tags 1 branch 36 tags Code Latest commit @flowerinthenight flowerinthenight Update README.md ... efd11a7 Oct 7, 2021 Update README.md efd11a7 Git stats * 99 commits Files Permalink Failed to load latest commit information. Type Name Latest commit message Commit time .github/workflows test: wip emulator Jan 27, 2021 examples chore: rm bin Aug 23, 2021 lock chore: update readme Oct 3, 2021 .gitignore chore: rm bin Aug 23, 2021 LICENSE Initial commit Dec 19, 2020 README.md Update README.md Oct 7, 2021 go.mod chore: update libs Sep 23, 2021 go.sum chore: update libs Sep 23, 2021 spindle.go chore: set drift to 2s Oct 1, 2021 View code spindle Usage How it works TODO README.md main spindle A distributed locking library built on top of Cloud Spanner. It uses Spanner's TrueTime and transactions support to achieve its locking mechanism. This library is also one of the main building blocks of hedge, another one of our production staples. For context, spindle has been in our production for quite some time and is used in several critical services. The biggest deployment size running spindle is about ~100+ pods with unpredictable scaling schedules. Between k8s scaling triggers and multiple service deployments in a day, spindle so far has held its own, running like clockwork. Usage At the moment, the table needs to be created beforehand using the following DDL (locktable is just an example): CREATE TABLE locktable ( name STRING(MAX) NOT NULL, heartbeat TIMESTAMP OPTIONS (allow_commit_timestamp=true), token TIMESTAMP OPTIONS (allow_commit_timestamp=true), writer STRING(MAX), ) PRIMARY KEY (name) This library doesn't use the usual synchronous "lock", "do protected work", "unlock" sequence. For that, you can check out the included lock package. Instead, after instantiating the lock object, you will call the Run(...) function which will attempt to acquire a named lock at a regular interval (lease duration) until cancelled. A HasLock() function is provided that returns true (along with the lock token) if the lock is successfully acquired. Something like: db, _ := spanner.NewClient(context.Background(), "your/database") defer db.Close() // Notify me when done. done := make(chan error, 1) // For cancellation. quit, cancel := context.WithCancel(context.Background()) // Instantiate the lock object using a 5s lease duration using locktable above. lock := spindle.New(db, "locktable", "mylock", spindle.WithDuration(5000)) // Start the main loop, async. lock.Run(quit, done) time.Sleep(time.Second * 20) locked, token := lock.HasLock() log.Println("HasLock:", locked, token) time.Sleep(time.Second * 20) cancel() <-done How it works The initial lock (the lock record doesn't exist in the table yet) is acquired by a process using an SQL INSERT. Once the record is created (by one process), all other INSERT attempts will fail. In this phase, the commit timestamp of the locking process' transaction will be equal to the timestamp stored in the token column. This will serve as our fencing token in situations where multiple processes are somehow able to acquire a lock. Using this token, the real lock holder will start sending heartbeats by updating the heartbeat column. When a lock is active, all participating processes will detect if the lease has expired by checking the heartbeat against Spanner's current timestamp. If so (say, the active locker has crashed, or cancelled), another round of SQL INSERT is attempted, this time, using the name format . The process that gets the lock this time will then attempt to update the token column using its commit timestamp, thus, updating the fencing token. In the event that the original locker process recovers (if crashed), or continues after a stop-the-world GC pause, the latest token should invalidate its locking claim (its token is already outdated). A simple code is provided to demonstrate the mechanism through logs. You can try running multiple binaries in multiple terminals or in a single terminal, like: $ cd examples/simple/ $ go build -v $ for num in 1 2 3; do ./simple &; done --------------------------------------------------------------------- TODO * [ ] Add tests using Spanner Emulator About A distributed locking library built on top of Cloud Spanner and TrueTime. Topics golang gcp truetime distributed-lock spanner distributed-locking Resources Readme License Apache-2.0 License Releases 36 tags Packages 0 No packages published Used by 2 * @flowerinthenight @flowerinthenight / hedge * @flowerinthenight @flowerinthenight / dlock Languages * Go 100.0% * (c) 2021 GitHub, Inc. * Terms * Privacy * Security * Status * Docs * Contact GitHub * Pricing * API * Training * Blog * About You can't perform that action at this time. You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.