Object Storage Direct
Each service publishes directly to object storage. Competing writers coordinate through conditional updates to the log manifest.
Service → object storage
Your software owns its state and behavior. Mainframe connects replicas to one ordered history.
Your handler, application logic, and local data share a process. Replicas coordinate at the log backend.
| Part | Responsibility |
|---|---|
| Your software | The API, command format, data structures, indexes, and rules for changing state. |
| Mainframe core | Compose engines, submit commands to the chosen log, and apply ordered entries while tracking progress. |
| Log backend | Establish one order, publish entries durably, serve retained history, and coordinate competing writers. |
| Your service integration | Request deduplication, atomic state persistence, snapshots, read freshness, and external effects. |
The backend establishes the log’s order and durability. Saving an object alone does not decide which concurrent command comes first.
Each service publishes directly to object storage. Competing writers coordinate through conditional updates to the log manifest.
Service → object storage
Services send appends to an HTTP broker, which batches and publishes them. Readers fetch the log from object storage. The broker is an additional process to operate.
Write: service → broker → object storage
The manifest identifies the batches that belong to the log. An uploaded object alone is not an accepted append. If a response is lost, reconcile against fresh metadata before deciding whether to retry. Backend correctness depends on the store honoring the required conditional writes, reads, and durability. Read the validation approach ↗
The object storage backends are prototypes. An in-memory log is available for local development; it loses its history when the process stops.
If the required barrier cannot be reached, wait or return an error. Returning older data would change the read contract.
Watch a replica catch up ↗Saving a progress number does not save application state. A missing entry or incompatible snapshot must stop recovery.
Watch recovery ↗Acceptance into the log establishes an order. During apply, your code checks stock, record versions, or another rule against the state at that position. Record the result and advance progress even when the application rejects the command. Use stable request IDs so retries can return the saved result.
Follow two conflicting updates ↗The current Engine API is Rust. A JSON-RPC handler can expose your service to clients in other languages; this implementation does not provide Go or TypeScript engine SDKs.
The examples import mainframe_core from the mainframe-core package. Place the example crate beside your mainframe checkout.
SharedLogEnginepropose can transform or reject a command before publication. apply processes an ordered entry. sync is the engine's synchronization hook.EngineStack// Submit a command and learn its log position.
p = stack.propose(command).await?;
// Apply entry p using an exclusive upper boundary.
stack.sync_and_apply(p.next()).await?;
// Read the application result saved while applying it.
return local_results[request_id];
This is pseudocode. Your integration must persist state, results, and progress
consistently and tolerate retries of failed or cancelled apply calls.
checkpoint() returns a common engine boundary; the caller still
saves the engine snapshots. Restore every engine to that boundary before
constructing the stack with from_checkpoint.
Implementation basis: core/src/lib.rs and the
backend sources. Inspect a complete local example ↗
A loglet owns one segment of history. VirtualLog presents a chain
of those segments as one shared log. Reconfiguration seals the current segment,
creates an empty successor at the same boundary, and publishes the chain update
through MetaStore. Earlier history remains addressable.
This separates the application's logical positions from the backend serving each segment. Safe reconfiguration still depends on the loglets' seal behavior and the metadata store's conditional update contract.
Implementation basis: core/src/virtual_log.rs.
Read the virtual consensus explanation and paper ↗
These are the shared-log requirements. A persistent deployment must validate its backend against them.
| The log must provide | Your application must handle |
|---|---|
| One ordered history with unique positions. | Deterministic application, including stock, version, and transaction rules. |
| Retention of acknowledged entries on a persistent backend, until safely trimmed. | Atomic persistence of state, request results, and progress; compatible snapshots. |
| Consistent tail and positional reads under its documented contract. | A read barrier and catch-up when a caller needs fresh data. |
| A fixed final boundary after sealing; explicit errors for unavailable history. | Retry reconciliation, request deduplication, and retention of everything recovery needs. |
The in-memory log is for local development and loses history on exit. Object storage backends are prototypes; their guarantees depend on the store’s conditional writes, reads, and durability.
The suites exercise individual log operations, engine recovery, competing writers, and failures between processes.
These descriptions document test coverage, not a live test result. Public CI reports and source browsing are not currently provided on this site; reproducing the suites requires access to the core repository.
Append entries, read them back, close the log to new writes, and remove an old prefix. Shared tests check ordering, unique positions, concurrent reads and writes, and randomized workloads.
For example Race an append against closing the log. An accepted entry must fall before the final boundary; later appends must fail.
core/src/tests/ · core/src/lib.rs
Tests exercise repeated and concurrent catch-up calls, checkpoint recovery, and failures partway through an engine stack. They also check that switching log segments preserves the sequence.
For example Make one engine fail during apply. On retry, resume at that engine without calling engines that already completed the entry again.
review/tests/regressions.rs · core/src/virtual_log.rs
Integration and regression tests use disposable MinIO storage. They cover independent writers, failed metadata reads, failed or lost commit responses, concurrent trimming, and interrupted segment transitions.
For example Lose the response to a metadata commit. Check that recovery does not publish a gap in the log or reuse an occupied position.
review/tests/regressions.rs · object-storage-direct/ · object-storage-broker/
A separate runner starts concurrent clients, crashes and restarts processes, and reads back acknowledged writes. Its local fault mode interrupts the storage connection. It saves each operation and outcome for the Jepsen/Knossos checker.
For example Ask whether the recorded operations fit one legal sequential log history that respects real-time order. The checker also requires recovery-read coverage; an empty history cannot pass.
scripts/test_processes.py · jepsen-suite/
From the Mainframe repository root, run the standalone core tests. These exercise the core implementation without provisioning S3 or MinIO.
cargo test --manifest-path core/Cargo.toml --locked
On pushes and pull requests to main, the workflow runs core tests, workspace tests, review regressions against isolated MinIO, and tests for the storage-test runner. A separate job tests the history checker against known valid and invalid histories.
Independent-process fault runs and live S3/GCS validation are
separate from those CI jobs. Local results establish behavior
under the tested conditions; they do not establish the same
behavior on every cloud backend. The run commands and setup are
documented in review/README.md.
Local reads access your own state without a database hop. Writes pay for publication through the selected backend; fresh reads may also wait for catch-up. Measure those paths separately in your environment.
No measured throughput or latency results are published here yet.
500 attempted appends · 31.3 KiB requested payload per backend
Build with
cargo build --release --workspace. Set
S3_BUCKET, region, and credentials for a disposable
test bucket. For local MinIO, set AWS_ENDPOINT_URL.
The wrapper starts a broker on port 8080; use that address for
BROKER_URL.
Keep the machine, endpoint, region, payload size, and concurrency identical. Record the source revision and environment alongside the output. Repeat runs to capture variation, and report failures with latency. The harness runs backends sequentially when “both” is selected; reads measure a different workload from concurrent appends. Use dedicated test storage: the benchmark writes data and does not clean it up.
Successful operations divided by elapsed wall time. Failures are counted separately.
p50 is the median time for a successful operation; 99% finish within the p99 time. Read checks run one at a time.
Checks both position and payload for entries written during the current run.
Meta’s Delos is the original research and production system. Mainframe is an independent implementation inspired by it. These publications describe Meta’s deployments and the ideas behind this library.
Meta described DelosTable as a relational store for control-plane data, including the Resource Broker’s machine-allocation ledger. Its table API supports transactions, secondary indexes, and range queries. A shared log coordinates changes while replicas maintain their local representation.
Meta’s Delos supplies an ordered history and a replication framework. Meta reported replacing ZooKeeper-backed ordering with a native implementation without service downtime.
Design lesson: keep database semantics separate from the consensus implementation. This illustration omits the full transaction protocol; it is not a SQL execution trace. Diagram: explanatory reconstruction based on the cited publication.
Meta built Zelos to support ZooKeeper clients using Delos underneath. The API includes sessions, watches, and ephemeral nodes. Those behaviors require additional protocol logic above a shared log; a total log order alone does not preserve a client session’s issue order.
Meta’s Delos supplies the ordered log and composable engine framework. Replicas use reusable engines to interpret that history consistently.
Design lesson: reuse consensus, then explicitly implement the API’s additional semantics. Watches and ephemeral nodes also depend on session state, not just the ordering shown here. Diagram: explanatory reconstruction based on the cited publication.
Virtual Consensus in Delos separates the application’s logical log from its underlying loglets. Sealing one segment and installing its successor lets the application continue across changes in the replication implementation.
VirtualLog and Loglet model the segment boundary and reconfiguration. Recovery and competing transitions require configuration coordination.
Source: paper §3–4. This is a reconstruction of the boundary transition, not a full failure-recovery protocol. Diagram: explanatory reconstruction based on the cited publication.
Log-structured Protocols in Delos describes composable engines that execute above a shared log. Different databases can share protocol machinery while keeping their own APIs and application state.
Engine exposes propose, apply, and sync; EngineStack connects engines to SharedLog. The examples of batching, leases, and sessions here describe the research, not a catalog of shipped engines.
Source: paper Figure 1 and §3–4. The arrows show the two directions of processing, not a network hop for every engine. Diagram: explanatory reconstruction based on the cited publication.
CORFU presents a shared log over distributed flash storage. Clients use a sequencer and a mapping from log positions to storage units. Applications consume the history to construct their own state.
SharedLog exposes positional reads and appends. The S3 backends do not implement CORFU’s flash-cluster protocol or imply that a standalone sequencer provides consensus.
Incomplete writes need explicit resolution. The diagram simplifies chain replication, filling, and reconfiguration; follow the publication for the complete protocol. Diagram: explanatory reconstruction based on the cited publication.
Start from the same state. Deliver the same ordered inputs. Apply a deterministic transition function. Each replica then computes the same result. A live clock or random choice must not make replicas disagree during application.
Operations must fit a legal sequential history that respects real-time order. If a write finishes before a read begins, that read cannot return the earlier value when no later write intervenes. Concurrent operations may have either legal order.
These are correctness foundations, not storage backends. The fresh-read example and history checker connect these ideas to this project.
Scalog separates data replication from global ordering and studies how a shared log can scale and reconfigure. It is useful context for understanding that a shared-log API can have very different implementations underneath.
Related work cited in the repository; Scalog’s protocol is not implemented by these S3 backends.