Skip to content

Conversation

corylanou
Copy link
Collaborator

@corylanou corylanou commented Oct 1, 2025

Problem

Fixes #771

In v0.5.0, LTX files became unavailable for timestamp-based restoration after compaction. When level 0 files were compacted into level 1, the compacted file was assigned the current time as its CreatedAt timestamp rather than preserving the earliest timestamp from the source files.

This caused restoration to fail for any timestamp that fell between:

  • The original L0 file creation time (earlier)
  • The compaction time (later)

After compaction deleted the L0 files, these timestamps became "unavailable" because the L1 file's CreatedAt was too late to match the requested restoration point.

Solution

Per author's suggestion, this PR implements a cleaner approach: reading timestamps from LTX file headers rather than file system or object metadata.

LTX files contain an authoritative Timestamp field in their headers (milliseconds since epoch). The issue was that LTXFiles() implementations were reading timestamps from:

  • File modification times (file, sftp backends)
  • Object metadata LastModified/CreationTime (S3, GCS, Azure backends)
  • Or not setting timestamps at all (NATS backend)

This fix modifies LTXFiles() in all storage backends to use ltx.PeekHeader() to read the authoritative timestamp from each LTX file's header.

Benefits of this approach:

  • No interface changes required
  • More architecturally correct (LTX headers are source of truth)
  • Works across all storage backends consistently
  • Timestamps are preserved through compaction automatically

Changes

  • file: Read header timestamp using ltx.PeekHeader(), add time import
  • sftp: Read header timestamp via SFTP client
  • s3: Read header timestamp in file iterator
  • gs: Read header timestamp in file iterator, pass client to iterator
  • abs: Read header timestamp in file iterator
  • nats: Add CreatedAt field and read from header
  • test: Added TestCompaction_PreservesEarliestTimestamp verifying timestamp preservation

All implementations use a fallback to file/object metadata if header reading fails, ensuring robustness.

Testing

  • ✅ New test TestCompaction_PreservesEarliestTimestamp passes
  • ✅ All existing tests pass (full test suite with -short)
  • ✅ Pre-commit hooks pass (goimports, go-vet, staticcheck)
  • ✅ Clean build with no errors

Example

Before this fix:

L0 files: TXID 1-3, header.Timestamp: 10:00:00, file.ModTime: 10:00:00
Compaction at: 10:01:00
L1 file: TXID 1-3, header.Timestamp: 10:00:00, file.ModTime: 10:01:00
LTXFiles() reads: file.ModTime (10:01:00)  ← Problem!
Restore to 10:00:30 → FAILS (no files before 10:01:00)

After this fix:

L0 files: TXID 1-3, header.Timestamp: 10:00:00
Compaction at: 10:01:00
L1 file: TXID 1-3, header.Timestamp: 10:00:00
LTXFiles() reads: header.Timestamp (10:00:00)  ← Fixed!
Restore to 10:00:30 → SUCCESS

Performance Consideration

Reading headers adds one network/disk request per file when listing files. This is necessary for correctness and only occurs during list operations, not during normal replication.

@corylanou
Copy link
Collaborator Author

Verification Testing Complete ✅

I've tested the exact scenario from the issue description and confirmed both the bug and the fix.

Test Setup

  • Insert 10 rows over 20 seconds
  • Wait 35 seconds for L1 compaction (30s interval)
  • L0 files (TXID 1-10) get deleted and compacted into L1
  • Attempt to restore to timestamp from early in the sequence

Main Branch (v0.5.0) - Bug Confirmed ✗

Error:

time=2025-10-01T13:19:27.876-05:00 level=ERROR msg="failed to run" 
error="cannot calc restore plan: transaction not available"

Root Cause: L1 file has CreatedAt: 18:18:50Z (compaction time), but we're trying to restore to 18:18:34Z (before compaction). The L1 file appears "too new" so restoration fails.

Fix Branch - Verified Working ✓

Result: ✓ RESTORE SUCCESS - found 10 rows

How it works: The fix preserves the earliest CreatedAt timestamp from all source L0 files when creating the L1 compacted file. This maintains temporal granularity for point-in-time restoration.

Full test results saved in my local testing at /tmp/test-results-771.md.

@corylanou corylanou force-pushed the fix/771-ltx-deletion-regression branch 3 times, most recently from 4e3bc19 to 93cae3e Compare October 1, 2025 18:59
This implements the author's suggested approach for issue #771 by reading
timestamps from LTX file headers rather than file/object metadata.

The issue occurs when L0 files are compacted into L1. Previously, the L1
file would get the current time as its CreatedAt timestamp (from file
system or cloud storage metadata), losing the original timestamp from
the source L0 files. This broke point-in-time restoration for timestamps
between L0 creation and compaction.

The fix modifies LTXFiles() in all storage backends to use ltx.PeekHeader()
to read the authoritative Timestamp field from each LTX file's header,
rather than relying on file modification times or object metadata.

Changes:
- file: Read header timestamp, add time import
- sftp: Read header timestamp via SFTP client
- s3: Read header timestamp in file iterator
- gs: Read header timestamp in file iterator, pass client to iterator
- abs: Read header timestamp in file iterator
- nats: Add CreatedAt field and read from header
- Add test verifying timestamp preservation after compaction

This approach requires no interface changes and is more architecturally
correct since LTX headers are the authoritative source of truth for
file timestamps.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@corylanou corylanou force-pushed the fix/771-ltx-deletion-regression branch from 93cae3e to 2de4348 Compare October 1, 2025 19:31
@corylanou
Copy link
Collaborator Author

Closing in favor of #778

@corylanou corylanou closed this Oct 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Regression] LTX transactions get deleted in 0.5.0, cannot restore more than a few seconds

1 participant