Skip to content

Commit 9d5289f

Browse files
committed
feat: produce gcSnapshotBitmap during Memory GC for zero-traversal checkpointing
1 parent 8327c5c commit 9d5289f

11 files changed

Lines changed: 602 additions & 34 deletions

File tree

cpp/pixels-retina/include/RGVisibility.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class RGVisibility : public pixels::RetinaBase<RGVisibility<CAPACITY>> {
3333
void deleteRGRecord(uint32_t rowId, uint64_t timestamp);
3434
uint64_t* getRGVisibilityBitmap(uint64_t timestamp);
3535

36-
void collectRGGarbage(uint64_t timestamp);
36+
std::vector<uint64_t> collectRGGarbage(uint64_t timestamp);
3737

3838
uint64_t getBitmapSize() const;
3939

cpp/pixels-retina/include/RGVisibilityJni.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cpp/pixels-retina/include/TileVisibility.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class TileVisibility : public pixels::RetinaBase<TileVisibility<CAPACITY>> {
9797
~TileVisibility() override;
9898
void deleteTileRecord(uint16_t rowId, uint64_t ts);
9999
void getTileVisibilityBitmap(uint64_t ts, uint64_t* outBitmap) const;
100-
void collectTileGarbage(uint64_t ts);
100+
void collectTileGarbage(uint64_t ts, uint64_t* gcSnapshotBitmap);
101101

102102
private:
103103
TileVisibility(const TileVisibility &) = delete;

cpp/pixels-retina/lib/RGVisibility.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,14 @@ RGVisibility<CAPACITY>::~RGVisibility() {
6262
}
6363

6464
template<size_t CAPACITY>
65-
void RGVisibility<CAPACITY>::collectRGGarbage(uint64_t timestamp) {
66-
// TileVisibility::collectTileGarbage uses COW + Epoch, so it's safe to call concurrently
67-
for (uint64_t i = 0; i < tileCount; i++) {
68-
tileVisibilities[i].collectTileGarbage(timestamp);
65+
std::vector<uint64_t> RGVisibility<CAPACITY>::collectRGGarbage(uint64_t timestamp) {
66+
size_t totalWords = tileCount * BITMAP_SIZE_PER_TILE_VISIBILITY;
67+
std::vector<uint64_t> rgSnapshot(totalWords, 0);
68+
for (uint32_t t = 0; t < tileCount; t++) {
69+
tileVisibilities[t].collectTileGarbage(timestamp,
70+
rgSnapshot.data() + t * BITMAP_SIZE_PER_TILE_VISIBILITY);
6971
}
72+
return rgSnapshot;
7073
}
7174

7275
template<size_t CAPACITY>

cpp/pixels-retina/lib/RGVisibilityJni.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,20 @@ JNIEXPORT jlongArray JNICALL Java_io_pixelsdb_pixels_retina_RGVisibility_getVisi
129129
/*
130130
* Class: io_pixelsdb_pixels_retina_RGVisibility
131131
* Method: garbageCollect
132-
* Signature: (JJ)V
132+
* Signature: (JJ)[J
133133
*/
134-
JNIEXPORT void JNICALL Java_io_pixelsdb_pixels_retina_RGVisibility_garbageCollect
134+
JNIEXPORT jlongArray JNICALL Java_io_pixelsdb_pixels_retina_RGVisibility_garbageCollect
135135
(JNIEnv* env, jobject, jlong timestamp, jlong handle) {
136136
try {
137137
auto* rgVisibility = reinterpret_cast<RGVisibilityInstance*>(handle);
138-
rgVisibility->collectRGGarbage(timestamp);
138+
std::vector<uint64_t> snapshot = rgVisibility->collectRGGarbage(timestamp);
139+
jlongArray result = env->NewLongArray(snapshot.size());
140+
env->SetLongArrayRegion(result, 0, snapshot.size(),
141+
reinterpret_cast<const jlong*>(snapshot.data()));
142+
return result;
139143
} catch (const std::exception& e) {
140144
env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what());
145+
return nullptr;
141146
}
142147
}
143148

cpp/pixels-retina/lib/TileVisibility.cpp

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ void TileVisibility<CAPACITY>::getTileVisibilityBitmap(uint64_t ts, uint64_t* ou
238238
}
239239

240240
template<size_t CAPACITY>
241-
void TileVisibility<CAPACITY>::collectTileGarbage(uint64_t ts) {
241+
void TileVisibility<CAPACITY>::collectTileGarbage(uint64_t ts, uint64_t* gcSnapshotBitmap) {
242242
// Drain the pending retirement slot left by deleteTileRecord's empty-chain path.
243243
VersionedData<CAPACITY>* pending = pendingRetire.exchange(nullptr, std::memory_order_acquire);
244244
if (pending) {
@@ -248,7 +248,12 @@ void TileVisibility<CAPACITY>::collectTileGarbage(uint64_t ts) {
248248

249249
// Load old version
250250
VersionedData<CAPACITY>* oldVer = currentVersion.load(std::memory_order_acquire);
251-
if (ts <= oldVer->baseTimestamp) return;
251+
252+
// Early return A: safeGcTs <= baseTimestamp, nothing to compact
253+
if (ts <= oldVer->baseTimestamp) {
254+
std::memcpy(gcSnapshotBitmap, oldVer->baseBitmap, NUM_WORDS * sizeof(uint64_t));
255+
return;
256+
}
252257

253258
// Find the last block that should be compacted
254259
DeleteIndexBlock *blk = oldVer->head;
@@ -275,7 +280,22 @@ void TileVisibility<CAPACITY>::collectTileGarbage(uint64_t ts) {
275280
blk = blk->next.load(std::memory_order_acquire);
276281
}
277282

278-
if (!lastFullBlk) return;
283+
// Early return B: no compactable block
284+
if (!lastFullBlk) {
285+
std::memcpy(gcSnapshotBitmap, oldVer->baseBitmap, NUM_WORDS * sizeof(uint64_t));
286+
if (oldVer->head) {
287+
auto* tailSnap = tail.load(std::memory_order_acquire);
288+
size_t tailUsedSnap = tailUsed.load(std::memory_order_acquire);
289+
size_t cnt = (oldVer->head == tailSnap) ? tailUsedSnap : DeleteIndexBlock::BLOCK_CAPACITY;
290+
for (size_t i = 0; i < cnt; i++) {
291+
uint64_t item = oldVer->head->items[i];
292+
if (item == 0) break;
293+
if (extractTimestamp(item) <= ts) SET_BITMAP_BIT(gcSnapshotBitmap, extractRowId(item));
294+
else break;
295+
}
296+
}
297+
return;
298+
}
279299

280300
// Create new version with Copy-on-Write
281301
// Manually compute the new base bitmap from oldVer
@@ -304,8 +324,22 @@ void TileVisibility<CAPACITY>::collectTileGarbage(uint64_t ts) {
304324
blk = blk->next.load(std::memory_order_acquire);
305325
}
306326

307-
// Get new head and break the chain to avoid double-free
327+
// Compact path: build gcSnapshotBitmap by scanning the boundary block
308328
DeleteIndexBlock* newHead = lastFullBlk->next.load(std::memory_order_acquire);
329+
std::memcpy(gcSnapshotBitmap, newBaseBitmap, NUM_WORDS * sizeof(uint64_t));
330+
if (newHead) {
331+
auto* tailSnap = tail.load(std::memory_order_acquire);
332+
size_t tailUsedSnap = tailUsed.load(std::memory_order_acquire);
333+
size_t cnt = (newHead == tailSnap) ? tailUsedSnap : DeleteIndexBlock::BLOCK_CAPACITY;
334+
for (size_t i = 0; i < cnt; i++) {
335+
uint64_t item = newHead->items[i];
336+
if (item == 0) break;
337+
if (extractTimestamp(item) <= ts) SET_BITMAP_BIT(gcSnapshotBitmap, extractRowId(item));
338+
else break;
339+
}
340+
}
341+
342+
// Break the chain to avoid double-free
309343
lastFullBlk->next.store(nullptr, std::memory_order_release);
310344

311345
// Create new version with new head - this is the atomic COW update

0 commit comments

Comments
 (0)