|
This version is still in development and is not considered stable yet. For the latest stable version, please use Korvet 0.19! |
Migrating
This page describes how to upgrade an existing Korvet 0.12.5 deployment to the current version. The upgrade involves three kinds of changes:
-
Renamed configuration options (
KORVET_SERVER_*is nowKORVET_BROKER_*). -
A new Redis key layout for metadata and stream storage.
-
A one-time, offline data migration performed with the
korvet migratecommand.
Only 0.12.5 is supported as a full migration source. The migrate command detects the source layout and refuses to run against any other version, with one exception: against a deployment that already uses the current layout it converts leftover per-partition committed-offset keys, as described in Upgrading from 0.13–0.16.
|
Upgrading from 0.13–0.16
One breaking change applies when upgrading a deployment that already uses the current key layout (Korvet 0.13 through 0.16):
-
Committed offsets moved from one String per group and partition (
{namespace}:broker:commit:{topic:partition:group}) to one Hash per group ({namespace}:broker:commit:{group}). The broker does not read the old keys; runkorvet migrate --executeonce (with the broker offline) to convert them. The conversion is idempotent and re-running it converges. If the broker finds leftover old-format keys at startup it logs a prominent warning directing to the migrate command, and consumer groups appear to have no committed offsets until the conversion has run.
$ korvet migrate -u redis://localhost:6379 --execute
Detected Korvet Redis layout: <current version>
Target Korvet version: <current version>
Migration executed.
MIGRATE committed offsets korvet:broker:commit: (6 offsets)
Committed-offset keys whose topic no longer exists cannot be converted unambiguously and are skipped with a SKIP line; they are ignored by the broker and can be deleted manually.
Configuration Changes
The configuration model was reorganized between 0.12.5 and the current version.
The most visible change is that the Kafka listener moved from korvet.server.* to korvet.broker.* (environment variables: KORVET_SERVER_* to KORVET_BROKER_*), but several other areas were renamed, restructured, or removed.
Review every korvet.* property and environment variable in your deployment against the tables below; see Configuration for the full current reference with defaults.
The tables list properties in their dotted form. The environment-variable form follows Spring Boot’s relaxed binding in both versions: uppercase the property name and replace . and - with _. For example, korvet.broker.max-request-bytes is set with KORVET_BROKER_MAX_REQUEST_BYTES, and the same rule yields the 0.12.5 name on the left-hand side of each row. Sections below call out the cases where the resulting environment variable is not a mechanical rename.
Broker Listener (korvet.server.* → korvet.broker.*)
| 0.12.5 | Current | Notes |
|---|---|---|
|
|
Moved to the root |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Renamed. |
|
|
Renamed. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
removed |
No longer user-configurable. |
For environment variables, the mapping is mechanical: KORVET_SERVER_MAX_REQUEST_SIZE becomes KORVET_BROKER_MAX_REQUEST_BYTES, KORVET_SERVER_ADVERTISED_HOST becomes KORVET_BROKER_ADVERTISED_HOST, and so on.
Redis Connection (korvet.redis.*)
| 0.12.5 | Current | Notes |
|---|---|---|
|
unchanged |
|
|
|
Now has an explicit default of |
|
|
Renamed. |
|
|
Now nested under |
|
|
Now nested under |
|
removed |
The per-purpose connection pools (metadata, archival source, committed offsets) were consolidated into the single |
|
removed |
|
|
removed |
|
|
removed |
|
|
removed |
|
|
unchanged |
Note that because . and - both map to _, the environment variables for the pool settings are unchanged despite the nesting: KORVET_REDIS_POOL_SIZE and KORVET_REDIS_POOL_MAX_WAIT keep working. The renamed I/O thread setting becomes KORVET_REDIS_IO_THREAD_POOL_SIZE → KORVET_REDIS_IO_THREADS.
Topic Configuration (korvet.topics.*)
In 0.12.5, korvet.topics.* was a single, flat set of defaults applied to all topics.
It is now a *list* of glob patterns evaluated first-match-wins, with the same flat per-topic setting names inside each list entry.
A flat 0.12.5 default block translates to one catch-all name: "*" entry:
# 0.12.5
korvet:
topics:
auto-create: true
partitions: 3
retention-time: 7d
retention-bytes: -1
# Current
korvet:
topics:
- name: "*"
auto-create: true
partitions: 3
retention-time: 7d
retention-bytes: -1
| 0.12.5 | Current | Notes |
|---|---|---|
|
|
Now per-pattern. |
|
|
|
|
|
|
|
|
|
|
|
Now per-pattern. |
|
|
Now per-pattern. |
|
|
Now per-pattern. |
|
|
Now per-pattern. |
|
|
|
|
|
Buckets were replaced by segments; a |
|
removed |
At-rest value compression is now configured server-wide with |
|
removed |
The legacy RAW/JSON/AUTO value layout no longer exists; all records use the new envelope format. |
|
removed |
No longer user-configurable; average message size is measured at runtime. |
|
removed |
In environment-variable form, the list entries are addressed by index, and each entry needs a NAME. The flat 0.12.5 variables translate to a 0 catch-all entry:
# 0.12.5
export KORVET_TOPICS_AUTO_CREATE=true
export KORVET_TOPICS_PARTITIONS=3
export KORVET_TOPICS_RETENTION_TIME=7d
export KORVET_TOPICS_RETENTION_BYTES=-1
# Current
export KORVET_TOPICS_0_NAME='*'
export KORVET_TOPICS_0_AUTO_CREATE=true
export KORVET_TOPICS_0_PARTITIONS=3
export KORVET_TOPICS_0_RETENTION_TIME=7d
export KORVET_TOPICS_0_RETENTION_BYTES=-1
Storage (korvet.storage.*)
In 0.12.5 the only storage configuration was the remote (cold) tier under korvet.storage.remote.\*, backed by Delta Lake.
The current version splits storage configuration into three areas:
-
korvet.storage.local.*— new: the Redis (hot) tier, including the at-rest compression codec and an optional dedicated Redis client for message storage (see New Configuration Areas). -
korvet.storage.worker.*— new: the background worker that handles retention and segment tiering, replacing the 0.12.5 archiver. -
korvet.storage.remote.*— the cold tier, which moved from Delta Lake to Apache Iceberg:
| 0.12.5 | Current | Notes |
|---|---|---|
|
|
Unchanged, but now points at an Iceberg warehouse. |
|
unchanged |
A new |
|
removed |
Credential resolution is now automatic. |
|
removed |
Archiver internals are no longer exposed; the storage worker ( |
|
removed |
Replaced by Iceberg writer settings |
|
removed |
The cold index is no longer user-configurable. |
New Configuration Areas
These did not exist in 0.12.5; defaults are generally sensible, but review them as part of the upgrade:
-
korvet.broker.enabled,korvet.broker.response-queue-timeout,korvet.broker.produce-timeout,korvet.broker.rebalance-threads— new listener tuning options. -
korvet.broker.sasl.*— SASL authentication (PLAIN, SCRAM-SHA-256). -
korvet.broker.metrics.*— consumer-offset gauge publishing. -
korvet.redis.circuit-breaker.*— circuit breaker for stream operations (enabled by default). -
korvet.storage.local.compression.codec— server-wide at-rest compression codec (defaultnone). -
korvet.storage.local.redis.*— optional separate Redis client/pool for message storage. -
korvet.storage.worker.*— background worker for retention and segment management. -
korvet.schema-registry.*— embedded schema registry. -
korvet.admin.— Admin API bootstrap credentials. The defaults areadmin/admin; *change these in production.
Redis Key Layout Changes
The current version uses a different Redis key layout than 0.12.5, for both metadata and message storage. This is why a data migration is required: a current server cannot read 0.12.5 keys directly.
Metadata Keys
| Area | 0.12.5 | Current |
|---|---|---|
Topic registry |
|
|
Broker registry |
|
|
Credentials |
|
|
Committed offsets |
|
|
Stream Storage Keys
Messages are still stored in Redis Streams, but both the key names and the per-entry field layout changed.
Keys:
-
0.12.5:
{namespace}:stream:{topic}:{partition} -
Current:
{namespace}:storage:local:{topic}:{partition}
Stream entry fields:
In 0.12.5, each record was stored as multiple stream fields: the record key under __key, the value either under __value (raw) or flattened into one field per top-level JSON attribute, and each header under __header.<name>.
The current format stores each record as a fixed envelope:
| Field | Content |
|---|---|
|
Record value bytes, stored with the configured at-rest codec ( |
|
Record key bytes, verbatim |
|
All record headers encoded into one self-delimiting blob |
|
Kafka record timestamp as a decimal string |
Stream entry IDs (and therefore Kafka offsets) are preserved by the migration.
0.12.5 did not store the producer’s record timestamp, so migrated records carry no timestamp (-1) rather than a fabricated one.
|
The korvet migrate Command
The migrate command reads a 0.12.5 keyspace and rewrites it in place into the current layout.
It migrates, in order:
-
Topic registry — converts the topic Set and per-topic Hashes into the new JSON document, and pins each topic’s
storageCompressionTypeto the codec used during migration. -
Broker registry — converts the broker Set and per-broker Hashes into the
broker:nodesJSON document. -
Credentials — moves each credential Hash to the new key and renames its fields.
-
Committed offsets — rewrites each per-partition committed-offset String into the owning group’s Hash, preserving values.
-
Local streams — rewrites every
{topic}:{partition}stream into the new location, converting each entry from the legacy field layout to the new envelope format (decompressing legacy values where needed and re-compressing with the chosen codec), preserving stream entry IDs.
By default the command is a dry run: it prints what it would migrate without writing anything.
Pass --execute to apply the migration.
Successfully migrated source keys are deleted unless --keep-source is given.
Options
| Option | Default | Description |
|---|---|---|
|
(required) |
Redis connection URI of the deployment to migrate. |
|
|
Korvet keyspace/namespace used by the legacy deployment. |
|
off |
Apply the migration. Without this option the command only prints a dry run. |
|
off |
Replace existing destination keys. Without it, the command skips any area whose destination key already exists (useful when re-running after a partial migration). |
|
off |
Keep legacy source keys after successful migration. By default, migrated source keys are deleted. |
|
|
At-rest codec to write migrated stream values with. Must match the target server’s |
|
|
Number of streams (topic-partitions) to migrate in parallel. Each transfer rewrites one stream, in order, on its own Redis connection. Increase this to speed up migrations with many topic-partitions. |
The command also accepts the standard Redis connection options (TLS, credentials, timeouts) shared by all korvet commands; run korvet migrate --help for the full list.
Output and Exit Code
The command prints the detected source layout, the target version, and one line per migrated (MIGRATE), skipped (SKIP), or failed (ERROR) item:
$ korvet migrate -u redis://localhost:6379 --execute
Detected Korvet Redis layout: 0.12.5
Target Korvet version: {page-component-version}
Migration executed.
MIGRATE topic registry korvet:topics (3 topics)
MIGRATE broker registry korvet:broker:nodes (1 brokers)
MIGRATE credentials korvet:broker:credentials: (2 credentials)
MIGRATE committed offsets korvet:broker:commit: (6 offsets)
MIGRATE local streams korvet:storage:local: (9 streams)
The exit code is 0 on success (including dry runs) and 1 if any error was reported.
Step-by-Step Migration
The migration must be performed offline: no 0.12.5 broker may be writing to Redis while data is being rewritten, and the new broker must not start until the migration has completed.
-
Stop producers and consumers. Drain or pause all Kafka clients so no data is lost while the broker is down.
-
Shut down the 0.12.5 broker(s). Stop every Korvet server process or container that uses this Redis database.
-
Back up the Redis database. Take an RDB snapshot (or use your usual backup mechanism) so you can roll back if needed:
redis-cli -u redis://localhost:6379 BGSAVE -
Run a dry run. Inspect what will be migrated without changing anything:
korvet migrate -u redis://localhost:6379Review the
MIGRATE/SKIPlines. If you used a custom namespace, pass--namespace <name>. -
Execute the migration:
korvet migrate -u redis://localhost:6379 --executeFor large deployments with many topic-partitions, parallelize stream migration:
korvet migrate -u redis://localhost:6379 --execute --transfers 8If you want to keep the legacy keys around until you have verified the new deployment, add
--keep-source(note that this temporarily doubles the memory used by stream data).Verify that the command exits with code
0and reports noERRORlines. If it fails partway, fix the cause and re-run with--execute --replaceto overwrite partially written destinations. -
Update the broker configuration. Apply all renames from Configuration Changes above:
korvet.server.*tokorvet.broker.*(includingkorvet.server.keyspacetokorvet.namespace), thekorvet.redispool restructure, and the flatkorvet.topics.\*block to a pattern list. Remove any settings listed as removed. If you passed a non-default--storage-compression, setkorvet.storage.local.compression.codecto the same value. -
Start the new broker version. Deploy and start the current Korvet release against the same Redis database.
-
Verify. Check that the broker starts cleanly, then validate with a Kafka client: list topics, consume existing messages from an old topic, produce and consume a new message, and confirm consumer groups resume from their committed offsets.
-
Resume traffic. Re-enable producers and consumers. Once you are satisfied, you can delete the legacy keys if you used
--keep-source, and remove the backup per your retention policy.
Rollback
If verification fails, stop the new broker, restore the Redis backup taken in step 3, and restart the 0.12.5 deployment. Because the migration runs offline, no new data is produced between backup and verification, so the restore is lossless.
Importing Topics from Kafka
The korvet import topics command imports topic definitions, and optionally topic data, from an existing Kafka cluster into Korvet.
It reads topic metadata (partition counts and explicitly set topic configurations) from the source cluster with the Kafka AdminClient and creates matching topics on the target Korvet server.
With --data it also copies records, partition by partition, preserving key, value, headers, timestamp, and per-partition order.
Add --live to keep tailing the source after the initial copy (for cutover without stopping producers first), and --transfers to copy several partitions in parallel.
With --groups it also migrates consumer group committed offsets, translating each position by record timestamp.
korvet import topics \
--source-bootstrap-server kafka:9092 \
--target-bootstrap-server korvet:9092 \
--topic orders \
--topic payments
By default the command is a dry run: it prints the import plan without writing anything to the target.
Pass --apply to create the planned topics.
Options
| Option | Default | Description |
|---|---|---|
|
(required) |
The source Kafka cluster to read topic definitions from. |
|
(required) |
The Korvet server to import topics into. |
|
all topics |
A source topic to import (repeatable). If omitted, all non-internal source topics (names not starting with |
|
off |
Also copy records, partition by partition, preserving key, value, headers, timestamp, and per-partition order. Korvet assigns new offsets. |
|
off |
With |
|
|
Number of partitions to copy in parallel with |
|
off |
Also migrate consumer group committed offsets for the imported topics. Kafka offsets do not map 1:1 to Korvet offsets, so each committed position is translated: the target position becomes the first copied record whose record timestamp is at or after the timestamp of the first unconsumed source record. Requires the records to be present on the target (run with or after |
|
off |
Create the planned topics (and with |
Plan Output
For each source topic the plan reports one of three actions:
-
CREATE— the topic does not exist on the target and will be created with the source partition count and the supported source configurations. -
EXISTS— the topic already exists on the target with a matching partition count; it is skipped. -
INCOMPATIBLE— the topic exists on the target with a different partition count. The command reports the conflict and, with--apply, aborts without creating anything.
Only explicitly set source topic configurations (dynamic topic configs) are considered; broker defaults are not copied.
Each configuration is classified as either applied (supported by Korvet, see Topic Management) or skipped with a warning when Korvet does not support it.
Replication factor is not carried over: Korvet topics do not use client-visible replication, so a source replication factor greater than 1 is reported as ignored.
With --data the plan also reports the number of records that would be copied per topic.
Exit Code
The command exits non-zero when any topic is INCOMPATIBLE, or when a topic named with --topic does not exist on the source cluster.
Otherwise it exits zero, including for a dry run.
Limitations
-
ACLs are not copied.
-
Group offset translation is timestamp-based, so records sharing the boundary record’s timestamp can be re-delivered once: resumed consumers should tolerate at-least-once delivery around the cutover point. Groups still containing active members are reported with a warning, since their source positions keep moving after the snapshot.
-
Korvet assigns its own offsets to copied records; Kafka offsets are not preserved. Point consumers at the new cluster by timestamp or from the beginning rather than by carried-over offsets.
-
Without
--live, record copy stops at each partition’s end offset as observed when the copy starts; records produced to the source afterwards are not copied. Stop producers before a cutover import, or use--liveand interrupt the import once producers have been switched to Korvet and the reported lag reaches zero. -
--datacopies into existing target topics too, so re-running an import with--dataagainst a target that already holds the records duplicates them. -
Existing target topics are never altered; only missing topics are created.