tsoracle — monotonic timestamps and gapless sequences in Rust

tsoracle

Strictly monotonic timestamps and gapless sequences in Rust.

Distributed · highly-available · fault-tolerant · embeddable

$ cargo install tsoracle
$ tsoracle
── What's a TSO? ──

A timestamp oracle is a service that hands out strictly increasing integer IDs which order events across a distributed system. You reach for one when you're building a database with MVCC, merging change-data from many shards, or maintaining audit logs with a real happens-before relation across machines.

Per-host wall clocks aren't monotonic enough. Database sequences don't scale across shards. A TSO is the canonical answer — and the one Spanner, CockroachDB, and FoundationDB all use internally.

tsoracle goes one step further: the same engine also issues gapless sequences. Where a timestamp only has to be ordered, a sequence has to be contiguous…, 41, 42, 43, … with nothing skipped — exactly what you want for surrogate keys, invoice and order numbers, or ledger line numbers. One round-trip, one durable counter per named key, no holes across crashes or failover. Call get_seq() instead of get_ts().

Read: Why distributed systems need a TSO · Gapless sequences with GetSeq

── Features ──
Strictly monotonic

Every issued timestamp is strictly greater than every previous one. No duplicates, no regression.

Gapless sequences

Need contiguous IDs, not just ordered ones? get_seq() hands out dense, gapless runs per named key — nothing skipped, never reused across crashes or failover.

Crash-safe

Window state is fsync'd before any timestamp from it is handed out. Restarts never rewind.

Pluggable consensus

Production openraft and OmniPaxos drivers included. One trait to back tsoracle with raft-rs, etcd, or your own replicated log.

gRPC client included

Leader discovery, request coalescing, reconnection — handled. Just call get_ts() or get_seq().

Operational metrics

Allocator, leader, and request metrics emitted through the metrics facade. Prometheus-ready.

Embeddable

Run the standalone CLI, or embed tsoracle-server in your own binary with a few lines of Rust.

── How it works ──
client → leader → followers
One round-trip allocates a batch of N IDs.

The leader fsyncs the new high-water mark and replicates it through raft before the range leaves the process. Clients hand out IDs from the batch locally — millions per second on commodity hardware.

get_seq() rides the same path for gapless IDs: the leader advances a durable per-key counter, fsyncs and replicates the advance, and returns a contiguous block. The one difference is that a gapless advance can't be un-spent, so the call is non-idempotent — an ambiguous failure is surfaced as SeqUncertain, never silently retried.

Read the architecture summary

── Obtain ──
Standalone binary
$ cargo install tsoracle
$ tsoracle

# Listens on 127.0.0.1:50551
# Persists state to ./tsoracle-data/
# Bare `tsoracle` is shorthand for `tsoracle serve file`.
Embedded in your binary
use tsoracle_server::Server;
use tsoracle_driver_file::FileDriver;

let driver = FileDriver::open_or_init("./data")?;
let server = Server::builder()
    .consensus_driver(driver)
    .build()?;
server.serve_with_shutdown(addr, shutdown).await?;

The embedded path runs tsoracle-server as a library in your process. See the embedded-server example for the runnable version.

Other distribution channels: source releases and changelogs on GitHub Releases, the published Rust crates on crates.io, container images at ghcr.io/prisma-risk/tsoracle, and a Helm chart at ghcr.io/prisma-risk/charts. The full source tree is at github.com/prisma-risk/tsoracle under the Apache-2.0 license.

── Hardening ──

tsoracle is tested with coverage-guided fuzzing on its postcard decoders, failpoint-driven crash tests on the storage and replication paths, and an in-process + multi-process stress harness covering both the openraft and OmniPaxos backends. Fuzz harnesses run nightly; the stress harness runs on every PR.

Details: crates/tsoracle-tests · fuzz/

── Feedback & bug reports ──

Bug reports and enhancement proposals are tracked as issues on GitHub. Pick the template that matches what you're reporting:

Browse the existing reports at github.com/prisma-risk/tsoracle/issues. Blank issues are disabled — please use one of the templates so triage stays fast.

Security vulnerabilities follow a separate, private path: please do not open a public issue. Open a draft security advisory instead. We acknowledge within 24 hours, triage within 72 hours, and coordinate disclosure within 30 days. The full policy is in SECURITY.md.

── Contributing ──

Contributions are welcome — from a one-line clarification to a new ConsensusDriver implementation. The most useful changes are grounded in real use of tsoracle: a bug you hit in deployment, a missing operational hook, an example that fills a documentation gap. For anything larger than a few lines, please open an issue first — aligning on scope before code is faster than redesigning in review. Discussion happens on the issue itself; there is no separate forum or chat.

The contributor guide — local setup, the checks CI will run on your PR, the panic policy, the release flow — is in CONTRIBUTING.md. In short:

Looking for somewhere to start? The good first issue and help wanted labels list issues that are scoped and well-described for new contributors.

── Recent posts ──

View all posts →

── Documentation ──

How tsoracle works — the architecture summary on this site.

DeepWiki — prose documentation covering the window allocator, the ConsensusDriver contract, and operational topics (fsync cost, leader handoff, deployment topologies).

docs.rs/tsoracle-server — generated API reference.