#spanner #distributed-lock #distributed-locking #leader-election

spindle_rs

A distributed locking crate built on Cloud Spanner and TrueTime

9 releases

Uses new Rust 2024

new 0.1.8 Mar 16, 2025
0.1.7 Mar 16, 2025

#836 in Database interfaces

Download history 141/week @ 2025-03-09

179 downloads per month

Apache-2.0

33KB
561 lines

main crates.io docs-rs

Overview

This crate implements distributed locking using Cloud Spanner. It relies on Spanner's TrueTime and transactions support to achieve its locking mechanism. It's a port of the original spindle, which is written in Go.

Use cases

One use case for this library is leader election. If you want one host/node/pod to be the leader within a cluster/group, you can achieve that with this crate. When the leader fails, it will fail over to another host/node/pod within a specific timeout.

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)

After creating the lock object, you will call the run() function which will attempt to acquire a named lock at a regular interval (lease duration). A has_lock() function is provided which returns true (along with the lock token) if the lock is successfully acquired. Something like:

use spindle_rs::*;
...

fn main() -> Result<(), Box<dyn Error>> {
    let (tx, rx) = channel();
    ctrlc::set_handler(move || tx.send(()).unwrap()).unwrap();
    let mut lock = LockBuilder::new()
        .db("projects/p/instances/i/databases/db".to_string())
        .table("locktable".to_string()) // see CREATE TABLE
        .name("spindle-rs".to_string())
        // .id(":8080".to_string())
        .duration_ms(5000)
        .build();

    lock.run();

    // Wait for a bit before calling has_lock().
    thread::sleep(Duration::from_secs(10));
    let (locked, node, token) = lock.has_lock();
    println!("has_lock: {locked}, {node}, {token}");

    // Wait for Ctrl-C.
    rx.recv().unwrap();
    lock.close();

    Ok(())
}

Example

A sample code is provided to demonstrate the mechanism through logs. You can try running multiple processes in multiple terminals.

$ git clone https://github.com/flowerinthenight/spindle-rs
$ cd spindle-rs/
$ cargo build
# Update args with your actual values:
$ RUST_LOG=info ./target/debug/example \
  projects/p/instances/i/databases/db \
  locktable

The leader process should output something like leader active (me). You can then try to stop (Ctrl+C) that process and observe another one taking over as leader.

License

This library is licensed under the Apache 2.0 License.

Dependencies

~22–33MB
~485K SLoC