Documentation Index
Fetch the complete documentation index at: https://mintlify.com/s2-streamstore/s2/llms.txt
Use this file to discover all available pages before exploring further.
Records are the fundamental units of data in S2 streams. The CLI provides commands for appending, reading, and tailing records.
Append records
Append records to a stream from stdin:
echo "Hello, S2!" | s2 append s2://my-basin/events
Output:
✓ [APPENDED] 0..0 // tail: 1 @ 1704067200000
This shows:
- Sequence number range appended:
0..0 (single record)
- New tail position: sequence
1, timestamp 1704067200000
Append from a file
s2 append s2://my-basin/events --input events.txt
Or using shell redirection:
s2 append s2://my-basin/events < events.txt
Append multiple records
Records are newline-delimited by default:
cat <<EOF | s2 append s2://my-basin/events
First record
Second record
Third record
EOF
Output:
✓ [APPENDED] 0..2 // tail: 3 @ 1704067200500
Append JSON records
Use the --format json flag for JSON records:
cat <<EOF | s2 append s2://my-basin/events --format json
{"event": "user_login", "user_id": 123}
{"event": "page_view", "page": "/home"}
EOF
JSON records are still newline-delimited (one JSON object per line).
Append JSON with base64 bodies
Use --format json-base64 when record bodies contain binary data:
cat <<EOF | s2 append s2://my-basin/events --format json-base64
{"body": "SGVsbG8sIFMyIQ==", "headers": [{"name": "content-type", "value": "text/plain"}]}
EOF
Append with fencing token
Enforce a fencing token to coordinate writers:
echo "Data" | s2 append s2://my-basin/events --fencing-token "writer-1"
This append will be rejected if the stream’s current fencing token doesn’t match.
Append with sequence number match
Only append if the next sequence number matches the expected value:
echo "Data" | s2 append s2://my-basin/events --match-seq-num 100
This is useful for ensuring no records were appended by another writer.
Control batching with linger
Adjust how long to wait before flushing a batch:
cat large-file.txt | s2 append s2://my-basin/events --linger 100ms
Default: 5ms
Lower values reduce latency but may increase number of API calls. Higher values improve throughput for bulk appends.
Read records
Read all records from a stream:
s2 read s2://my-basin/events
Output:
⦿ 1024 bytes (10 records in range 0..=9)
First record
Second record
...
By default, read will tail the stream indefinitely, waiting for new records. Use --count or press Ctrl+C to stop.
Read a specific number of records
s2 read s2://my-basin/events --count 100
Reads the first 100 records and exits.
Read from a specific sequence number
s2 read s2://my-basin/events --seq-num 1000
Starts reading from sequence number 1000 (inclusive).
Read from a timestamp
Read records from a specific Unix timestamp (milliseconds):
s2 read s2://my-basin/events --timestamp 1704067200000
Or use a human-friendly relative time:
s2 read s2://my-basin/events --ago 1h
s2 read s2://my-basin/events --ago 24h
s2 read s2://my-basin/events --ago 7d
Read the last N records
Read starting from N records before the tail:
s2 read s2://my-basin/events --tail-offset 100
Reads the last 100 records.
Limit by bytes
Stop reading after consuming a certain number of bytes:
s2 read s2://my-basin/events --bytes 10485760 # 10 MiB
Read until a timestamp
Read records up to (but not including) a specific timestamp:
s2 read s2://my-basin/events --timestamp 1704067200000 --until 1704070800000
Clamp start position at tail
If the requested start position is beyond the tail, start at the tail instead of returning an error:
s2 read s2://my-basin/events --seq-num 999999 --clamp
Output to a file
s2 read s2://my-basin/events --count 1000 --output records.txt
Or using shell redirection:
s2 read s2://my-basin/events --count 1000 > records.txt
Text format (default):
s2 read s2://my-basin/events --format text
Outputs raw record bodies, one per line.
JSON format:
s2 read s2://my-basin/events --format json
Outputs records as JSON objects with metadata:
{"seq_num":0,"timestamp":1704067200000,"body":"First record","headers":[]}
{"seq_num":1,"timestamp":1704067200100,"body":"Second record","headers":[]}
JSON with base64 bodies:
s2 read s2://my-basin/events --format json-base64
Outputs records with base64-encoded bodies:
{"seq_num":0,"timestamp":1704067200000,"body":"Rmlyc3QgcmVjb3Jk","headers":[]}
Tail a stream
Show the last N records (like Unix tail):
s2 tail s2://my-basin/events
Default: Shows last 10 records and exits.
Specify number of records
s2 tail s2://my-basin/events --lines 100
# or short form
s2 tail s2://my-basin/events -n 100
Follow mode
Continuously show new records (like tail -f):
s2 tail s2://my-basin/events --follow
# or short form
s2 tail s2://my-basin/events -f
Press Ctrl+C to stop following.
Same format options as read:
s2 tail s2://my-basin/events -f --format json
Common workflows
Stream processing pipeline
# Read records, process with jq, append to another stream
s2 read s2://raw/events --format json --count 1000 \
| jq -c 'select(.user_id != null)' \
| s2 append s2://filtered/events --format json
Export records to file
# Export all records from the last 24 hours
s2 read s2://prod/events --ago 24h --format json > events-$(date +%Y%m%d).jsonl
Continuous monitoring
# Monitor events in real-time
s2 tail s2://prod/events -f --format json | jq -c 'select(.level == "error")'
Bulk import from file
# Import records with progress indication
pv large-dataset.txt | s2 append s2://imports/dataset --linger 100ms
Replay records to different stream
# Replay records from specific time range
s2 read s2://prod/events \
--timestamp 1704067200000 \
--until 1704070800000 \
| s2 append s2://dev/events
Coordinated append with fencing
#!/bin/bash
# Set fencing token for this writer
WRITER_ID="writer-$(hostname)-$$"
s2 fence s2://prod/events "$WRITER_ID"
# Append with fencing protection
while read -r line; do
echo "$line" | s2 append s2://prod/events --fencing-token "$WRITER_ID"
done
Incremental backup
#!/bin/bash
# Track last backed up sequence number
LAST_SEQ=$(cat .last-backup-seq 2>/dev/null || echo 0)
# Read new records since last backup
s2 read s2://prod/events --seq-num $LAST_SEQ --count 10000 > backup-$(date +%Y%m%d-%H%M%S).txt
# Update checkpoint
TAIL=$(s2 check-tail s2://prod/events | awk '{print $1}')
echo $TAIL > .last-backup-seq
Appending command records
Certain operations like trim and fence append special command records to the stream. When reading with --format text, these are displayed as:
trim to 10000 // 12345 @ 1704067200000
new fencing token "my-token" // 12346 @ 1704067200100
These command records are not included in the record body output.
Batching
- Use
--linger to control batch size vs. latency tradeoff
- Larger batches (higher linger) improve throughput
- Smaller batches (lower linger) reduce latency
Reading
- Use
--count or --bytes to limit reads
- Filter records early in the pipeline to reduce data transfer
- Use
--format text for better performance if metadata isn’t needed
Network usage
Enable compression in your CLI configuration:
s2 config set compression zstd
Examples
Append timestamped events
while true; do
echo "{\"timestamp\": $(date +%s000), \"message\": \"heartbeat\"}" \
| s2 append s2://monitoring/heartbeat --format json
sleep 60
done
Read and count records
s2 read s2://prod/events --count 10000 --format json \
| jq -r '.event' \
| sort \
| uniq -c \
| sort -rn
Tail with filtering
s2 tail s2://prod/logs -f --format json \
| jq -r 'select(.level == "error") | "\(.timestamp) \(.message)"'
Read records from specific hour
# Read records from 2024-01-01 14:00:00 to 14:59:59
START=$(date -d "2024-01-01 14:00:00" +%s)000
END=$(date -d "2024-01-01 15:00:00" +%s)000
s2 read s2://prod/events --timestamp $START --until $END