|
For the latest stable version, please use Korvet 0.19! |
Redis Streams Storage
Korvet uses Redis Streams as its primary storage layer for all messages.
Why Redis Streams?
-
Low latency: Sub-millisecond read/write performance
-
Consumer groups: Built-in support for coordinated consumption
-
Persistence: AOF and RDB for durability
-
Scalability: Handle millions of messages per second
Stream Structure
In Redis-only mode (the default), each topic partition maps to a single Redis Stream. When tiered
storage is enabled for the topic (remote.storage.enable=true), the partition is instead split into
segment streams that seal and roll over as they fill.
For the exact stream-key patterns, segment keying, and record field layout, see Redis Data Structures.
Segments (tiered storage only)
Segments apply only to topics with tiered storage enabled; Redis-only topics use the single per-partition stream described above.
A tiered partition starts with a single open segment (segmentId 0). The storage worker seals the
open segment and opens the next one (1, 2, …) when the open segment reaches its message-count
limit or its configured age. Sealing into discrete segments enables:
-
Efficient retention: Drop entire sealed segments when data expires
-
Archival: Offload sealed segments to remote storage without blocking writes to the open segment
-
Memory tiering with Redis Flex: Because each segment is a separate stream key, Redis Flex (Auto Tiering) can keep the open segment hot in RAM while transparently demoting colder sealed segments to flash/SSD — a warm tier between RAM and the remote object store. This is handled by Redis and needs no Korvet configuration.
Write Behavior
A few local-tier write specifics worth knowing:
-
The broker assigns each stream entry ID (a timestamp-based
timestamp-sequencevalue); it is not the*auto-generated ID. -
The
XADDcall sets only the entry ID and carries no retention arguments — retention is applied separately by the storage worker (see Retention Policies). -
Null versus empty (for example a
null-value tombstone versus an empty value) is encoded by stream-field presence: a field is omitted when its component is absent and present-but-empty when the component is a zero-length array.
The record field layout itself is documented in Redis Data Structures.
At-Rest Compression
Korvet can compress the value field before writing it to Redis. The codec is set per topic via
storage.compression.type, falling back to the server-level korvet.storage.local.compression.codec
(default lz4). Setting a topic’s storage.compression.type to none stores the value uncompressed so
non-Kafka clients can read it directly. When a codec is used, the value is written as that codec’s
standard frame format with no Korvet-specific marker prefixed (gzip, the
LZ4 frame, the
Snappy framing format, or a zstd
frame), so a non-Kafka client can decompress the field with any stock decompressor for that codec —
knowing only the topic’s codec.
storage.compression.type is fixed at topic creation. Because the stored value carries no codec marker,
the effective codec is pinned at creation: a topic that does not set its own storage.compression.type
snapshots the server-level default into its config, so a later change to the server default never
reinterprets a topic’s already-written values.
| Property | Default | Description |
|---|---|---|
|
|
Default codec for values at rest in Redis, snapshotted onto topics that do not pin their own |
At-rest compression is independent of Kafka protocol compression (compression.type). At-rest compression applies to data stored in Redis, while protocol compression applies to data in transit between Kafka clients and Korvet.
|
Retention Policies
Korvet enforces retention with a background storage worker that issues XTRIM, not at produce time:
-
retention.ms: Time-based retention (default: 7 days) -
retention.bytes: Size-based retention (default: unlimited) -
compression.type: Compression type for fetch responses (none, gzip, snappy, lz4, zstd)
How Retention Works
A leader-locked background storage worker periodically trims each stream with XTRIM:
-
Count-based (
retention.bytes):XTRIM <streamKey> MAXLEN <count>. The byte limit is converted to a message count by dividing by the topic’s measured average message size (falling back to 1024 bytes when no measurement exists yet). -
Time-based (
retention.ms):XTRIM <streamKey> MINID <minTimestamp>, whereminTimestampis the current time minusretention.ms.
# Count-based trim
XTRIM korvet:storage:local:my-topic:0 MAXLEN 1000
# Time-based trim (keep messages newer than minTimestamp)
XTRIM korvet:storage:local:my-topic:0 MINID 1234567890000
|
Configuring Retention
Set retention when creating topics via Kafka Admin API:
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
AdminClient admin = AdminClient.create(props);
NewTopic topic = new NewTopic("my-topic", 3, (short) 1);
topic.configs(Map.of(
"retention.ms", "86400000", // 1 day
"retention.bytes", "1073741824", // 1 GB
"compression.type", "lz4" // Compress fetch responses with LZ4
));
admin.createTopics(List.of(topic));
Or configure defaults in application.yml via a catch-all pattern:
korvet:
topics:
- name: "*"
retention:
time: 7d
bytes: 10GB
compression: lz4
Performance Tuning
Async Operations
Korvet uses asynchronous Redis operations for maximum throughput:
-
All Redis commands use Lettuce’s async API (
RedisFuture) -
Operations return
CompletableFutureto avoid blocking -
Multiple operations execute in parallel
-
Netty event loop threads remain non-blocking
Benefits: * Higher throughput with fewer threads * Better resource utilization * Reduced latency under load
Pipelining
Korvet automatically batches Redis operations using Lettuce’s command pipelining:
-
Multiple commands are batched together
-
setAutoFlushCommands(false)delays command execution -
flushCommands()sends all commands in a single network round-trip -
Significantly improves throughput for high-volume producers
Example: Producing 1000 messages sends 1000 XADD commands in a single pipeline instead of 1000 round-trips.
* Configurable pool size based on workload
Compression Types
Korvet supports two types of compression:
1. At-Rest Compression
Compresses the stored record value at rest in Redis Streams:
-
Where: Applied to the
valuefield in Redis Streams -
When: At write time (produce) and read time (fetch)
-
Configuration: Per topic via
storage.compression.type(create-time only), falling back to the server-levelkorvet.storage.local.compression.codec(defaultlz4). The effective codec is pinned at topic creation. Set a topic tononeto keep values directly readable by non-Kafka clients. -
Use case: Reduce Redis memory usage for large or repetitive data
How it works:
-
Producer writes: The value is compressed before storing in Redis, as the topic codec’s standard frame format with no Korvet-specific marker
-
Consumer reads: The value is decompressed when fetching from Redis
-
The codec is pinned per topic at creation, so the value field is always a plain standard frame a non-Kafka client can decompress with any stock decompressor for that codec
-
Transparent to Kafka clients - they receive uncompressed data
-
Independent of Kafka protocol compression
Benefits:
-
Reduces Redis memory usage
-
Lower storage costs
-
Faster Redis persistence (smaller AOF/RDB files)
-
No impact on Kafka client compatibility
See At-Rest Compression for configuration details.
2. Protocol Compression (Kafka Standard)
Compresses Kafka protocol messages between clients and Korvet:
-
Where: Applied to Kafka Fetch/Produce request/response payloads
-
When: During network transmission
-
Configuration:
compression.typetopic config (none, gzip, snappy, lz4, zstd) -
Use case: Reduce network bandwidth between Kafka clients and Korvet
How it works:
-
Producer side: Kafka clients can send compressed or uncompressed batches; Korvet decompresses them before storing
-
Consumer side: Korvet compresses fetch responses based on topic’s
compression.typeconfiguration -
Storage: Messages are stored uncompressed in Redis (unless at-rest compression is enabled)
Benefits:
-
Reduces network bandwidth for fetch responses
-
Transparent to clients - works with all Kafka clients
-
Flexible per-topic configuration
-
Standard Kafka feature
Compression Comparison
| Feature | At-Rest Compression | Protocol Compression |
|---|---|---|
Purpose |
Reduce Redis memory usage |
Reduce network bandwidth |
Applied at |
Redis storage layer |
Kafka protocol layer |
Configuration |
|
|
Affects |
Redis memory, persistence |
Network traffic |
Transparent to |
Kafka clients |
Storage layer |
Recommended for |
Large messages, repetitive data |
High-throughput consumers |
You can use both types of compression together! For example, set korvet.storage.local.compression.codec=zstd to save Redis memory and compression.type=lz4 for fast network compression.
|
See Compression for protocol compression configuration details.