Skip to content

Commit 088cbee

Browse files
[Enhancement] Record generation version on lake PK index sstables (backport #76208) (#76343)
Co-authored-by: xiangguangyxg <110401425+xiangguangyxg@users.noreply.github.com>
1 parent 82d1dfd commit 088cbee

11 files changed

Lines changed: 366 additions & 11 deletions

be/src/storage/lake/lake_persistent_index.cpp

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
#include "storage/lake/lake_persistent_index.h"
1616

17+
#include <unordered_map>
18+
1719
#include "fs/fs_util.h"
1820
#include "fs/key_cache.h"
1921
#include "serde/column_array_serde.h"
@@ -238,6 +240,10 @@ Status LakePersistentIndex::ingest_sst(const FileMetaPB& sst_meta, const Persist
238240
sstable_pb.set_filesize(sst_meta.size());
239241
sstable_pb.set_shared_rssid(rssid);
240242
sstable_pb.set_shared_version(version);
243+
// Record the generation version (PersistentIndexSstablePB.generation_version):
244+
// the version at which this ingested sstable becomes visible in this tablet.
245+
// Coexists with shared_version (read-time projection) and does not affect it.
246+
sstable_pb.set_generation_version(version);
241247
sstable_pb.set_encryption_meta(sst_meta.encryption_meta());
242248
// Preserve the shared flag from sst_meta. During tablet split cross-publish,
243249
// newly-ingested SSTs may be shared with sibling split tablets; losing this flag
@@ -865,7 +871,32 @@ Status LakePersistentIndex::apply_opcompaction(const TabletMetadataPtr& metadata
865871
return Status::OK();
866872
}
867873

868-
Status LakePersistentIndex::commit(MetaFileBuilder* builder) {
874+
void LakePersistentIndex::assign_generation_versions(PersistentIndexSstableMetaPB* new_meta,
875+
const PersistentIndexSstableMetaPB& prev_meta,
876+
int64_t generation_version) {
877+
// Callers resolve |generation_version| to a real publish/reshard version first; a new file must
878+
// never be stamped 0 ("unknown"). Benign if it ever slips through in release -- 0 is the
879+
// conservative value consumers treat as retain -- so a debug assert suffices.
880+
DCHECK_GT(generation_version, 0);
881+
std::unordered_map<std::string, int64_t> prev_versions;
882+
prev_versions.reserve(prev_meta.sstables_size());
883+
for (const auto& s : prev_meta.sstables()) {
884+
prev_versions.emplace(s.filename(), s.generation_version());
885+
}
886+
for (auto& s : *new_meta->mutable_sstables()) {
887+
if (s.generation_version() != 0) {
888+
continue;
889+
}
890+
auto it = prev_versions.find(s.filename());
891+
if (it != prev_versions.end()) {
892+
s.set_generation_version(it->second);
893+
} else {
894+
s.set_generation_version(generation_version);
895+
}
896+
}
897+
}
898+
899+
Status LakePersistentIndex::commit(MetaFileBuilder* builder, int64_t generation_version) {
869900
if ((too_many_rebuild_files() || too_many_rebuild_rows()) && !_memtable->empty()) {
870901
// If we have too many files or rows need to be rebuilt,
871902
// we need to do flush to reduce index rebuild cost later.
@@ -888,6 +919,14 @@ Status LakePersistentIndex::commit(MetaFileBuilder* builder) {
888919
new_sstable_pb->CopyFrom(sstable_pb);
889920
}
890921
}
922+
// generation_version == 0 means "use this publish's version"; a non-zero override is passed by
923+
// the reshard flush (see UpdateManager::flush_pk_memtable). The builder still holds the
924+
// previous persisted sstable_meta here (before finalize_sstable_meta below), which
925+
// assign_generation_versions uses to carry existing versions forward.
926+
if (generation_version == 0) {
927+
generation_version = builder->tablet_meta()->version();
928+
}
929+
assign_generation_versions(&sstable_meta, builder->tablet_meta()->sstable_meta(), generation_version);
891930
builder->finalize_sstable_meta(sstable_meta);
892931
auto [file_cnt, row_cnt] = need_rebuild_counts(*builder->tablet_meta(), sstable_meta);
893932
_need_rebuild_file_cnt = file_cnt;

be/src/storage/lake/lake_persistent_index.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ class LakePersistentIndex : public PersistentIndex {
121121

122122
Status apply_opcompaction(const TabletMetadataPtr& metadata, const TxnLogPB_OpCompaction& op_compaction);
123123

124-
Status commit(MetaFileBuilder* builder);
124+
Status commit(MetaFileBuilder* builder, int64_t generation_version = 0);
125125

126126
Status load_from_lake_tablet(TabletManager* tablet_mgr, const TabletMetadataPtr& metadata, int64_t base_version,
127127
const MetaFileBuilder* builder);
@@ -138,6 +138,14 @@ class LakePersistentIndex : public PersistentIndex {
138138
static void pick_sstables_for_merge(const PersistentIndexSstableMetaPB& sstable_meta,
139139
std::vector<PersistentIndexSstablePB>* sstables, bool* merge_base_level);
140140

141+
// Assign the generation version (PersistentIndexSstablePB.generation_version) to each
142+
// sstable in |new_meta|: a sstable that already carries a non-zero value is left
143+
// untouched; a file present in |prev_meta| keeps its recorded value (a legacy 0 stays 0,
144+
// so lake vacuum retention treats it conservatively); a file absent from |prev_meta| is
145+
// new this publish and is stamped with |generation_version|.
146+
static void assign_generation_versions(PersistentIndexSstableMetaPB* new_meta,
147+
const PersistentIndexSstableMetaPB& prev_meta, int64_t generation_version);
148+
141149
// Check if this rowset need to rebuild, return `True` means need to rebuild this rowset.
142150
static bool needs_rowset_rebuild(const RowsetMetadataPB& rowset, uint32_t rebuild_rss_id);
143151

be/src/storage/lake/lake_primary_index.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ Status LakePrimaryIndex::ingest_sst(const FileMetaPB& sst_meta, const Persistent
227227
}
228228
}
229229

230-
Status LakePrimaryIndex::commit(const TabletMetadataPtr& metadata, MetaFileBuilder* builder) {
230+
Status LakePrimaryIndex::commit(const TabletMetadataPtr& metadata, MetaFileBuilder* builder,
231+
int64_t generation_version) {
231232
TRACE_COUNTER_SCOPE_LATENCY_US("primary_index_commit_latency_us");
232233
if (!_enable_persistent_index) {
233234
return Status::OK();
@@ -250,7 +251,7 @@ Status LakePrimaryIndex::commit(const TabletMetadataPtr& metadata, MetaFileBuild
250251
case PersistentIndexTypePB::CLOUD_NATIVE: {
251252
auto* lake_persistent_index = dynamic_cast<LakePersistentIndex*>(_persistent_index.get());
252253
if (lake_persistent_index != nullptr) {
253-
return lake_persistent_index->commit(builder);
254+
return lake_persistent_index->commit(builder, generation_version);
254255
} else {
255256
return Status::InternalError("Persistent index is not a LakePersistentIndex.");
256257
}

be/src/storage/lake/lake_primary_index.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class LakePrimaryIndex : public PrimaryIndex {
7171

7272
Status apply_opcompaction(const TabletMetadataPtr& metadata, const TxnLogPB_OpCompaction& op_compaction);
7373

74-
Status commit(const TabletMetadataPtr& metadata, MetaFileBuilder* builder);
74+
Status commit(const TabletMetadataPtr& metadata, MetaFileBuilder* builder, int64_t generation_version = 0);
7575

7676
// Force any in-memory memtables of the cloud-native persistent index to
7777
// be flushed into sstables on shared storage. A no-op for LOCAL /

be/src/storage/lake/tablet_merger.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2512,6 +2512,8 @@ Status emit_legacy_shared_sstable_into_dest(TabletManager* tablet_manager, const
25122512
RETURN_IF_ERROR(rebuild_legacy_shared_sstable(tablet_manager, new_metadata.id(), src_pb, ctx.metadata(),
25132513
new_metadata, *family_maps_ptr, &projected_pb));
25142514
if (!projected_pb.filename().empty()) {
2515+
// Rebuilt = a brand-new physical file, first visible at the merge version.
2516+
projected_pb.set_generation_version(new_metadata.version());
25152517
dest->Add()->Swap(&projected_pb);
25162518
}
25172519
return Status::OK();
@@ -2537,6 +2539,8 @@ Status emit_non_shared_legacy_sstable_into_dest(TabletManager* tablet_manager, c
25372539
RETURN_IF_ERROR(rebuild_non_shared_legacy_sstable(tablet_manager, new_metadata.id(), src_pb, ctx,
25382540
ctx.metadata(), &rebuilt_pb));
25392541
if (!rebuilt_pb.filename().empty()) {
2542+
// Rebuilt = a brand-new physical file, first visible at the merge version.
2543+
rebuilt_pb.set_generation_version(new_metadata.version());
25402544
dest->Add()->Swap(&rebuilt_pb);
25412545
}
25422546
return Status::OK();
@@ -2588,7 +2592,8 @@ Status merge_sstables(TabletManager* tablet_manager, std::vector<TabletMergeCont
25882592
// the case where an old tablet accumulated post-split DML that never
25892593
// reached shared storage before merge; see the symmetric call in
25902594
// split_tablet for the pre-split side of the invariant.
2591-
ASSIGN_OR_RETURN(auto flushed_metadata, update_manager->flush_pk_memtable(ctx.metadata()));
2595+
ASSIGN_OR_RETURN(auto flushed_metadata,
2596+
update_manager->flush_pk_memtable(ctx.metadata(), new_metadata->version()));
25922597
ctx.set_metadata(std::move(flushed_metadata));
25932598
if (!ctx.metadata()->has_sstable_meta()) continue;
25942599

be/src/storage/lake/tablet_splitter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1536,7 +1536,7 @@ StatusOr<std::unordered_map<int64_t, MutableTabletMetadataPtr>> split_tablet(
15361536
// the "reshard inputs must have full sstable coverage" invariant; merge
15371537
// does the post-split half in merge_sstables.
15381538
ASSIGN_OR_RETURN(TabletMetadataPtr old_tablet_metadata,
1539-
tablet_manager->update_mgr()->flush_pk_memtable(tablet_metadata));
1539+
tablet_manager->update_mgr()->flush_pk_memtable(tablet_metadata, new_version));
15401540

15411541
// Dispatch on FE-supplied new_tablet_ranges. When set, FE has computed the
15421542
// K-1 boundaries externally (external boundaries / external boundaries); BE computes

be/src/storage/lake/update_manager.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ void UpdateManager::unload_and_remove_primary_index(int64_t tablet_id) {
213213
}
214214
}
215215

216-
StatusOr<TabletMetadataPtr> UpdateManager::flush_pk_memtable(const TabletMetadataPtr& metadata) {
216+
StatusOr<TabletMetadataPtr> UpdateManager::flush_pk_memtable(const TabletMetadataPtr& metadata,
217+
int64_t generation_version) {
217218
if (!is_primary_key(*metadata) || !metadata->enable_persistent_index() ||
218219
metadata->persistent_index_type() != PersistentIndexTypePB::CLOUD_NATIVE) {
219220
return metadata;
@@ -257,7 +258,9 @@ StatusOr<TabletMetadataPtr> UpdateManager::flush_pk_memtable(const TabletMetadat
257258
// mutable_metadata->sstable_meta(). builder.finalize() is NOT called
258259
// here — this flush does not produce its own metadata version; the
259260
// returned metadata is consumed by the surrounding reshard publish.
260-
RETURN_IF_ERROR(index_entry->value().commit(metadata, &builder));
261+
// Pass the reshard publish version as generation_version so freshly-flushed sstables are
262+
// stamped with it (this flush runs at base_version); the metadata version is left untouched.
263+
RETURN_IF_ERROR(index_entry->value().commit(metadata, &builder, generation_version));
261264

262265
// Success: dismiss the failure cleanup and release the cache entry.
263266
// write_guard is released when it goes out of scope at function return.

be/src/storage/lake/update_manager.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ class UpdateManager {
225225
// exclusion between publish_resharding_tablet and publish_version, so
226226
// the cached _data_version cannot advance past metadata->version()
227227
// during this call.
228-
StatusOr<TabletMetadataPtr> flush_pk_memtable(const TabletMetadataPtr& metadata);
228+
StatusOr<TabletMetadataPtr> flush_pk_memtable(const TabletMetadataPtr& metadata, int64_t generation_version);
229229

230230
StatusOr<IndexEntry*> rebuild_primary_index(const TabletMetadataPtr& metadata, MetaFileBuilder* builder,
231231
int64_t base_version, int64_t new_version,

be/test/storage/lake/lake_persistent_index_test.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,6 +1330,7 @@ TEST_F(LakePersistentIndexTest, test_ingest_sst_preserves_shared_flag_for_new_ss
13301330
if (sst.filename() == sst_filename) {
13311331
match_count++;
13321332
EXPECT_TRUE(sst.shared()) << "sst_meta.shared=true must be preserved in committed sstable_meta";
1333+
EXPECT_EQ(1, sst.generation_version()) << "ingest_sst stamps the generation version (the ingest version)";
13331334
}
13341335
}
13351336
EXPECT_EQ(match_count, 1);
@@ -1573,4 +1574,101 @@ TEST_F(LakePersistentIndexTest, test_load_dels_parallel_matches_single_pass) {
15731574
}
15741575
}
15751576

1577+
TEST(AssignGenerationVersionsTest, stamps_new_preserves_existing_and_legacy) {
1578+
PersistentIndexSstableMetaPB prev;
1579+
{
1580+
auto* s = prev.add_sstables();
1581+
s->set_filename("existing_v3.sst");
1582+
s->set_generation_version(3);
1583+
}
1584+
{
1585+
auto* s = prev.add_sstables();
1586+
s->set_filename("legacy_zero.sst"); // version unset == 0
1587+
}
1588+
1589+
PersistentIndexSstableMetaPB cur;
1590+
{
1591+
auto* s = cur.add_sstables();
1592+
s->set_filename("existing_v3.sst"); // reappears, version dropped by copy
1593+
}
1594+
{
1595+
auto* s = cur.add_sstables();
1596+
s->set_filename("legacy_zero.sst"); // legacy, still unset
1597+
}
1598+
{
1599+
auto* s = cur.add_sstables();
1600+
s->set_filename("fresh_new.sst"); // brand new this publish
1601+
}
1602+
{
1603+
auto* s = cur.add_sstables();
1604+
s->set_filename("ingested.sst");
1605+
s->set_generation_version(9); // producer already stamped, absent from prev
1606+
}
1607+
{
1608+
auto* s = cur.add_sstables();
1609+
s->set_filename("existing_v3.sst");
1610+
s->set_generation_version(7); // same filename as prev@3 but already non-zero
1611+
}
1612+
1613+
LakePersistentIndex::assign_generation_versions(&cur, prev, /*generation_version=*/100);
1614+
1615+
ASSERT_EQ(5, cur.sstables_size());
1616+
EXPECT_EQ(3, cur.sstables(0).generation_version()); // existing, local 0 -> carried from prev (3)
1617+
EXPECT_EQ(0, cur.sstables(1).generation_version()); // legacy 0 -> STAYS 0 (present in prev); vacuum retains
1618+
EXPECT_EQ(100, cur.sstables(2).generation_version()); // new (absent from prev) -> new version
1619+
EXPECT_EQ(9, cur.sstables(3).generation_version()); // producer-stamped non-zero -> preserved (absent from prev)
1620+
EXPECT_EQ(7, cur.sstables(4).generation_version()); // non-zero -> NEVER overwritten, even though prev has @3
1621+
}
1622+
1623+
TEST_F(LakePersistentIndexTest, test_commit_stamps_version) {
1624+
const int N = 100;
1625+
const int64_t publish_version = 7;
1626+
auto tablet_id = _tablet_metadata->id();
1627+
auto index = std::make_unique<LakePersistentIndex>(_tablet_mgr.get(), tablet_id);
1628+
ASSERT_OK(index->init(_tablet_metadata));
1629+
1630+
using Key = uint64_t;
1631+
std::vector<Key> keys(N);
1632+
std::vector<Slice> key_slices;
1633+
std::vector<IndexValue> values;
1634+
key_slices.reserve(N);
1635+
values.reserve(N);
1636+
for (int j = 0; j < N; j++) {
1637+
keys[j] = j;
1638+
key_slices.emplace_back((uint8_t*)(&keys[j]), sizeof(Key));
1639+
values.emplace_back(j * 2);
1640+
}
1641+
index->prepare(EditVersion(publish_version, 0), 0);
1642+
std::vector<IndexValue> old_values(N);
1643+
ASSERT_OK(index->upsert(N, key_slices.data(), values.data(), old_values.data()));
1644+
ASSERT_OK(index->flush_memtable(true));
1645+
ASSERT_OK(index->sync_flush_all_memtables(10000000));
1646+
1647+
Tablet tablet(_tablet_mgr.get(), tablet_id);
1648+
auto meta = std::make_shared<TabletMetadata>();
1649+
meta->CopyFrom(*_tablet_metadata);
1650+
meta->set_version(publish_version);
1651+
MetaFileBuilder builder(tablet, meta);
1652+
ASSERT_OK(index->commit(&builder));
1653+
1654+
// A freshly-flushed sstable is stamped with the publish version.
1655+
ASSERT_GE(meta->sstable_meta().sstables_size(), 1);
1656+
for (const auto& s : meta->sstable_meta().sstables()) {
1657+
EXPECT_EQ(publish_version, s.generation_version()) << "freshly-flushed sstable must carry its publish version";
1658+
}
1659+
1660+
// Re-commit at a later version WITHOUT new data: existing sstables must keep
1661+
// their original version (no re-stamp), because they are present in
1662+
// the previous sstable_meta the builder carries forward.
1663+
auto meta2 = std::make_shared<TabletMetadata>();
1664+
meta2->CopyFrom(*meta); // carries the publish_version sstable_meta forward
1665+
meta2->set_version(publish_version + 10);
1666+
MetaFileBuilder builder2(tablet, meta2);
1667+
ASSERT_OK(index->commit(&builder2));
1668+
ASSERT_GE(meta2->sstable_meta().sstables_size(), 1);
1669+
for (const auto& s : meta2->sstable_meta().sstables()) {
1670+
EXPECT_EQ(publish_version, s.generation_version()) << "re-commit must not re-stamp an existing sstable";
1671+
}
1672+
}
1673+
15761674
} // namespace starrocks::lake

0 commit comments

Comments
 (0)