Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Cassandra Time-Window Compaction Strategy (TWCS)

Cassandra 5.0+

Starting with Cassandra 5.0, Unified Compaction Strategy (UCS) is the recommended compaction strategy for most workloads, including time-series patterns. UCS can handle time-series data efficiently with appropriate configuration. TWCS remains fully supported for existing deployments.

Optimized for Append-Only Workloads

TWCS is designed for append-only time-series data and performs best when data is written once and not updated. When newer writes for previously written data land in SSTables assigned to newer windows, TWCS background compaction does not merge those SSTables back into older windows. This means both versions persist, causing read amplification and preventing efficient space reclamation. TWCS can tolerate occasional updates, but frequent updates significantly degrade performance and space efficiency.

Avoid Explicit DELETE Statements

TWCS is optimized around time-windowed SSTables and expiration handling. Tombstones from DELETE operations are written into SSTables that may be assigned to newer windows, while the data they mark for deletion exists in older windows. Since TWCS background compaction selects candidates within a single window bucket, tombstones and their target data are unlikely to meet during normal background compaction, preventing proper space reclamation. Use TTL-based expiration instead.

TWCS is designed for time-series data. It groups SSTables into time-window buckets using each SSTable's maximum timestamp. In normal background compaction, candidate selection is bucket-based, so compactions are chosen from a single window bucket rather than spanning multiple window buckets. This can enable efficient space reclamation when TTL-aligned SSTables become fully expired and are safe to drop.


Time-Window Compaction Strategy was introduced in Cassandra 3.0.8/3.8 (2016) to address the inefficiency of STCS and LCS for time-series workloads with TTL. It evolved from DateTieredCompactionStrategy (DTCS), which was introduced in Cassandra 2.0.11/2.1.1 but proved problematic in production due to complexity and edge cases.

TWCS simplified the time-based approach: rather than complex tiering by age, it uses fixed-size time windows. This design made behavior predictable and eliminated many DTCS edge cases.

Time-series data has unique characteristics that STCS and LCS handle poorly:

  1. Append-only writes: Data is written once and never updated
  2. Time-ordered access: Queries typically request recent data or specific time ranges
  3. Uniform expiration: Data often expires after a fixed retention period (TTL)
  4. High volume: Continuous streams of measurements, events, or logs

With STCS, expired data requires compaction to reclaim space—expensive for large datasets. With LCS, the leveled structure provides no benefit since time-series queries don't need key-range organization.

TWCS addresses these issues by:

  • Grouping data into time-based windows
  • Background compaction selecting within a single window bucket
  • Enabling fully expired SSTables to be dropped when the compaction controller determines they are safe to reclaim
AspectSTCSLCSTWCS
Space reclamationRequires compactionRequires compactionDrop fully expired SSTable
TTL efficiencyPoor (scattered data)Poor (spread across levels)Excellent (window-aligned)
Time-range queriesNo optimizationNo optimizationNatural data locality
Write amplificationLowHighLow

TWCS organizes compaction around time windows:

  1. Window assignment: Each SSTable is assigned to a window based on its maximum timestamp
  2. Intra-window compaction: TWCS applies STCS-style prioritization to the newest window and allows compaction of older windows when sufficient SSTables are present (see Intra-Window Compaction for details).
  3. Background compaction is bucket-based: Normal background compaction selects candidates from a single window bucket rather than spanning multiple windows. Note that user-triggered compaction (e.g., nodetool compact) and maximal compaction may operate across windows.
  4. Window expiration: TWCS periodically checks for fully expired SSTables and can reclaim them directly when the compaction controller determines they are safe to drop.
Time-Window Compaction Strategy (TWCS)Time-Window Compaction Strategy (TWCS)Window W4: 10:00-11:00NEWEST«active»Window W3: 09:00-10:00TTL: 3h leftWindow W2: 08:00-09:00TTL: 2h leftWindow W1: 07:00-08:00TTL: 1h leftWindow W0: 06:00-07:00FULLY EXPIRED«expired»SST1SST2SST3SSTable(compacted)SSTable(compacted)SSTable(compacted)SSTable(compacted)STCS compactionwithin window onlyDrop fully expired SSTableNo compaction neededDrop fully expired SSTable directly (no rewrite)TWCS PropertiesData grouped by time windowsSTCS compaction within each windowBackground compaction within windows onlyDrop fully expired SSTables directly

Each SSTable is assigned to exactly one window based on its maximum timestamp:

SSTable metadata:
Minimum timestamp: 2024-01-15 10:23:45
Maximum timestamp: 2024-01-15 10:58:12
Window configuration:
compaction_window_unit: HOURS
compaction_window_size: 1
Calculation:
Window start = floor(max_timestamp / (window_size × unit)) × (window_size × unit)
Window start = floor(10:58:12 / (1 × 1 hour)) × (1 × 1 hour)
Window start = 10:00:00
Result: SSTable assigned to window [10:00:00 - 11:00:00)

Using maximum timestamp ensures that all data in the SSTable falls within or before the assigned window.

TWCS applies STCS-style bucketing and prioritization to the newest window it has observed. Older windows are also eligible for compaction when they contain at least two SSTables:

  1. Newest window: SSTables are grouped by size using STCS bucketing. Compaction triggers when min_threshold similar-sized SSTables exist.
  2. Older windows: Any window with at least 2 SSTables is eligible for compaction, trimmed to max_threshold.
  3. In practice, completed windows often converge toward a small number of SSTables, but convergence to exactly one SSTable is not guaranteed.

The key advantage of TWCS is that fully expired SSTables can be dropped directly, avoiding the need to rewrite old and new data together to reclaim expired data:

Without TWCS (STCS/LCS)With TWCSSSTable AHour1 + Hour2SSTable BHour1 + Hour3SSTable CHour1 + Hour4Hour 1 TTL expires:Must compact ALL SSTablesto remove Hour 1 dataSpace reclamation:requires rewriting via compactionHour 1SSTableHour 2SSTableHour 3SSTableHour 4SSTableHour 1 TTL expires:Drop fully expired Hour 1 SSTableNo compaction neededSpace reclamation:direct SSTable drop (no rewrite)

TWCS's primary advantage is space reclamation without compaction:

  • Fully expired SSTables can be dropped directly rather than rewritten through compaction
  • More predictable disk space recovery when TTL and windowing align well

Similar to STCS, TWCS has low write amplification:

  • Data is written once to initial SSTable
  • Compacted only within its window (typically once)
  • TWCS generally has lower write amplification than LCS for append-oriented time-series workloads

Queries for time ranges benefit from data organization:

  • Recent data in newer windows
  • Historical queries touch specific windows
  • Reduced SSTable overlap for time-range scans

Fixed window sizes make operations predictable:

  • Window boundaries are deterministic
  • Better estimate when space may be reclaimed
  • Plan capacity based on retention period

By avoiding cross-window compaction:

  • Less total data movement
  • Compaction confined to individual window buckets
  • Older windows are typically no longer selected as the newest compaction bucket

TWCS performs optimally with append-only data:

  • Updates to old data can create SSTables assigned to newer windows
  • Background compaction does not merge SSTables from different windows, so both versions persist
  • Both versions persist until TTL expires
  • Occasional updates are tolerable but degrade read performance and space efficiency proportionally to update frequency

Late-arriving data causes problems:

Newest observed window: Hour 10
Late write arrives for Hour 5
Result:
- New SSTable assigned to Hour 5 window bucket
- Hour 5 bucket now has multiple SSTables
- These may not compact together (different sizes)
- Some SSTables may not be eligible for direct expiry-based reclamation yet

Explicit deletes (DELETE statements) are problematic:

  • Tombstone written to a newer window
  • Original data in old window
  • Tombstone and data are unlikely to meet during normal background compaction
  • Must wait for both to expire via TTL

Window size significantly impacts behavior:

  • Too small: Many windows, many SSTables, overhead
  • Too large: Less efficient space reclamation timing
  • Must match data patterns and TTL

Some time-series patterns don't fit:

  • Data without TTL (windows accumulate forever)
  • Frequently corrected/updated data
  • Heavy delete workloads

Workload PatternWhy TWCS Works
IoT sensor dataAppend-only, TTL-based retention
Application metricsTime-ordered, fixed retention
Log aggregationImmutable events, time-based queries
Financial tick dataSequential writes, regulatory retention
Monitoring dataHigh volume, predictable expiration
Workload PatternWhy TWCS Is Wrong
Mutable dataUpdates span windows
No TTL definedWindows accumulate indefinitely
Heavy delete workloadTombstones are unlikely to meet older data during normal background compaction
Significant out-of-order writesWindows may retain multiple SSTables
Non-time-based access patternsNo benefit from time organization

TWCS Background Compaction ModelTWCS Background Compaction ModelMemtable flushes to SSTableAssign SSTable to time windowbased on maximum timestampNewest window?yesnoAdd SSTable to newest windowmin_threshold SSTables?yesnoRun STCS compactionwithin window onlyWait for more SSTablesOlder windowSSTable fully expired?yesnoDrop fully expired SSTableDrop fully expired SSTable directly(no rewrite required)SSTable remains untilsafe to drop

Each SSTable is assigned to a time window based on its maximum timestamp:

SSTable with data timestamps:
- Min timestamp: 2024-01-15 10:30:00
- Max timestamp: 2024-01-15 10:45:00
With 1-hour windows:
- Window: 2024-01-15 10:00:00 - 11:00:00
- SSTable assigned to this window

CREATE TABLE sensor_readings (
sensor_id text,
reading_time timestamp,
value double,
PRIMARY KEY ((sensor_id), reading_time)
) WITH CLUSTERING ORDER BY (reading_time DESC)
AND compaction = {
'class': 'TimeWindowCompactionStrategy',
-- Time window size
'compaction_window_unit': 'HOURS', -- MINUTES, HOURS, DAYS
'compaction_window_size': 1, -- 1 hour windows
-- Expired SSTable handling
'unsafe_aggressive_sstable_expiration': false
}
AND default_time_to_live = 86400 -- 24 hour TTL
AND gc_grace_seconds = 3600; -- 1 hour (shorter for time-series)
ParameterDefaultDescription
compaction_window_unitDAYSTime unit for windows. Valid values: MINUTES, HOURS, DAYS.
compaction_window_size1Number of units per window. Must be ≥ 1.
timestamp_resolutionMICROSECONDSResolution of timestamps in data. Valid values: SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS. A warning is logged if non-default values are used.
expired_sstable_check_frequency_seconds600How often (in seconds) to check for fully expired SSTables that can be dropped. Cannot be negative.
unsafe_aggressive_sstable_expirationfalseWhen true, drops SSTables without checking for tombstones affecting other SSTables. Requires JVM flag -DALLOW_UNSAFE_AGGRESSIVE_SSTABLE_EXPIRATION=true.

TWCS reuses STCS bucketing and sizing options for compaction candidate selection, especially in the newest window, so these options also apply:

ParameterDefaultDescription
min_threshold4Minimum SSTables in a window to trigger intra-window compaction.
max_threshold32Maximum SSTables to compact at once within a window.
bucket_high1.5Upper bound multiplier for STCS bucketing within windows.
bucket_low0.5Lower bound multiplier for STCS bucketing within windows.
ParameterDefaultDescription
enabledtrueEnables background compaction.
tombstone_threshold0.2Ratio of droppable tombstones that triggers single-SSTable compaction.
tombstone_compaction_interval86400Minimum seconds between tombstone compaction attempts.
unchecked_tombstone_compactionfalseBypasses tombstone compaction eligibility pre-checking.
only_purge_repaired_tombstonesfalseOnly purge tombstones from repaired SSTables.
log_allfalseEnables detailed compaction logging.

Tombstone Compactions Disabled by Default

TWCS disables tombstone compactions unless one of the tombstone-related options (tombstone_threshold, tombstone_compaction_interval, or unchecked_tombstone_compaction) is explicitly present with a value other than "false". This behavior is set in the strategy constructor.

TTLRecommended WindowResult
1 hour5-10 minutes~6-12 windows
24 hours1 hour24 windows
7 days1 day7 windows
30 days1 day30 windows
90 days1 week~13 windows

Rule of thumb: Choose window size so that 10-30 windows exist before data expires.


The primary benefit of TWCS is efficient TTL expiration:

TWCS + TTL EfficiencyTWCS + TTL EfficiencyDay 1: All windows presentDay 8: Window 1 becomes fully expiredWindow 1TTL: 7 daysWindow 2TTL: 7 daysWindow 3TTL: 7 daysWindow 1EXPIREDWindow 2TTL: 2 daysWindow 3TTL: 3 daysSSTable deletedNo compactionDirect dropSpace ReclamationTWCS: direct SSTable dropSTCS/LCS: requires rewriting via compactionDROP

For time-series with TWCS, gc_grace_seconds can often be reduced:

-- Traditional table: 10 days (default)
gc_grace_seconds = 864000
-- Time-series with frequent repair: 1 hour
gc_grace_seconds = 3600
-- Time-series with very frequent repair: 10 minutes
gc_grace_seconds = 600

Warning: Reducing gc_grace_seconds requires running repair at least that frequently to prevent zombie data resurrection.


Symptoms:

  • Old windows have multiple SSTables that do not compact together
  • Space not reclaimed when TTL expires
  • SSTable count growing unexpectedly

Diagnosis:

Terminal window
# List SSTables with timestamps
for f in /var/lib/cassandra/data/keyspace/table-*/*-Data.db; do
echo "=== $f ==="
tools/bin/sstablemetadata "$f" | grep -E "Minimum|Maximum"
done

Cause:

Out-of-Order Write ProblemOut-of-Order Write ProblemHour 5 Window (older)Hour 10 Window (newest)SSTable(compacted)NEW SSTable(late write)SSTable 1SSTable 2Newest Observed Window: Hour 10Problem:Late write creates a new SSTablein an older window bucketMultiple SSTables, different sizesMay retain multiple SSTables

Solutions:

  1. Ensure data arrives in order (fix data pipeline)
  2. Use larger windows to accommodate expected delays:
    -- If data can arrive up to 2 hours late, use 4-hour windows
    'compaction_window_size': 4
  3. Accept some space inefficiency for late-arriving data

Symptoms:

  • Reads merging data across many windows
  • Higher read latency than expected
  • Multiple versions of same partition key

Cause:

TWCS assumes append-only. Updates violate this assumption:

Cross-Window Update ProblemCross-Window Update ProblemWindow 1 (old)Window 5 (new)sensor1: v1(original)sensor1: v2(update)Read sensor1Problem:v1 and v2 in different windowsBackground compaction does not merge across windowsBoth versions persist until TTLReads must merge across windowscheckcheck

Solution:

TWCS is only appropriate for append-only time-series. If updates are required, consider:

  1. LCS for frequently updated data
  2. Redesign data model to avoid updates

Symptoms:

  • Space not reclaimed after deletes
  • Tombstones persisting beyond gc_grace_seconds

Cause:

Tombstone Spread ProblemTombstone Spread ProblemOld Windows (W1-W3)Newer Window (W10)Original datafor sensor 'x'Range tombstonefor sensor 'x'DELETE FROM sensorsWHERE sensor_id = 'x'AND reading_time < '2024-01-01'Problem:Tombstone in newer windowData in old windowsUnlikely to meet in background compactionSpace not reclaimedwrites to

Solution:

Avoid explicit deletes with TWCS. Use TTL instead:

-- Instead of DELETE, let TTL handle expiration
INSERT INTO sensors (sensor_id, reading_time, value)
VALUES ('x', '2024-01-15 10:30:00', 42.5)
USING TTL 604800; -- 7 days

Symptoms:

  • Multiple SSTables in completed (old) windows
  • Expected single SSTable per window not achieved

Diagnosis:

Terminal window
# Check SSTables per window
nodetool tablestats keyspace.table

Causes:

  1. Insufficient similar-sized SSTables (STCS within window)
  2. Out-of-order writes
  3. Compaction not keeping pace

Solutions:

-- Lower threshold for intra-window compaction
ALTER TABLE keyspace.table WITH compaction = {
'class': 'TimeWindowCompactionStrategy',
'compaction_window_unit': 'HOURS',
'compaction_window_size': 1,
'min_threshold': 2 -- Compact with fewer SSTables
};

When data has uniform TTL and no deletes, aggressive expiration can drop SSTables without full compaction:

ALTER TABLE keyspace.table WITH compaction = {
'class': 'TimeWindowCompactionStrategy',
'compaction_window_unit': 'HOURS',
'compaction_window_size': 1,
'unsafe_aggressive_sstable_expiration': true
};

Warning: "unsafe" means:

  • Does not check for tombstones affecting other SSTables
  • Only safe when:
    • All data has the same TTL
    • No explicit deletes
    • No range tombstones
FactorConsiderationRecommendation
Query patternsQueries typically span 1 hourWindows ≤ 1 hour
Queries span 1 dayWindows ≤ 1 day
Write rateHigh write rateSmaller windows (more SSTables, manageable size)
Low write rateLarger windows (fewer SSTables)
TTL durationShort TTL (hours)Minute/hour windows
Long TTL (weeks)Day windows
Late-arriving dataData arrives up to X lateWindow size > X

MetricHealthyInvestigate
SSTables per window1-2 (completed)>4
Total SSTable count~windows × 2Much higher
Pending compactionsLowSustained growth
Space after SSTables become fully expiredDecreasingNot changing
Terminal window
# Check SSTable timestamps
for f in /var/lib/cassandra/data/keyspace/table-*/*-Data.db; do
tools/bin/sstablemetadata "$f" | grep -E "Minimum|Maximum timestamp"
done
# Monitor space usage over time
watch 'nodetool tablestats keyspace.table | grep "Space used"'

This section documents implementation details from the Cassandra source code.

Conceptually, window boundaries are calculated by flooring the timestamp to the configured window size. In the current implementation, getWindowBoundsInMillis() first normalizes the timestamp to seconds, computes lower and upper bounds in seconds for the configured unit (MINUTES, HOURS, or DAYS), and then converts the result back to milliseconds.

The conceptual formula:

L=t(tmod(u×w))U=L+(u×w)1\begin{aligned} L &= t - (t \mod (u \times w)) \\ U &= L + (u \times w) - 1 \end{aligned}

Where:

  • LL = lower bound (window start)
  • UU = upper bound (window end)
  • tt = timestamp
  • ww = compaction_window_size
  • uu = unit size (e.g., 60 seconds for MINUTES, 3600 seconds for HOURS, 86400 seconds for DAYS)

Each SSTable is assigned to exactly one window based on its maximum timestamp:

  1. Extract the maximum timestamp from SSTable metadata
  2. Calculate window bounds using the formula above
  3. The SSTable belongs to the window containing its max timestamp

Using max timestamp (rather than min) ensures all data in the SSTable falls within or before the assigned window.

The candidate selection algorithm processes windows in order:

  1. Expired SSTables: Identified first during candidate selection and included whenever found (checked every expired_sstable_check_frequency_seconds)
  2. Newest window (determined by highestWindowSeen, not wall clock time): Uses STCS-style bucketing and prioritization when at least min_threshold SSTables are present
  3. Older windows: Any window with ≥2 SSTables is eligible for compaction
  4. Result limiting: Candidates trimmed to max_threshold
TimeWindowCompactionStrategysstablesByWindow: HashMultimap<Long, SSTableReader>highestWindowSeen: longstcsOptions: SizeTieredCompactionStrategyOptionssstableCountByBuckets: MapField DescriptionssstablesByWindow: Window timestamp to SSTables mappinghighestWindowSeen: Tracks newest window for detectionstcsOptions: Configuration for intra-window compactionsstableCountByBuckets: Metrics for monitoring
ConstantValueDescription
Default window unitDAYSTime unit for windows
Default window size1One unit per window
Default timestamp resolutionMICROSECONDSExpected timestamp precision
Expired check frequency600 secondsHow often to check for fully expired SSTables
Default min_threshold4Minimum SSTables for intra-window compaction