-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add Table::checkpoint()
API
#797
base: main
Are you sure you want to change the base?
feat: add Table::checkpoint()
API
#797
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
few comments to start
kernel/src/checkpoint/mod.rs
Outdated
&mut self, | ||
engine: &dyn Engine, | ||
) -> DeltaResult<SingleFileCheckpointData> { | ||
if self.data_consumed { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can use types to manage this 'state transition' if needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Taking a step back, do we want to limit the engine from calling .checkpoint_data()
multiple times to retrieve multiple copies of the checkpoint data & path in the first place? If they really wanted to, they could always just call table.checkpoint()
again so I don't see a strong reason to?
kernel/src/checkpoint/log_replay.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is stacked on #744, do not review this file (added in stacked pr)
I can separate out the |
//! | ||
//! ## Example: Writing a classic-named V1/V2 checkpoint (depending on `v2Checkpoints` feature support) | ||
//! | ||
//! TODO(seb): unignore example |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing that I like to do is to use FIXME for stuff I want to fix this PR, and TODO for stuff that I wanna fix in a subsequent issue/PR.
//! - Single-file UUID-named V2 checkpoints (using `n.checkpoint.u.{json/parquet}` naming) are to be | ||
//! implemented in the future. The current implementation only supports classic-named V2 checkpoints. | ||
//! - Multi-file V2 checkpoints are not supported yet. The API is designed to be extensible for future | ||
//! multi-file support, but the current implementation only supports single-file checkpoints. | ||
//! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see github issues and TODOs associated with these so that they're tracked. Something like:
- TODO(433): Support Single-file
@zachschuermann This is a pattern I've seen in other repositories, and I'd advocate for this to be the way we track todos.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice, great idea! let's try to use that issue numbering i agree that's nice
regarding issues/etc. how about we update the original 'checkpoint support' issue with some clearly documented bits on what was done and what is future work (and can always make the future work into sub-issues, etc.)
i think original issues were #736 and #499 - how about we consolidate (feel free to just close one or both with comments on whatever we select or new one we make
//! let checkpoint_data = writer.checkpoint_data()?; | ||
//! | ||
//! // Write checkpoint data to storage (implementation-specific) | ||
//! let metadata = your_storage_implementation.write_checkpoint( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not clear to me what your_storage_implementation
is. For code docs like this, I'd like for them to be something that actually compiles
} | ||
|
||
// The current checkpoint API only supports single-file checkpoints. | ||
let parts: i64 = 1; // Coerce the type to `i64`` to match the expected schema. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let parts: i64 = 1; // Coerce the type to `i64`` to match the expected schema. | |
let parts = 1i64; |
/// - `sizeInBytes` (i64, optional): Size of checkpoint file in bytes | ||
/// - `numOfAddFiles` (i64, optional): Number of Add actions | ||
/// | ||
/// Note: The fields `checkpointSchema` and `checksum` are not yet included in this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you link the todos here?
TODO(xxx): Add checkpointSchema field to _last_checkpoint
TODO(xxx): Add checksum field to _last_checkpoint
let last_checkpoint_schema = Arc::new(StructType::new([ | ||
StructField::not_null("version", DataType::LONG), | ||
StructField::not_null("size", DataType::LONG), | ||
StructField::nullable("parts", DataType::LONG), | ||
StructField::nullable("sizeInBytes", DataType::LONG), | ||
StructField::nullable("numOfAddFiles", DataType::LONG), | ||
])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declare this somewhere with a static lazy lock, then clone the arc.
self.total_actions_counter.load(Ordering::Relaxed), | ||
self.add_actions_counter.load(Ordering::Relaxed), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be wary of using Ordering::Relaxed without being absolutely certain things won't go wrong.
Check out the rustinomicon.
I'd advise to keep all the counter accesses/updates as SeqCst
. We're not doing a lot of load/stores. Especially when you compare to the huge amount of data we'd be processing, so it's not a big performance hit.
let schema = Arc::new(StructType::new([StructField::not_null( | ||
CHECKPOINT_METADATA_NAME, | ||
DataType::struct_type([StructField::not_null("version", DataType::LONG)]), | ||
)])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is another opportunity for static LazyLock + clone.
let now_ms: i64 = now_duration | ||
.as_millis() | ||
.try_into() | ||
.map_err(|_| Error::generic("Current timestamp exceeds i64 millisecond range"))?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please create an error type for the checkpoint writer. The use of error::generic in our code makes it harder to test these error cases, and it makes debugging really challenging.
Suppose you run a kernel connector, and just gets back "Current timestamp exceeds i64 millisecond range". Where do they even begin to look for the bug?
What changes are proposed in this pull request?
Please ignore the
checkpoint/log_replay
mod (change in stacked PR #774)This PR implements the checkpoint API, enabling table checkpoints that provide compact summaries of table state for faster recovery without full log replay.
Key Features
Adds a
Table::checkpoint()
method that automatically selects the appropriate checkpoint type based on table feature support:v2Checkpoints
feature → Creates a Single-file Classic-named V2 Checkpointv2Checkpoints
feature → Creates a Single-file Classic-named V1 CheckpointImplements the
CheckpointWriter
..checkpoint_data()
_last_checkpoint
file on call to.finalize()
..finalize()
, otherwise the table may be corrupted.API Overview
The checkpoint workflow for the engine consists of:
CheckpointWriter
viaTable::checkpoint(engine, version)
CheckpointWriter::get_data()
CheckpointWriter.finalize()
This PR is stacked on #774.
Please only review these commits.
How was this change tested?
Unit tests in
checkpoint/mod.rs
test_deleted_file_retention_timestamp
- tests file retention timestamp calculationstest_create_checkpoint_metadata_batch_when_v2_checkpoints_is_supported
test_create_checkpoint_metadata_batch_when_v2_checkpoints_not_supported
test_create_last_checkpoint_metadata
test_create_last_checkpoint_metadata_with_invalid_batch
'Integration tests' in
checkpoint/tests.rs
test_v1_checkpoint_latest_version_by_default
: table that does not supportv2Checkpoint
, no checkpoint version specifiedtest_v1_checkpoint_specific_version
: table that does not supportv2Checkpoint
, checkpointing at a specific versiontest_v2_checkpoint_supported_table
: table that supportsv2Checkpoint
& no version is specifiedtest_checkpoint_error_handling_invalid_version
: checkpoint with a version that does not exist in the log