Status
Current state: "Under Discussion"
Discussion thread: [DISCUSS] KIP-354 Time-based log compaction policy
Vote thread: [VOTE] KIP-354 Time-based log compaction policy
JIRA: KAFKA-7321
Motivation
Compaction enables Kafka to remove old messages that are flagged for deletion while other messages can be retained for a relatively longer time. Today, a log segment may remain un-compacted for an unbound time since the eligibility for log compaction is determined based on dirty ratio (“min.cleanable.dirty.ratio”) and min compaction lag ("min.compaction.lag.ms") setting. Ability to delete a record through compaction in a timely manner has become an important requirement in some use cases (e.g., GDPR). For example, one use case is to delete PII (Personal Identifiable information) data within certain days (e.g., 7 days) while keeping non-PII indefinitely in compacted format. The goal of this change is to provide a configurable maximum compaction lag that ensures a record is compacted after the specified time interval.
Example
A compacted topic with user id as key and PII in the value: 1 => {name: "John Doe", phone: "5555555"} 2 => {name: "Jane Doe", phone: "6666666"} # to remove the phone number we can replace the value with a new message 1 => {name: "John Doe"} # to completely delete key 1 we can send a tombstone record 1 => null # but until compaction runs (and some other conditions are met), reading the whole topic will get all three values for key 1, and the old values are still retained on disk.
This example mentions GDPR because it is widely known, but the requirement here is to provide some guarantees around a tombstone or a new value leading to deletion of old values within a maximum time.
Note: This Change focuses on when to compact a log segment, and it doesn’t conflict with KIP-280, which focuses on how to compact log.
Current Behavior
For log compaction enabled topic, Kafka today uses “min.cleanable.dirty.ratio” and "min.compaction.lag.ms" to determine what log segments it needs to pick up for compaction. "min.compaction.lag.ms" marks a log segment uncleanable until the segment is rolled and remains un-compacted for the specified "lag". The detailed information can be found in KIP-58. In addition, only log partitions whose dirty ratio is larger than “min.cleanable.dirty.ratio” are picked up by log cleaner for compaction. In summary, with these two existing compaction configurations, Kafka cannot enforce a maximum lag on compacting an un-compacted message record.
Proposed Changes
We propose adding a new topic level configuration: “max.compaction.lag.ms”, which controls the max lag after which a record is required to be picked up for compaction (note that this lag interval includes the time the record resides in an active segment). In other words, a message record has a guaranteed upper-bound in time to become mandatory for compaction despite min cleanable dirty ratio. The clock starts when the message record is appended to an active segment.
Here are a list of changes to enforce such a max compaction lag:
1. Force a roll of non-empty active segment if the first record is older than "max.compaction.lag.ms" so that compaction can be done on that segment. The time to roll an active segments is controlled by "segment.ms" today. However, to ensure messages currently in the active segment can be compacted in time, we need to roll the active segment when either "max.compaction.lag.ms" or "segment.ms" is reached.
We define:Estimate the earliest message timestamp of an un-compacted log segment. we only need to estimate earliest message timestamp for un-compacted log segments to ensure timely compaction because the deletion requests that belong to compacted segments have already been processed.
for the first (earliest) log segment: The estimated earliest timestamp is set to the timestamp of the first message if timestamp is present in the message. Otherwise, the estimated earliest timestamp is set to "segment.largestTimestamp - maxSegmentMs” (segment.largestTimestamp is lastModified time of the log segment or max timestamp we see for the log segment.). In the later case, the actual timestamp of the first message might be later than the estimation, but it is safe to pick up the log for compaction earlier.
from the second log segment onwards: there are two methods to estimate the earliest message timestamp of a log segment. First method is to use the largestTimestamp (lastmodified time) of previous segment as an estimation. Second method is to use the timestamp of the first message if timestamp is present in the message. Since getting the timestamp of a message requires additional IOs, the first method of estimation is sufficient in practice.
- Let log cleaner pick up logs that have reached max compaction lag for compaction.
For any given log partition with compaction enabled, as long as the estimated earliest message timestamp of first un-compacted segment is earlier than "max.compaction.lag.ms", the log is picked up for compaction. Otherwise, Kafka uses "min.cleanable.dirty.ratio" and "min.compaction.lag.ms" to determine the log's eligibility for compaction as it does today. - Add two Metrics (as described in the next section)
Public Interfaces
Adding topic level configuration "max.compaction.lag.ms", and corresponding broker configuration "log.cleaner.max.compaction.lag.ms", which is set to MAX_LONG by default. If both "max.compaction.lag.ms" and "min.compaction.lag.ms" are provided in topic creation, Kafka enforces "max.compaction.lag.ms" is no less than "min.compaction.lag.ms". This configuration only applies to topics that have compaction enabled.
Add two Metrics:
1) kafka.log:type=LogCleaner,name=num-logs-compacted-by-max-compaction-lag
type: gauge
value: the number of logs which needs to be immediately compacted as determined by max.compaction.lag in the last cleaner run.2) kafka.log:type=LogCleaner,name=max-compaction-delay
type: gauge
value: the maximum value of "compaction_finish_time - earliest_timestamp_of_first_uncompacted_segment - max.compaction.lag.ms" in the last cleaner run.
This metric tells the delay between the time when a log is required to be picked up for compaction and the time when the compaction is done for the log.
Compatibility, Deprecation, and Migration Plan
- By default "max.compaction.lag.ms" is set to MAX_LONG and this max compaction lag rule will not lead to additional log compaction. There are no compatibility issues and no migration is required.
Performance impact
- Kafka already collects compaction metrics (CleanerStats) that include how many bytes that are read/written during each compaction run and how long does it take to compact a log partition. We can also determine the frequency of log compaction by looking at how frequent compaction metrics are recorded. Those metrics can be used to measure the performance impact when adapting this KIP. In addition, if a log partition already gets compacted once per day before this KIP, setting the log compaction time interval to more than one day should have little impact on the amount of resource spent on compaction since the existing log compaction configuration (e.g., min dirty ratio) will trigger log compaction before "max.compaction.lag.ms". The added metric "num-logs-compacted-by-max-compaction-lag" can be used to determine how many log partitions are actually determined by "max.compaction.lag.ms" to be compacted.
Rejected Alternatives
One way to force compaction on any cleanable log segment is setting “min.cleanable.dirty.ratio” to 0. However, compacting a log partition whenever a segment become cleanable (controlled by "min.compaction.lag.ms") is very expensive. We still want to accumulate some amount of log segments before compaction is kicked out. In addition, in order to honor the max compaction lag requirement, we also need to force a roll on active segment if the required lag has passed. So the existing configuration doesn't meet requirements to ensure a maximum compaction lag.