How Kafka tiered storage moves old segments to object storage

August 19, 2026

How Kafka tiered storage moves old segments to object storage

August 19, 2026
How Kafka tiered storage moves old segments to object storage

TL;DR

  • Tiered storage splits a Kafka log into a local tier on broker disks and a remote tier in object storage, so retention stops being a function of how many SSDs you bought.
  • You enable tiered storage per topic with remote.storage.enable=true, provided the broker-level feature is enabled. Storage is then governed by two sets of retention policies: local.retention.ms / local.retention.bytes decide when segments are purged from local disk, while standard retention.ms / retention.bytes govern total log retention across both tiers combined.
  • Apache Kafka ships the framework but no production RemoteStorageManager, so you supply a plugin such as the Aiven open source one before any of this works.
  • The win is smaller brokers and much faster rebalances, because replication only has to move the local tier.
  • Watch out for compacted topics, which are not supported, and for historical reads, which now come off S3 rather than page cache.

Introduction

A while back we were sizing a Kafka cluster for a customer who wanted thirty days of retention on their event topics. The throughput was modest, maybe 40MB/s across the cluster, but thirty days of that with replication factor 3 works out at roughly 300TB of provisioned block storage. The brokers themselves needed a fraction of that CPU and memory. We were, in effect, buying a very expensive and very slow object store, and paying EBS prices for it.

40MB/s sustained, thirty day retention
  Local disk only Tiered, six hour local retention
Data on broker disks, replication factor 3 311 TB 2.6 TB, provisioned at 4 TB for headroom
Data in object storage, single copy none 104 TB
EBS gp3 at $0.0928 per GB month $28,860 $370
S3 Standard at $0.024 per GB month none $2,500
Monthly storage total $28,860 $2,870

Request charges are left out of the table since they are noise at this volume, a few dollars a month for the PUTs, though a full replay pulling tens of terabytes back out of S3 will show up on the bill.

That is a ninety per cent cut on the storage line, and it understates the saving because you also stop sizing instances around disk attachment limits and IOPS, so the brokers themselves get smaller.

That is the problem tiered storage solves, and as of Kafka 3.9 it is production ready rather than the early access curiosity it was in 3.6.

What tiered storage actually changes

Kafka's own documentation makes the observation that the design hangs on: Kafka data is mostly consumed in a streaming fashion using tail reads, which are served out of the page cache, while older data is read infrequently for backfill or recovery. Keeping all of it on local disk means every byte of cold data is paying for hot storage.

With tiered storage enabled, a broker still writes to its local log directory exactly as before. Once a segment is closed, the RemoteLogManager copies it, along with its offset and time indexes, to the remote tier. The local copy remains on disk to handle hot reads until local retention policies (local.retention.*) trigger its deletion. Even after the local copy is deleted, the segment remains part of the log and fully readable. A consumer requesting an offset beyond local retention is served transparently from the remote tier, with no client-side changes and no separate archive topic to manage.

Kafka tiered storage: local tier on broker disk, remote tier in object storage Producers append to the active segment on the broker's local disk. When a segment closes, the RemoteLogManager copies it and its indexes to object storage. Closed segments stay on local disk until local.retention expires, then are purged locally while remaining readable from the remote tier. Tail reads are served from page cache; historical reads are served from object storage. Producers append LOCAL TIER: broker disk retained for local.retention.ms, served from page cache active segment closed closed purged locally purged locally newest oldest RemoteLogManager copies closed segments REMOTE TIER: object storage S3, GCS or Azure Blob. Retained for retention.ms, single copy, no replication factor. segments plus their offset and time indexes Tail reads served from the local tier, page cache microseconds Historical reads served from the remote tier, ranged GETs tens to hundreds of ms, per-request cost Same topic, same offsets, no client change either way.
Closed segments are copied to object storage, then purged from local disk once local.retention expires, but stays fully readable at the same offsets.

The part we find most interesting operationally is not the storage bill. It is what happens to replication. When a broker fails or you add one, only the local tier has to be re-replicated. Instead of streaming 100TB across the network to rebuild a replica, you stream whatever your local retention window holds, which might be a few hours. Cluster expansion goes from a weekend job to a coffee break.

The two plugins you have to supply

This is where most people trip up on the first attempt. Apache Kafka defines two interfaces and ships a usable default for only one of them.

RemoteStorageManager handles the lifecycle of remote log segments and indexes. Kafka provides no default implementation. There is a LocalTieredStorage class in the test sources which is fine for a laptop demo and absolutely not for production. For real clusters you take the Aiven tiered-storage-for-apache-kafka plugin, which is Apache 2.0 licensed and supports S3, GCS and Azure Blob, or you use whatever your vendor bundles.

RemoteLogMetadataManager tracks which segments live remotely and at which offsets. Here Kafka does give you a default, TopicBasedRemoteLogMetadataManager, which keeps the metadata in an internal __remote_log_metadata topic.

Turning it on

The cluster level switch is a static broker property, so it needs a rolling restart. Something like this in server.properties. You will need to also update the KRaft controller.

# Enable the feature cluster wide
remote.log.storage.system.enable=true

# The plugin that talks to object storage
remote.log.storage.manager.class.name=io.aiven.kafka.tieredstorage.RemoteStorageManager
remote.log.storage.manager.class.path=/opt/kafka/plugins/tiered-storage/*

# Metadata, using the built in topic based implementation
remote.log.metadata.manager.class.name=org.apache.kafka.server.log.remote.metadata.storage.TopicBasedRemoteLogMetadataManager
remote.log.metadata.manager.listener.name=PLAINTEXT

# Plugin specific settings, prefixed and passed straight through
rsm.config.storage.backend.class=io.aiven.kafka.tieredstorage.storage.s3.S3Storage
rsm.config.storage.s3.bucket.name=acme-kafka-tiered
rsm.config.storage.s3.region=eu-west-2
rsm.config.chunk.size=4194304

# Throughput guards so archiving cannot starve the hot path
remote.log.manager.copy.max.bytes.per.second=104857600
remote.log.manager.fetch.max.bytes.per.second=104857600

The remote.log.metadata.manager.listener.name property is mandatory with the default metadata manager and is a common cause of brokers refusing to start. It has to name a listener that the brokers can use to talk to each other.

Those last two quota settings are worth setting from day one. Without them, the first topic you enable will happily saturate your network uplink backfilling months of segments into S3 while your producers wonder what happened.

Enabling it on a topic

Nothing is tiered until you say so per topic. For a new topic:

kafka-topics.sh --bootstrap-server broker1:9092 --create \
  --topic payments.events \
  --partitions 12 --replication-factor 3 \
  --config remote.storage.enable=true \
  --config local.retention.ms=21600000 \
  --config retention.ms=2592000000

That says: keep six hours of log history on local broker disks and maintain thirty days in total log retention. As soon as a segment closes (governed by segment.bytes or segment.ms), it is immediately copied to remote storage. The local copy stays on disk to handle hot reads until it hits six hours (local.retention.ms), at which point it is purged locally while remaining readable from remote storage. Finally, the segment is purged entirely at thirty days (retention.ms). The local disk footprint reduction is roughly 120 to 1 compared to keeping all thirty days on local disk.

For an existing topic the same properties go through kafka-configs.sh:

kafka-configs.sh --bootstrap-server broker1:9092 --alter \
  --entity-type topics --entity-name payments.events \
  --add-config 'remote.storage.enable=true,local.retention.bytes=53687091200'

If you leave local.retention.* unset, it defaults to -2, inheriting the value of retention.*. In this configuration, segments are copied to remote storage and remain on local disk for their entire lifetime. Data is duplicated across both tiers, defeating the primary goal of offloading data to save local disk space. To reclaim local storage, explicitly set local.retention.* to a smaller value than retention.*.

Turning it off is more awkward than turning it on, so plan it. You can set remote.log.copy.disable=true to freeze the remote log as read only, or remote.log.delete.on.disable=true to purge it. Disabling the feature cluster wide requires deleting every topic that uses it first.

What it costs you

Be honest about the trade. A consumer replaying from three weeks ago is now doing ranged GETs against object storage rather than reading sequential blocks out of page cache. Expect first byte latency in the tens or hundreds of milliseconds instead of microseconds, and expect a per request bill. Kafka also serves only one partition per fetch request when the data comes from the remote tier, so a heavily parallel replay is slower than the equivalent local read.

The other limits are worth knowing before you design around them. Compacted topics are not supported, which rules out most changelog and state store topics. Tiered storage administration needs clients on 3.0 or later. Topics created before 2.8.0 lack producer snapshots and cannot be tiered.

None of that changes the conclusion for high volume event topics with long retention, which is the case we keep meeting. For those we would turn it on, size local retention to comfortably cover your consumers' normal lag plus your worst case replay window, and let the cold data go to S3.

Where to start

Pick your noisiest topic, the one driving your disk sizing. Work out how far back your consumers realistically read on a bad day, double it, and make that your local.retention.ms. Enable it on a staging cluster first and watch RemoteCopyBytesPerSec and the copy lag metrics before you touch production, because a badly tuned rollout will show up as producer latency long before it shows up on the storage bill.

Frequently asked questions

What is Kafka tiered storage?

Tiered storage splits a Kafka log into a local tier on broker disks and a remote tier in object storage such as S3, GCS or Azure Blob. Brokers still write to the local log directory as before; once a segment is closed the RemoteLogManager copies it, with its offset and time indexes, to the remote tier. Retention therefore stops being a function of how much block storage you provisioned. It is production ready as of Kafka 3.9.

How do I enable tiered storage on a Kafka topic?

Enable the feature cluster wide with the static broker property remote.log.storage.system.enable=true (a rolling restart, including the KRaft controller), then enable it per topic with remote.storage.enable=true alongside local.retention.ms and retention.ms. Existing topics can be altered with kafka-configs.sh. Nothing is tiered until you enable it on the topic.

What is the difference between local.retention.ms and retention.ms?

local.retention.ms and local.retention.bytes decide when segments are purged from local broker disk. Standard retention.ms and retention.bytes govern total log retention across the local and remote tiers combined. If local.retention.* is left unset it defaults to -2 and inherits retention.*, which keeps every segment on local disk for its full lifetime and duplicates the data across both tiers, defeating the point. Set it explicitly to a smaller value than retention.*.

Does Apache Kafka include a production RemoteStorageManager?

No. Apache Kafka defines the RemoteStorageManager interface but ships no production implementation, only a LocalTieredStorage class in the test sources, which is suitable for a laptop demo and not for production. Most clusters use the Apache 2.0 licensed Aiven tiered-storage-for-apache-kafka plugin, which supports S3, GCS and Azure Blob. The companion RemoteLogMetadataManager does have a usable default, TopicBasedRemoteLogMetadataManager, which stores metadata in an internal __remote_log_metadata topic.

Are compacted topics supported by Kafka tiered storage?

No. Compacted topics are not supported, which rules out most changelog and state store topics. Tiered storage administration also requires clients on 3.0 or later, and topics created before 2.8.0 lack producer snapshots and cannot be tiered.

How much can Kafka tiered storage save on storage cost?

For a 40MB/s cluster with thirty day retention and replication factor 3, keeping everything on local disk is roughly 311 TB of EBS gp3 at about $28,860 a month. With a six hour local retention window that becomes about 2.6 TB local (provisioned at 4 TB for headroom) plus 104 TB in S3 Standard, roughly $2,870 a month, a ninety per cent cut on the storage line. It understates the saving, because you also stop sizing instances around disk attachment limits and IOPS.

Running Kafka in production?

We run Kafka in production for customers on Strimzi, on EC2 and on bare metal, and we monitor it with AxonOps, which is where we tend to notice tiering problems first, because remote copy lag and local disk usage sit on the same dashboard.

If you want a hand working out whether tiered storage would pay for itself on your clusters, or you would rather someone else ran the whole thing, give us a shout.

Related reading

Subscribe to newsletter

Subscribe to receive the latest blog posts to your inbox every week.

By subscribing you agree to with our Privacy Policy.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Ready to Transform 

Your Business?