Skip to main content
Records are the fundamental unit of data in S2. Each record is an immutable data item with headers and a body, stored in an ordered stream.

Record structure

Every record has three main components:
From /home/daytona/workspace/source/common/src/record/mod.rs:261-265:

Position

The position identifies where the record is in the stream:
  • seq_num: Unique sequence number (0-based, incrementing)
  • timestamp: Unix timestamp in milliseconds

Headers

A list of name-value pairs (both are binary data):
  • Names must not be empty (except for command records)
  • Both names and values are arbitrary bytes
  • Headers contribute to metered size and storage

Body

Arbitrary binary data representing the record’s payload:

Record types

S2 supports two types of records: From /home/daytona/workspace/source/common/src/record/mod.rs:79-84:

Envelope records

Envelope records are the standard record type for application data:
  • Contain zero or more headers with non-empty names
  • Contain a body with arbitrary data
  • Used for storing events, messages, logs, etc.

Command records

Command records are special control records that affect stream behavior:
  • Have exactly one header with an empty name
  • The header value identifies the command type
  • The body contains the command payload
From /home/daytona/workspace/source/common/src/record/mod.rs:166-180:

Fence command

Sets a fencing token for conditional writes:
Fencing tokens:
  • Must be ≤ 48 bytes in length
  • Start as an empty string by default
  • Can be any arbitrary bytes
  • Enable exactly-once semantics
From /home/daytona/workspace/source/common/src/record/fencing.rs:

Trim command

Marks a trim point in the stream:
  • Body must be exactly 8 bytes (u64 sequence number)
  • Records before the trim point become unreadable
  • The trim point itself can never be trimmed
  • Used for implementing retention policies
From /home/daytona/workspace/source/lite/src/backend/streamer.rs:210-232:
Trimming is irreversible. Once records are trimmed, they cannot be recovered.

Data encoding formats

Records can be represented in two formats when transmitted over the API: From /home/daytona/workspace/source/api/src/data.rs:42-48:

Raw format (default)

  • Header names, values, and body are represented as UTF-8 strings
  • Efficient for text data
  • Storage is in UTF-8 encoding

Base64 format

  • Header names, values, and body are Base64-encoded
  • Safe for binary data transmission
  • Efficient storage of binary data (stored as original bytes)
Specify format using the S2-Format header:

Metered size

Each record has a metered size that determines:
  • Billing (for s2.dev)
  • Batch limits (max 1 MiB per batch)
  • Storage tracking
From /home/daytona/workspace/source/common/src/record/mod.rs:114-125:
Metered size includes:
  • 8 bytes overhead per record
  • 2 bytes per header (delimiter overhead)
  • Header deep size: Sum of all header name and value lengths
  • Body length: Size of the body in bytes
Metered size is calculated on the raw bytes, not the Base64-encoded representation.

Record encoding

Records are stored in a compact binary format:

Magic byte

The first byte encodes the record type and metered size length: From /home/daytona/workspace/source/common/src/record/mod.rs:86-90:
  • Bits 0-2: Record type (1=Command, 2=Envelope)
  • Bits 3-4: Metered size variable length (0=1 byte, 1=2 bytes, 2=3 bytes)

Wire format

Record data for envelope records:
Each header:

Appending records

Records are appended in batches:
Response:

Batch constraints

From /home/daytona/workspace/source/api/src/v1/stream/mod.rs:363-367:
  • Minimum: 1 record per batch
  • Maximum: 1000 records per batch
  • Size limit: Total metered size ≤ 1 MiB per batch

Append options

timestamp (optional):
  • Client-provided timestamp in milliseconds
  • Behavior depends on stream timestamping configuration
  • Server enforces monotonicity
match_seq_num (optional):
  • Enforce that first record gets this sequence number
  • Fails with seq-num-mismatch if tail has moved
  • Enables optimistic concurrency control
fencing_token (optional):
  • Enforce the current fencing token matches
  • Fails with fencing-token-mismatch if token differs
  • Enables exactly-once semantics
From /home/daytona/workspace/source/api/src/v1/stream/mod.rs:360-372:

Reading records

Response:

Record validation

Records are validated on append: From /home/daytona/workspace/source/common/src/record/mod.rs:167-180:
  • Headers with empty names are only allowed for command records
  • Command records must have exactly one header
  • Fencing tokens must be ≤ 48 bytes
  • Trim points must be 8 bytes (u64)
  • Total batch size must be ≤ 1 MiB metered bytes

Best practices

  1. Use headers for metadata: Store searchable metadata in headers
  2. Choose appropriate format: Use raw for text, base64 for binary
  3. Batch appends: Send multiple records per request for better throughput
  4. Include timestamps: Provide client timestamps when event time matters
  5. Leverage fencing: Use fencing tokens for exactly-once critical operations
  6. Monitor metered size: Large records impact performance and costs

Next steps

Durability

Learn about durability guarantees

Reading records

Explore record read API