What is a stream?
A stream is:- Ordered: Records maintain strict sequential ordering via sequence numbers
- Append-only: Records can only be added to the end, never modified
- Identified: Each stream has a unique name within its basin
- Durable: All acknowledged appends are persisted to object storage
- Streamable: Supports both historical reads and real-time streaming
Think of a stream as an append-only log or event sequence, similar to a Kafka topic partition or a database transaction log.
Stream naming
Stream names are flexible and have minimal restrictions:- Length: Between 1 and 512 bytes
- Characters: Letters, numbers, underscores (
_), and hyphens (-) - Case-sensitive:
MyStreamandmystreamare different streams - Unique: Must be unique within a basin
Examples
Stream positions
Each record in a stream has a unique position defined by two components:/home/daytona/workspace/source/common/src/record/mod.rs:23-27:
Sequence numbers
- Start at 0: The first record in a stream has
seq_num: 0 - Sequential: Each new record increments the sequence number by 1
- Unique: No two records in a stream have the same sequence number
- Immutable: Sequence numbers never change once assigned
Timestamps
- Milliseconds since epoch: Unix timestamp in milliseconds
- Monotonic: Always increasing or equal within a stream
- Configurable source: Can be client-specified, server-assigned, or hybrid
Stream tail
The tail of a stream is the position where the next record will be written:tail.seq_numis the sequence number that will be assigned to the next appended recordtail.timestampis the timestamp of the most recent record
Configuration
Streams can be configured with settings that control behavior and retention:Storage class
Controls performance characteristics (s2.dev only):- standard: Append tail latency under 400ms, cost-optimized
- express: Append tail latency under 40ms, performance-optimized
In s2-lite, storage class is not applicable as performance is determined by the underlying object storage and configuration.
Retention policy
Controls how long records are kept before automatic trimming: Age-based retention:Timestamping configuration
Controls how timestamps are assigned to appended records:Mode
client-prefer (default):- Use client-provided timestamp if present
- Fall back to server arrival time if not provided
- Best for most use cases
- Client must provide timestamp
- Append fails if timestamp is missing
- Use when client timestamp is critical
- Always use server arrival time
- Ignore any client-provided timestamp
- Use for strict server-side ordering
/home/daytona/workspace/source/api/src/v1/config.rs:75-87:
Uncapped
- false (default): Client timestamps are capped at server arrival time
- true: Client can provide future timestamps
/home/daytona/workspace/source/lite/src/backend/streamer.rs:609-611:
Delete-on-empty
Automatically delete a stream after it has been empty for a specified duration:- Set
min_age_secs: 0to disable (default) - Empty streams are deleted after the configured period
- Useful for temporary or ephemeral streams
Stream lifecycle
Creation
create_stream_on_append or create_stream_on_read enabled.
Listing streams
Checking the tail
Get the current tail position without reading records:Deletion
Streamer implementation
In s2-lite, each stream is backed by aStreamer - a Tokio task that manages the stream’s state:
From /home/daytona/workspace/source/lite/src/backend/streamer.rs:153-166:
- Serializes appends: Ensures strict ordering of writes
- Assigns sequence numbers: Manages the tail position
- Enforces fencing: Implements conditional write semantics
- Broadcasts to followers: Notifies real-time readers of new records
- Manages durability: Coordinates with SlateDB for persistence
Stream storage
In SlateDB, streams are stored using multiple key types: From/home/daytona/workspace/source/lite/src/backend/kv/mod.rs:67-98:
Performance considerations
Append throughput
From s2-lite implementation:- Pipelining: Multiple appends can be in-flight simultaneously
- Batch size: Up to 1000 records per batch, max 1 MiB metered bytes
- Backpressure: Semaphore-based flow control prevents overwhelming storage
/home/daytona/workspace/source/lite/src/backend/streamer.rs:460-477:
Read patterns
- Sequential reads: Most efficient, leverages SlateDB’s LSM structure
- Timestamp-based: Requires secondary index lookup
- Tail reads: Can follow live stream with minimal latency
Best practices
- Choose meaningful names: Use hierarchical naming for related streams
- Set appropriate retention: Balance storage costs with data retention needs
- Configure timestamping: Match client/server timestamp behavior to your use case
- Monitor tail lag: Track the difference between append and read positions
- Use fencing tokens: Implement exactly-once semantics for critical workflows
Next steps
Records
Learn about the structure of stream records
Durability
Understand durability guarantees