Conversation
A TsFile that was closed normally is not writable, and every C entry point that could make it writable truncates the file on open, so appending to existing data discards it. Add open_for_append(), which locates the metadata tail from the footer's own length, removes it, and rebuilds the schema from the chunks underneath, so writing continues and close() regenerates the tail. The tail is only removed once every chunk under it has been read back: a region the scan cannot consume is refused with the file byte-identical, because partial progress here would truncate data that is still valid. A file that never got a footer is recovered the way open() already recovers it. Aligned with apache#923.
Author
|
Heads-up on CI: all 9 workflow runs for this head are sitting at |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Adds
RestorableTsFileIOWriter::open_for_append(path), which makes a TsFile that was closed normally writable, as discussed in #923.The metadata tail is located from the footer's own length (
size - (magic + i32) - meta_size - 1) and removed, then the schema and chunk metadata are rebuilt from the chunks underneath, so writing continues where the file stopped andclose()regenerates the tail. Per review on the issue, the byte at the boundary is not inspected — Java'struncate(fileMetadataPos - 1)does not require a marker either, and the fixtures in this repo carryMetaMarker(0x03) where current writers putSEPARATOR_MARKER(0x02), so a marker check here would reject files this library is happy to append to.open()is unchanged: a complete file still reportscan_write() == false. A file that never got a footer is recovered exactly asopen(path, true)recovers it. Scope stays V4-only.Why a scan and not the footer
TsFileWriter::init(RestorableTsFileIOWriter*)already consumes what the chunk scan produces — real device ids, per-measurement encoding/compression, recovered statistics,aligned_devices_, table schemas viaupdate_table_schema()/finalize_table_schemas(). Reusing it means appending inherits the existing recovery tests instead of a second recovery path over index-node structures that would also have to re-derive per-chunk statistics from the footer.The one rule that is not in the crash path
The tail is only removed once every chunk under it has been read back:
Crash recovery truncating at the last parseable offset is correct for a half-written file, but here the file is known-good, so partial progress would truncate data that is still valid — append would destroy a file it should have refused. For the same reason a chunk whose body cannot be read is an error in this mode rather than a chunk recorded with empty statistics: those statistics are what the regenerated footer will claim the file holds.
Tests
cpp/test/file/restorable_tsfile_append_test.cc: append to a closed file (tree and table model) and read old + new rows back; new device and new measurement after recovery; older-than-recovered timestamp refused; no-op append+close compared against time-windowed queries taken before it, since the regenerated tail is rebuilt from scanned chunk metadata rather than copied; four append/close cycles, for theOPERATION_INDEX_RANGEsegment eachclose()writes ahead of its separator; bad metadata length and non-TsFile bytes both refused with the file unchanged; incomplete file salvaged then appended; empty and header-only files;open()still refusing a complete file; second open on a live handle refused.What I ran, and what I did not
Ran:
./mvnw spotless:check -P with-cpp(BUILD SUCCESS, with clang-format 17.0.6, the version this repo pins) andg++ -fsyntax-only -std=c++11 -Walloverrestorable_tsfile_io_writer.cc,tsfile_writer.cc,tsfile_tree_writer.cc,tsfile_table_writer.cc— clean, no new warnings (the one-Wsign-comparein the file is pre-existing at the same line upstream).Not run: the gtest suite itself. This machine has no CMake and no usable container/WSL toolchain, and the single MinGW available (6.3.0, win32 threads) cannot even parse
std::mutexintsfile_io_reader.h, so the test binary could not be configured, let alone executed. No behaviour in this PR was observed running. That is whatUnit-Test-Cppwill establish, and I will fix whatever it reports.Not in this PR
The C entry points (
tsfile_writer_append_new()and friends) and their lifetime handling. They need a non-truncating internal open —write_file_new()usesO_RDWR | O_CREAT | O_TRUNCtoday, which is the data-loss trap behind requirement 2 of #923 — and the recoveredRestorableTsFileIOWriterhas to outlive the higher-level writer behind a single C handle. I would rather land the C++ behaviour first and send that as a second PR with its own wrapper tests.Closes #923 once the C side follows.