Currently in testing: the GitHub code will be opened once complete.

What is Ticketing?

Ticketing is a dedicated lock server designed for exactly one job — distributed locking. It's a single Rust server binary paired with official per-language clients that all implement the same binary protocol, byte for byte. Just two operations — acquire and release — a per-key FIFO queue, fencing tokens, and an optional Raft cluster you turn on when you need it. Everything a distributed lock needs, and nothing else.

How is this different from locks built on a store?

Most distributed locks in production lean on a key-value store — a Lua script or Redlock on top of Redis, or a lock built on ZooKeeper's or etcd's sessions and leases. Redis is a great store, but it's a key-value store repurposed for distributed locking, not a purpose-built lock system, and that shows up as structural overhead: the parsing cost of a general-purpose protocol, and the overhead of general-purpose data structures, both come along for the ride.

Ticketing was designed exclusively for distributed locking from day one, so every layer is built around locks.

  • Protocol — fixed-width binary fields, not text and not a general-purpose serialization format. Values are read straight off byte offsets with no parsing, and pipelining lets you send the next request without waiting for the previous response.
  • Server internals — the entire state is a per-key FIFO queue plus a lease timer. The request path doesn't even allocate on the heap.
  • Operations — start one server binary (or container), configure it with a handful of environment variables, and you're done. No schema design, no separate store, no operational knowledge unrelated to locking required.

Performance

  • Handles over 1 million ops/sec while using under 10MB of memory
A speed comparison against Redisson (Redis), the most widely used distributed lock system.
Ticketing clientsRedis (Redisson RLock)
JavaScript
210.2 ms · 190,295 ops/s
210.2 ms
Rust
225.9 ms · 177,069 ops/s
225.9 ms
Go
228.4 ms · 175,131 ops/s
228.4 ms
C#
286.9 ms · 139,421 ops/s
286.9 ms
Kotlin
443.5 ms · 90,192 ops/s
443.5 ms
Java
457.8 ms · 87,374 ops/s
457.8 ms
C/C++
659.7 ms · 60,634 ops/s
659.7 ms
Python
739.4 ms · 54,098 ops/s
739.4 ms
Ruby
1,682.4 ms · 23,776 ops/s
1,682.4 ms
Redisbaseline
4,909.1 ms · 8,148 ops/s
4,909.1 ms
Bar length is the wall-clock time (ms) for the whole run — shorter is faster.
32 contended keys × 1,250 ops · concurrency 64 · mac mini m4 2024 basic (10 core), single local server, acknowledged release, 1s min-work budget

Why Ticketing

🚦 Ordering integrity — FIFO queue with direct handoff

First come, first served. When the holder releases, the lock is handed directly to the front of the queue with no re-contention. No starvation from polling-and-retry lock contention, and no retry storms.

🛟 A dead client doesn't leave the lock stuck — lease

If the process holding the lock dies or its connection drops, the server automatically reclaims the lock once the lease duration set at acquire time elapses, and hands it to the next waiter. Forgetting to release doesn't bring the whole system to a halt.

🔑 Preventing out-of-order work — fencing tokens

There's one failure a lock alone can't prevent: a holder that wakes up late — unaware its lease already expired — and asks to finish its work anyway. Ticketing issues a monotonically increasing fencing token with every acquire. As long as the protected resource enforces a single rule — "reject any token lower than the last one seen" — even this last gap is closed. See How It Works for the full explanation.

🗳️ A non-stop cluster built on Raft

A single server is enough on its own, but when you need zero downtime, form a cluster of 3 (or 5) nodes. Every lock state change is only finalized after a majority of nodes agree via Raft consensus, so the same lock is never issued twice at once even if the leader dies or the network partitions. If the leader fails, a new one is elected in roughly a second and clients switch over automatically. Service keeps running even as nodes go down one at a time — say, during cloud instance reboots or a rolling maintenance pass.

🔒 Built-in auth and TLS

Every connection goes through challenge-response token authentication immediately after connecting, and the token is never sent over the wire in plaintext. Encrypt the whole connection with TLS whenever you need to.

🌐 The same protocol, byte for byte, across every major language

Rust, Java/Kotlin, JavaScript/TypeScript, Python, C#, Go, Ruby, C/C++ — every official client implements the same binary protocol, with an API that feels natural to each language's ecosystem (async or blocking). A lock acquired from any language queues fairly with clients written in any other. See Libraries for install instructions and examples.

Just two operations

True to a lock-only server, the surface area is minimal too.

  1. Attach a key to the resource you want to protect — e.g. order-1234, account-77, daily-batch.
  2. Acquire that key before doing the work — if someone else holds it, you wait your turn in the FIFO queue.
  3. Release it when the work is done — the next waiter takes over immediately with no re-contention.

The only options on top of these two operations are lease (automatic reclaim) and waitTimeout (give up waiting) — that's the entire API.