Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[BACKPORT 2024.1][#23335] DocDB: Set field close timestamp for the lo…
…g segment being copied Summary: Original commit: 622046d / D36956 When a log segment is being copied partially, we weren't setting the footer's `close_timestamp_micros` for the new segment. This could result in log GC that violates time based policy controlled by flag `time_based_wal_gc_clock_delta_usec`, as we don't prevent a GC of a potential log segment if it doesn't have the close timestamp set (note that this does not violate other log GC policies which are required for correctness in any way). `Log::GetSegmentsToGCUnlocked` -> `LogReader::GetSegmentPrefixNotIncluding` -> `LogReader::ViolatesMaxTimePolicy(segment)` wrongly infers that the age violates the max log retention policy. ``` bool LogReader::ViolatesMaxTimePolicy(const scoped_refptr<ReadableLogSegment>& segment) const { ... int64_t now = GetCurrentTimeMicros(); int64_t age_seconds = (now - segment->footer().close_timestamp_micros()) / 1000000; // this will infer wrong age because close ts is missing if (age_seconds > FLAGS_log_max_seconds_to_retain) { ... return true; } return false; } ``` and then, `Log::GetSegmentsToGCUnlocked` -> `Log::ApplyTimeRetentionPolicy` doesn't prevent this since the footer doesn't have the close ts. ``` void Log::ApplyTimeRetentionPolicy(SegmentSequence* segments_to_gc) const { int64_t now = GetCurrentTimeMicros() + FLAGS_time_based_wal_gc_clock_delta_usec; for (auto iter = segments_to_gc->begin(); iter != segments_to_gc->end(); ++iter) { const auto& segment = *iter; if (!segment->footer().has_close_timestamp_micros()) continue; // We let the segment be GC'ed if it doesn't have the field close_timestamp_micros set. int64_t age_seconds = (now - segment->footer().close_timestamp_micros()) / 1000000; if (age_seconds < wal_retention_secs()) { // Truncate the list of segments to GC here -- if this one is too new, then all later ones are // also too new. segments_to_gc->truncate(iter); break; } } } ``` The above violation seems possible only on the tablet split codepath which invokes the function `ReadableLogSegment::CopyTo`, and that too when the split op isn't the last op in the parent's log segment. This is rare since there are only certain special ops that are allowed to be appended to the log after the split op (`NO_OP`, `SNAPSHOT_OP`, `CLONE_OP`, `CHANGE_CONFIG_OP`). Additional note: The field `close_timestamp_micros` seems only relevant in the log GC codepath. so there shouldn't have been any other issues caused by this bug. This diff addresses the issue by setting the field `close_timestamp_micros` for the log segment being copied. Jira: DB-12262 Test Plan: ./yb_build.sh --cxx-test='TEST_F(LogTest, CopyUpTo) {' The above test fails without the fix. Added a new test where we attempt a leader change on the parent tablet after split, forcing the log copy path to invoke actual copy (duplication) as opposed to hard linking to the paren't log segment. ``` switch (relation) { case SegmentOpIdRelation::kOpIdBeforeSegment: return true; case SegmentOpIdRelation::kOpIdIsLast: stop = true; FALLTHROUGH_INTENDED; case SegmentOpIdRelation::kEmptySegment: FALLTHROUGH_INTENDED; case SegmentOpIdRelation::kOpIdAfterSegment: RETURN_NOT_OK(env->LinkFile(src_path, dest_path)); VLOG_WITH_PREFIX(1) << Format("Hard linked $0 to $1", src_path, dest_path); return stop; case SegmentOpIdRelation::kOpIdIsInsideAndNotLast: // Copy part of the segment up to and including max_included_op_id. RETURN_NOT_OK(segment->CopyTo( env, GetNewSegmentWritableFileOptions(), dest_path, max_included_op_id)); return true; } ``` ./yb_build.sh --cxx-test='TEST_F(TabletSplitITest, TestLogCopySetsCloseTimestampInFooter) {' -n 50 --tp 1 this test fails consistently without the fix. Reviewers: timur Reviewed By: timur Subscribers: ybase Tags: #jenkins-ready Differential Revision: https://phorge.dev.yugabyte.com/D37177
- Loading branch information