Expand description
Coordinated object-storage (S3) backfill source for Spate.
Point the source at a bucket/prefix and it streams every object’s
records through the pipeline: a bounded job that self-terminates
(the pipeline exits Completed) once
the prefix is exhausted. Delivery is at-least-once: a resume replays
from the last committed position, so duplicates are possible and loss
is not.
§Shape
- Work is planned as splits. The fleet’s elected leader lists the
prefix once and packs the sorted listing into splits — small
batches of whole objects, ~64 MiB by default — each with a
deterministic identity (
split_id_for) and a self-containedSplitDescriptorcarrying its member keys, sizes, and ETags. Workers lease splits through the coordination store and read them straight from the descriptors: workers never list. - One lane per in-flight split. A gained split materializes one
data lane (one framework partition, one monotonic offset stream); a
record’s
i64offset packs (member ordinal within the split, record index within the object).coordination.max_in_flightbounds the working set and therefore read parallelism. - Progress lives in the coordination store — nowhere else. Commits
are fenced per-split writes; a lost or stolen split resumes on its
next owner from the acked watermark, drift-checked against the
descriptor’s ETag pins. The coordinator is assembly wiring: hand
one in via
S3Source::with_coordinator(e.g.spate-coordination’sStoreCoordinatorover its NATS JetStream store) and every replica of the pipeline shares the backfill, takes a leader-assigned share, and takes over from the dead. Without one the source runs solo over an in-process store: correct, but a restart replays the prefix (a startup WARN says so). This crate names no concrete backend — which store a deployment uses is the deployer’s choice, not a connector compile-time feature. - Bad objects poison their split, not the pipeline. An object
deleted after planning, overwritten under its ETag pin, corrupt, or
unreadable past the retry budget hands its split back (surfaced in
spate_s3_source_objects_failed_total{reason}); at the attempt cap the split is quarantined. A bounded job with quarantined splits ends failed, never silently incomplete. Credentials/configuration errors are still immediately fatal. - Records are framed, not decoded, here. The source is
format-agnostic: it streams object bytes (after gzip/zstd decompression)
through a
RecordFrameryou supply for the objects’ format viaS3Source::with_framer— e.g.spate-json’sNdjsonFramerfor NDJSON — emitting one raw payload per framed record. Deserialization then stays in the operator chain (spate-jsonetc.), exactly as with the Kafka source.
§Split identity and drift
A split’s id digests its sorted member keys and ETags (plus the
packing-algorithm version), and every GET is pinned If-Match to the
descriptor’s ETag. The consequences:
- Replanning an unchanged prefix reproduces identical ids — replans are create-if-absent no-ops, and completed splits stay completed.
- An overwritten object shows up as a new split (new id) on the
next plan; under an in-flight split it trips the
If-Matchpin and poisons the split. Either way it can never silently splice into committed progress. - A deleted object poisons only the splits that still needed it.
Prefer an append-only prefix for the lifetime of a backfill; by
default the listing is taken once (refresh_listing: false), so
late-arriving keys are simply not part of the job.
No object_store types appear in this crate’s public API (the same
dependency policy that keeps rdkafka out of spate-kafka’s). The one
seam that would break it, S3Source::with_store, is gated behind the
off-by-default testing feature so tests can inject wrapped or
fault-injecting stores without that type reaching a real consumer.
Structs§
- Descriptor
Object - One member object inside a
SplitDescriptor. - S3Batch
- One poll’s records, borrowed from the lane’s held buffer.
- S3Lane
- Data-plane pollable unit of the S3 source: owns one slice’s chunk stream and framing state.
- S3Source
- Coordinated object-storage backfill source. See the crate docs for the delivery model and the split identity/drift contract.
- S3Source
Config - Configuration of an
S3Source, deserialized from the pipeline’s opaquesource: { s3: ... }section. - Split
Descriptor - The opaque payload carried in a
SplitSpec::descriptor: the split’s member objects, in listing order.
Enums§
- Compression
- Compression codec of the objects under the prefix.
Constants§
- DESCRIPTOR_
VERSION - Version of the
SplitDescriptorwire encoding. Bumped on any change to the descriptor’s schema; a worker refuses a descriptor written by an incompatible release instead of misreading it.
Functions§
- split_
id_ for - Mint the deterministic split id for a member set.