Skip to content

Commit a0fa3ec

Browse files
committed
[#33444] DocDB: Orchestrate the index-backfill ordering-generation lifecycle from the master
The persisted ordering generation (#33580) had no production lifecycle: nothing activated it before marked backfill writes, nothing released it, and the master-side split suppression protecting a backfill is in-memory only (lost on failover). This part wires the full lifecycle and flips marked-write gating fail-closed. Activation rides ChangeMetadataOperation (a new `ChangeMetadataRequestPB.index_backfill_ordering_generation` variant, applied via `Tablet::UpdateIndexBackfillOrderingGeneration` exactly like `mark_backfill_done`): Raft-replicated, follower-applied, WAL-replayed, and `base_op_index` is the activation operation's own Raft index, so every replica and every replay derives the same base with no master-side bookkeeping. Re-activation (failover resume) is idempotent -- the base only moves up. Master flow, SKIP_ALL jobs on PGSQL indexed tables only (the mode rides YSQL chunk requests; YCQL backfill writes are never marked, so generations would only fence their splits without protecting anything): - Before the first chunk (`DoBackfill`), the job disables and drains index-table splitting, then fans out one waited `UpdateOrderingGenerationForTablet` task per index tablet (the `GetSafeTimeForTablet` join pattern). All acks -> chunks launch; any failure -> the job aborts before a single marked write exists, and CREATE INDEX fails cleanly. The drain closes the activation/split TOCTOU: consensus already rejects CHANGE_METADATA_OP while a split is pending, the generation fence rejects splits after activation applies, and a split appended in the append-to-apply window can at worst fail the activation task -- never corrupt a scan set that marked writes exist in. - Release is fire-and-forget from the terminal funnel (`UpdateIndexPermissionsForIndexes`), which every success/failure/abort path traverses. The tserver metadata validator converges stragglers, mirroring the retain_delete_markers machinery: tablets with an active generation join its existing GetBackfillStatus poll, and the generation is released locally (no Raft) once the master reports a terminal state. `IndexStatusPB` gains `BACKFILL_FAILED` (removal-path index permissions map to it) so a failed SKIP_ALL job whose funnel release was lost cannot leave an orphaned invalid index permanently split-fenced and retention-pinned; the retain_delete_markers heal itself stays success-only. - The tablet-split manager refuses to split an index whose indexed table has a durable SKIP_ALL backfill job. The manager's table validation takes no catalog locks itself -- the indexed table is a caller-resolved parameter, because the manual-split path enters through ValidateSplitCandidateUnlocked with the catalog mutex already held (a recursive shared acquisition is fatal under lock_debug) (`SysTablesEntryPB.backfill_jobs`, survives failover, cleared in the funnel), with an INFO-level skip reason -- today's two in-memory suppressions cover only the indexed table and evaporate on failover. The tablet-side generation fence stays as the fail-closed layer. Gating flip: `WriteOperation::ValidateLeaderOpId` now rejects marked writes with *no* active generation (previously deferred) -- a marked write outside a generation would store versions nothing tracks or releases (e.g. a stale chunk retry after the job's terminal state). Tests that drive marked writes directly now activate a generation first. `write_id_floor_version` numbering lands as `kIndexBackfillWriteIdFloorVersion = 1`. The mode selector remains hardcoded to CHECK_ALL (#33484), so this whole flow stays production-unreachable; tests drive it through the master-side TEST override. Test Plan: ./yb_build.sh release --cxx-test tablet_peer-test --gtest_filter 'TabletPeerTest.OrderingGenerationChangeMetadataOpSetsBaseFromOwnRaftIndex' ./yb_build.sh release --cxx-test tablet_peer-test --gtest_filter 'TabletPeerTest.FixedHybridTimeWriteRejectedAtOrBelowGenerationBase' ./yb_build.sh release --cxx-test pg_index_backfill-test --gtest_filter 'PgIndexBackfillSkipAllRaftOrdering.OrderingGenerationActivatedAndReleased/0' ./yb_build.sh release --cxx-test pg_index_backfill-test --gtest_filter 'PgIndexBackfillSkipAllActivationFailure.ActivationFailureFailsCreateIndexCleanly/0' ./yb_build.sh release --cxx-test pg_index_backfill-test --gtest_filter 'PgIndexBackfillSkipAllBlocked.SplitFencedDuringBackfill/0' Retrofitted (marked writes now require an active generation): ./yb_build.sh release --cxx-test tablet_peer-test --gtest_filter 'TabletPeerTest.FixedHybridTimeWrite*' ./yb_build.sh release --cxx-test fixed_hybrid_time_write_id-itest Regressions: skip_all e2e + marker canary through the real activation flow, uniq-idx-1 mode tests, DuplicatesExistBeforeBackfill, 3b-i generation tests, 0a/0b abort tests, and drop-path tests for the permissions mapping change (PgIndexBackfillTest.Drop, PgIndexBackfillFastClientTimeout.DropWhileBackfilling) -- all green. Assisted-By: devx/08801cf6-2854-4c6f-a378-1d98d239b22d --- _automated · Claude Fable 5 (opencode)_
1 parent cc9ba40 commit a0fa3ec

26 files changed

Lines changed: 825 additions & 56 deletions

src/yb/common/doc_hybrid_time.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ constexpr IntraTxnWriteId kBackfillWriteIdFloor = kIntraTxnWriteIdLimit;
5454
constexpr IntraTxnWriteId kBackfillWriteIdIndexMax = kMaxWriteId - kBackfillWriteIdFloor - 1;
5555
static_assert((kBackfillWriteIdFloor | kBackfillWriteIdIndexMax) == kMaxWriteId - 1);
5656

57+
// Version of the floor scheme recorded in each ordering generation
58+
// (IndexBackfillOrderingGenerationPB.write_id_floor_version): 1 = the scheme above; 0 is
59+
// reserved as absent/unknown. Bump if the floor or the derivation ever changes, so marked data
60+
// written under an older scheme cannot be misinterpreted.
61+
constexpr uint32_t kIndexBackfillWriteIdFloorVersion = 1;
62+
5763
// An aggressive upper bound on the length of a DocDB-encoded hybrid time with a write id.
5864
// This could happen in the degenerate case when all three VarInts in encoded representation of a
5965
// DocHybridTime take 10 bytes (the maximum length for a VarInt-encoded int64_t).

src/yb/integration-tests/fixed_hybrid_time_write_id-itest.cc

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ TEST_F(FixedHybridTimeWriteIdITest, LeaderChangePreservesDistinctWriteIds) {
285285
ASSERT_OK(WaitUntilTabletHasLeader(
286286
cluster_.get(), tablet_id, CoarseMonoClock::Now() + 10s * kTimeMultiplier,
287287
RequireLeaderIsReady::kTrue));
288+
ASSERT_OK(SetOrderingGenerationOnAllReplicas(tablet_id, MakeActiveOrderingGeneration()));
288289

289290
// W1 under the original leader: two distinct keys in one marked op share the op's Raft-index
290291
// write ID ("dup" is the key W2 collides with later; "stable" pins the shared-ID property).
@@ -344,6 +345,7 @@ TEST_F(FixedHybridTimeWriteIdITest, MasterSplitRefusedWhileOrderingGenerationAct
344345
ASSERT_OK(WaitUntilTabletHasLeader(
345346
cluster_.get(), tablet_id, CoarseMonoClock::Now() + 10s * kTimeMultiplier,
346347
RequireLeaderIsReady::kTrue));
348+
ASSERT_OK(SetOrderingGenerationOnAllReplicas(tablet_id, MakeActiveOrderingGeneration()));
347349

348350
// Give the tablet hash-spread data and SSTs so it is a viable split candidate.
349351
const auto op_id = ASSERT_RESULT(
@@ -355,9 +357,9 @@ TEST_F(FixedHybridTimeWriteIdITest, MasterSplitRefusedWhileOrderingGenerationAct
355357
const auto table_id = table_.table()->id();
356358
ASSERT_EQ(ListActiveTabletIdsForTable(cluster_.get(), table_id).size(), 1);
357359

358-
// With the generation active on every replica, the master accepts the split request but the
359-
// tablet-side fence rejects the split operation before it is appended to Raft.
360-
ASSERT_OK(SetOrderingGenerationOnAllReplicas(tablet_id, MakeActiveOrderingGeneration()));
360+
// The generation has been active on every replica since before the writes; the master
361+
// accepts the split request but the tablet-side fence rejects the split operation before it
362+
// is appended to Raft.
361363
ASSERT_OK(InvokeSplitTabletRpc(
362364
cluster_.get(), tablet_id, MonoDelta::FromSeconds(30) * kTimeMultiplier));
363365
SleepFor(MonoDelta::FromSeconds(3) * kTimeMultiplier);
@@ -393,9 +395,9 @@ TEST_F(FixedHybridTimeWriteIdITest, OrderingGenerationSurvivesRemoteBootstrap) {
393395
cluster_.get(), tablet_id, CoarseMonoClock::Now() + 10s * kTimeMultiplier,
394396
RequireLeaderIsReady::kTrue));
395397

398+
ASSERT_OK(SetOrderingGenerationOnAllReplicas(tablet_id, MakeActiveOrderingGeneration()));
396399
const auto op_id = ASSERT_RESULT(SendMarkedWrite(tablet_id, kWriteHT, {{"row", "value"}}));
397400
ASSERT_OK(WaitAllReplicasApplied(tablet_id, op_id));
398-
ASSERT_OK(SetOrderingGenerationOnAllReplicas(tablet_id, MakeActiveOrderingGeneration()));
399401

400402
// Tombstone one follower; it stays in the Raft config, so the leader remote-bootstraps it
401403
// back. The recovered replica's superblock comes from the leader and must carry the
@@ -464,13 +466,13 @@ TEST_F(FixedHybridTimeWriteIdITest, SplitWithFenceBypassedPreservesPerKeyWriteId
464466
ASSERT_OK(WaitUntilTabletHasLeader(
465467
cluster_.get(), tablet_id, CoarseMonoClock::Now() + 10s * kTimeMultiplier,
466468
RequireLeaderIsReady::kTrue));
469+
ASSERT_OK(SetOrderingGenerationOnAllReplicas(tablet_id, MakeActiveOrderingGeneration()));
467470

468471
const auto op_id = ASSERT_RESULT(
469472
SendMarkedWriteSpreadHashes(tablet_id, kWriteHT, /* num_keys= */ 256));
470473
ASSERT_OK(WaitAllReplicasApplied(tablet_id, op_id));
471474
ASSERT_OK(cluster_->FlushTablets());
472475
ASSERT_OK(WaitForAnySstFiles(cluster_.get(), tablet_id));
473-
ASSERT_OK(SetOrderingGenerationOnAllReplicas(tablet_id, MakeActiveOrderingGeneration()));
474476

475477
const auto table_id = table_.table()->id();
476478
ASSERT_OK(InvokeSplitTabletRpc(

src/yb/integration-tests/tablet-split-itest.cc

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -970,10 +970,12 @@ TEST_F(TabletSplitITest, MaxCreateTabletsPerTs) {
970970
auto table = catalog_mgr->GetTableInfo(table_->id());
971971

972972
ANNOTATE_UNPROTECTED_WRITE(FLAGS_max_create_tablets_per_ts) = 1;
973-
ASSERT_NOK(master.tablet_split_manager().ValidateSplitCandidateTable(table));
973+
ASSERT_NOK(master.tablet_split_manager().ValidateSplitCandidateTable(
974+
table, /* indexed_table= */ nullptr));
974975

975976
ANNOTATE_UNPROTECTED_WRITE(FLAGS_max_create_tablets_per_ts) = 2;
976-
ASSERT_OK(master.tablet_split_manager().ValidateSplitCandidateTable(table));
977+
ASSERT_OK(master.tablet_split_manager().ValidateSplitCandidateTable(
978+
table, /* indexed_table= */ nullptr));
977979
}
978980

979981
TEST_F(TabletSplitITest, SplitDuringReplicaOffline) {
@@ -2762,23 +2764,26 @@ TEST_F(TabletSplitSingleServerITest, AutoSplitNotValidOnceCheckedForTtl) {
27622764
auto table_info = ASSERT_NOTNULL(catalog_mgr->GetTableInfo(table_->id()));
27632765

27642766
// Candidate table should start as a valid split candidate.
2765-
ASSERT_OK(split_manager->ValidateSplitCandidateTable(table_info));
2767+
ASSERT_OK(split_manager->ValidateSplitCandidateTable(
2768+
table_info, /* indexed_table= */ nullptr));
27662769

27672770
// State that table should not be split for the next 1 second.
27682771
// Candidate table should no longer be valid.
27692772
split_manager->DisableSplittingForTtlTable(table_->id());
2770-
ASSERT_NOK(split_manager->ValidateSplitCandidateTable(table_info));
2773+
ASSERT_NOK(split_manager->ValidateSplitCandidateTable(
2774+
table_info, /* indexed_table= */ nullptr));
27712775

27722776
// After 2 seconds, table is a valid split candidate again.
27732777
SleepFor(kSecondsBetweenChecks * 2s);
2774-
ASSERT_OK(split_manager->ValidateSplitCandidateTable(table_info));
2778+
ASSERT_OK(split_manager->ValidateSplitCandidateTable(
2779+
table_info, /* indexed_table= */ nullptr));
27752780

27762781
// State again that table should not be split for the next 1 second.
27772782
// Candidate table should still be a valid candidate if ignore_disabled_list
27782783
// is true (e.g. in the case of manual tablet splitting).
27792784
split_manager->DisableSplittingForTtlTable(table_->id());
2780-
ASSERT_OK(split_manager->ValidateSplitCandidateTable(table_info,
2781-
master::IgnoreDisabledList::kTrue));
2785+
ASSERT_OK(split_manager->ValidateSplitCandidateTable(
2786+
table_info, /* indexed_table= */ nullptr, master::IgnoreDisabledList::kTrue));
27822787
}
27832788

27842789
TEST_F(TabletSplitSingleServerITest, ScheduledFullCompactionsDoNotBlockSplit) {

src/yb/master/backfill_index.cc

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "yb/tserver/tserver_admin.proxy.h"
3333

3434
#include "yb/common/common_util.h"
35+
#include "yb/common/doc_hybrid_time.h"
3536
#include "yb/common/wire_protocol.h"
3637

3738
#include "yb/docdb/doc_rowwise_iterator.h"
@@ -1076,6 +1077,18 @@ Status BackfillTable::DoBackfill() {
10761077
VLOG_WITH_PREFIX(1) << "starting backfill with timestamp: " << read_time_for_backfill();
10771078
}
10781079

1080+
if (unique_index_backfill_mode() == UniqueIndexBackfillMode::UNIQUE_INDEX_BACKFILL_SKIP_ALL &&
1081+
!ordering_generation_activated_.exchange(true)) {
1082+
// SKIP_ALL writes are marked (Raft-index write IDs), which every index tablet only accepts
1083+
// under an active ordering generation -- activate before the first chunk. The job continues
1084+
// into LaunchBackfillTablets through OrderingGenerationUpdateDone once every tablet acks.
1085+
// On failover resume this re-runs: re-activation is idempotent (the base moves up).
1086+
return LaunchOrderingGenerationActivation();
1087+
}
1088+
return LaunchBackfillTablets();
1089+
}
1090+
1091+
Status BackfillTable::LaunchBackfillTablets() {
10791092
auto tablets = VERIFY_RESULT(indexed_table_->GetTablets());
10801093
num_tablets_.store(tablets.size(), std::memory_order_release);
10811094
tablets_pending_.store(tablets.size(), std::memory_order_release);
@@ -1086,6 +1099,126 @@ Status BackfillTable::DoBackfill() {
10861099
return Status::OK();
10871100
}
10881101

1102+
Result<std::vector<scoped_refptr<TableInfo>>> BackfillTable::GetUniqueIndexTables(
1103+
RestrictToIndexesToBuild restrict_to_indexes_to_build) const {
1104+
const auto to_build = indexes_to_build();
1105+
std::vector<scoped_refptr<TableInfo>> tables;
1106+
for (const auto& index_info : index_infos_) {
1107+
if (!index_info.is_unique()) {
1108+
continue;
1109+
}
1110+
if (restrict_to_indexes_to_build && to_build.count(index_info.table_id()) == 0) {
1111+
continue;
1112+
}
1113+
auto res = master_->catalog_manager()->FindTableById(index_info.table_id());
1114+
if (!res && res.status().IsNotFound()) {
1115+
// Concurrent DROP INDEX; the job will fail through the missing-IndexInfoPB path.
1116+
LOG_WITH_PREFIX(WARNING) << "Index " << index_info.table_id() << " was not found; "
1117+
<< "skipping for ordering-generation update: " << res.status();
1118+
continue;
1119+
}
1120+
tables.push_back(VERIFY_RESULT_PREPEND(
1121+
std::move(res), Format("Could not find the index table $0", index_info.table_id())));
1122+
}
1123+
return tables;
1124+
}
1125+
1126+
Status BackfillTable::LaunchOrderingGenerationActivation() {
1127+
if (indexed_table_->GetTableType() != TableType::PGSQL_TABLE_TYPE) {
1128+
// The SKIP_ALL write path is PGSQL-only (the backfill request mode rides the YSQL chunk
1129+
// requests; YCQL backfill writes are never marked), so generations would only fence
1130+
// splits without protecting anything. Reachable today only via the TEST mode override.
1131+
return LaunchBackfillTablets();
1132+
}
1133+
auto index_tables = VERIFY_RESULT(GetUniqueIndexTables(RestrictToIndexesToBuild::kTrue));
1134+
if (index_tables.empty()) {
1135+
return LaunchBackfillTablets();
1136+
}
1137+
1138+
// Fence and drain index-table splitting before activating. The tablet-side fences (the
1139+
// pending-split rejection of CHANGE_METADATA_OP and, once active, the generation split
1140+
// fence) close the append-time races; this master-side drain removes the split/activation
1141+
// TOCTOU window entirely under a single master leader, and the persisted fence in the
1142+
// tablet-split manager (backfill-job state) keeps splits excluded across failover.
1143+
auto& tablet_split_manager = master_->tablet_split_manager();
1144+
const CoarseTimePoint deadline =
1145+
CoarseMonoClock::Now() +
1146+
FLAGS_index_backfill_tablet_split_completion_timeout_sec * 1s * kTimeMultiplier;
1147+
for (const auto& index_table : index_tables) {
1148+
tablet_split_manager.DisableSplittingForBackfillingTable(index_table->id());
1149+
while (!tablet_split_manager.IsTabletSplittingComplete(
1150+
*index_table, false /* wait_for_parent_deletion */, deadline)) {
1151+
if (CoarseMonoClock::Now() > deadline) {
1152+
return STATUS(
1153+
TimedOut,
1154+
"Index-tablet splitting did not complete after being disabled; cannot safely "
1155+
"activate the ordering generation.");
1156+
}
1157+
SleepFor(FLAGS_index_backfill_tablet_split_completion_poll_freq_ms * 1ms * kTimeMultiplier);
1158+
}
1159+
}
1160+
1161+
std::vector<std::pair<TabletInfoPtr, TableId>> tablets;
1162+
for (const auto& index_table : index_tables) {
1163+
for (auto& tablet : VERIFY_RESULT(index_table->GetTablets())) {
1164+
tablets.emplace_back(std::move(tablet), index_table->id());
1165+
}
1166+
}
1167+
if (tablets.empty()) {
1168+
return LaunchBackfillTablets();
1169+
}
1170+
1171+
LOG_WITH_PREFIX(INFO) << "Activating index-backfill ordering generation on " << tablets.size()
1172+
<< " index tablet(s) before launching backfill chunks";
1173+
activation_tablets_pending_.store(tablets.size(), std::memory_order_release);
1174+
for (auto& [tablet, index_table_id] : tablets) {
1175+
auto task = std::make_shared<UpdateOrderingGenerationForTablet>(
1176+
shared_from_this(), tablet, index_table_id, tablet::ActivateGeneration::kTrue,
1177+
NotifyBackfillTable::kTrue, epoch_);
1178+
RETURN_NOT_OK(task->Launch());
1179+
}
1180+
return Status::OK();
1181+
}
1182+
1183+
void BackfillTable::OrderingGenerationUpdateDone(
1184+
const Status& status, const TabletId& tablet_id) {
1185+
if (done()) {
1186+
return;
1187+
}
1188+
if (!status.ok()) {
1189+
LOG_WITH_PREFIX(WARNING) << "Ordering-generation activation failed for tablet " << tablet_id
1190+
<< ": " << status << "; aborting backfill";
1191+
WARN_NOT_OK(Abort(), "Failed to abort backfill after activation failure");
1192+
return;
1193+
}
1194+
if (--activation_tablets_pending_ == 0) {
1195+
LOG_WITH_PREFIX(INFO) << "Ordering generation active on all index tablets; "
1196+
<< "launching backfill chunks";
1197+
Status s = LaunchBackfillTablets();
1198+
if (!s.ok()) {
1199+
LOG_WITH_PREFIX(WARNING) << "Failed to launch backfill after activation: " << s;
1200+
WARN_NOT_OK(Abort(), "Failed to abort backfill");
1201+
}
1202+
}
1203+
}
1204+
1205+
Status BackfillTable::SendRpcToReleaseOrderingGenerations() {
1206+
// Release on every unique index of the job, built or not: activation may have partially
1207+
// succeeded before a failure. Fire-and-forget -- the tserver metadata validator and master
1208+
// reload reconciliation converge any straggler.
1209+
auto index_tables = VERIFY_RESULT(GetUniqueIndexTables(RestrictToIndexesToBuild::kFalse));
1210+
for (const auto& index_table : index_tables) {
1211+
for (const auto& tablet : VERIFY_RESULT(index_table->GetTablets())) {
1212+
auto task = std::make_shared<UpdateOrderingGenerationForTablet>(
1213+
shared_from_this(), tablet, index_table->id(), tablet::ActivateGeneration::kFalse,
1214+
NotifyBackfillTable::kFalse, epoch_);
1215+
WARN_NOT_OK(task->Launch(), "Failed to send ordering-generation release");
1216+
}
1217+
master_->tablet_split_manager().ReenableSplittingForBackfillingTable(index_table->id());
1218+
}
1219+
return Status::OK();
1220+
}
1221+
10891222
Status BackfillTable::Done(const Status& s, const std::unordered_set<TableId>& failed_indexes) {
10901223
if (!s.ok()) {
10911224
LOG_WITH_PREFIX(WARNING) << "failed to backfill the index: " << AsString(failed_indexes)
@@ -1316,6 +1449,13 @@ Status BackfillTable::UpdateIndexPermissionsForIndexes() {
13161449
RETURN_NOT_OK(ClearCheckpointStateInTablets());
13171450
indexed_table_->ClearIsBackfilling();
13181451
master_->tablet_split_manager().ReenableSplittingForBackfillingTable(indexed_table_->id());
1452+
if (unique_index_backfill_mode() == UniqueIndexBackfillMode::UNIQUE_INDEX_BACKFILL_SKIP_ALL) {
1453+
// Terminal funnel: every success/failure/abort path of a SKIP_ALL job passes through here,
1454+
// so the ordering generations are released (and index-table splitting re-enabled) on all of
1455+
// them. At-least-once; backstops converge anything this misses (e.g. master death here).
1456+
WARN_NOT_OK(
1457+
SendRpcToReleaseOrderingGenerations(), "Failed to release ordering generations");
1458+
}
13191459

13201460
VLOG(1) << "Sending alter table requests to the Indexed table";
13211461
RETURN_NOT_OK(master_->catalog_manager_impl()->SendAlterTableRequest(indexed_table_, epoch_));
@@ -1666,6 +1806,115 @@ void GetSafeTimeForTablet::UnregisterAsyncTaskCallback() {
16661806
"Could not UpdateSafeTime");
16671807
}
16681808

1809+
UpdateOrderingGenerationForTablet::UpdateOrderingGenerationForTablet(
1810+
std::shared_ptr<BackfillTable> backfill_table,
1811+
const TabletInfoPtr& tablet,
1812+
const TableId& index_table_id,
1813+
tablet::ActivateGeneration activate,
1814+
NotifyBackfillTable notify_backfill_table,
1815+
LeaderEpoch epoch)
1816+
: RetryingTSRpcTaskWithTable(
1817+
backfill_table->master(), backfill_table->threadpool(),
1818+
std::unique_ptr<TSPicker>(new PickLeaderReplica(tablet)), tablet->table(),
1819+
std::move(epoch),
1820+
/* async_task_throttler */ nullptr),
1821+
backfill_table_(backfill_table),
1822+
tablet_(tablet),
1823+
index_table_id_(index_table_id),
1824+
activate_(activate),
1825+
notify_backfill_table_(notify_backfill_table) {
1826+
deadline_ = MonoTime::Max(); // Single-attempt deadline comes from ComputeDeadline().
1827+
}
1828+
1829+
Status UpdateOrderingGenerationForTablet::Launch() {
1830+
tablet_->table()->AddTask(shared_from_this());
1831+
RETURN_NOT_OK_PREPEND(
1832+
Run(),
1833+
Substitute("Failed to send UpdateOrderingGeneration request for $0. ",
1834+
tablet_->ToString()));
1835+
VLOG(3) << "Started UpdateOrderingGenerationForTablet : " << this->description();
1836+
return Status::OK();
1837+
}
1838+
1839+
std::string UpdateOrderingGenerationForTablet::description() const {
1840+
return Format(
1841+
"$0 ordering generation for index tablet $1 of $2",
1842+
activate_ ? "Activate" : "Release", tablet_id(), index_table_id_);
1843+
}
1844+
1845+
TabletId UpdateOrderingGenerationForTablet::tablet_id() const { return tablet_->id(); }
1846+
1847+
bool UpdateOrderingGenerationForTablet::SendRequest(int attempt) {
1848+
ADOPT_WAIT_STATE(backfill_table_->wait_state());
1849+
tablet::ChangeMetadataRequestPB req;
1850+
req.set_dest_uuid(permanent_uuid());
1851+
req.set_tablet_id(tablet_->tablet_id());
1852+
req.set_propagated_hybrid_time(master_->clock()->Now().ToUint64());
1853+
auto* generation_op = req.mutable_index_backfill_ordering_generation();
1854+
generation_op->set_table_id(index_table_id_);
1855+
generation_op->set_activate(activate_);
1856+
if (activate_) {
1857+
// History at and above backfill_read_time.Decremented() must survive until verification;
1858+
// the record carries the barrier, enforcement lands with the verification read fence.
1859+
generation_op->set_retention_barrier_ht(
1860+
backfill_table_->read_time_for_backfill().Decremented().ToUint64());
1861+
generation_op->set_write_id_floor_version(kIndexBackfillWriteIdFloorVersion);
1862+
}
1863+
1864+
ts_admin_proxy_->UpdateIndexBackfillOrderingGenerationAsync(
1865+
req, &resp_, &rpc_, BindRpcCallback());
1866+
VLOG(1) << "Send " << description() << " to " << permanent_uuid()
1867+
<< " (attempt " << attempt << "):\n" << req.DebugString();
1868+
return true;
1869+
}
1870+
1871+
void UpdateOrderingGenerationForTablet::HandleResponse(int attempt) {
1872+
ADOPT_WAIT_STATE(backfill_table_->wait_state());
1873+
Status status = Status::OK();
1874+
if (resp_.has_error()) {
1875+
status = StatusFromPB(resp_.error().status());
1876+
switch (resp_.error().code()) {
1877+
case TabletServerErrorPB::TABLET_NOT_FOUND:
1878+
case TabletServerErrorPB::OPERATION_NOT_SUPPORTED:
1879+
LOG(WARNING) << "TS " << permanent_uuid() << ": " << description()
1880+
<< " failed, no further retry: " << status;
1881+
TransitionToFailedState(MonitoredTaskState::kRunning, status);
1882+
break;
1883+
default:
1884+
LOG(WARNING) << "TS " << permanent_uuid() << ": " << description() << " failed: "
1885+
<< status << " code " << resp_.error().code();
1886+
break;
1887+
}
1888+
} else {
1889+
TransitionToCompleteState();
1890+
VLOG(1) << "TS " << permanent_uuid() << ": " << description() << " complete";
1891+
}
1892+
1893+
server::UpdateClock(resp_, master_->clock());
1894+
}
1895+
1896+
void UpdateOrderingGenerationForTablet::UnregisterAsyncTaskCallback() {
1897+
ADOPT_WAIT_STATE(backfill_table_->wait_state());
1898+
if (state() == MonitoredTaskState::kAborted) {
1899+
// Deliberately no join notification (same shape as GetSafeTimeForTablet): external task
1900+
// aborts accompany leadership loss or table teardown, where the job object is dying with
1901+
// us; notifying could double-drive a job that is already unwinding.
1902+
VLOG(1) << " was aborted";
1903+
return;
1904+
}
1905+
if (!notify_backfill_table_) {
1906+
return;
1907+
}
1908+
1909+
Status status;
1910+
if (resp_.has_error()) {
1911+
status = StatusFromPB(resp_.error().status());
1912+
} else if (state() != MonitoredTaskState::kComplete) {
1913+
status = STATUS_FORMAT(InternalError, "$0 in state $1", description(), state());
1914+
}
1915+
backfill_table_->OrderingGenerationUpdateDone(status, tablet_->tablet_id());
1916+
}
1917+
16691918
BackfillChunk::BackfillChunk(std::shared_ptr<BackfillTablet> backfill_tablet,
16701919
const std::string& start_key,
16711920
LeaderEpoch epoch)

0 commit comments

Comments
 (0)