| 514 | } |
| 515 | |
| 516 | void CSRNodeGroup::checkpointInMemAndOnDisk(const UniqLock& lock, NodeGroupCheckpointState& state) { |
| 517 | // TODO(Guodong): Should skip early here if no changes in the node group, so we avoid scanning |
| 518 | // the csr header. Case: No insertions/deletions in persistent chunk and no in-mem chunks. |
| 519 | auto& csrState = state.cast<CSRNodeGroupCheckpointState>(); |
| 520 | // Scan old csr header from disk and construct new csr header. |
| 521 | persistentChunkGroup->cast<ChunkedCSRNodeGroup>().scanCSRHeader(*state.mm, csrState); |
| 522 | csrState.newHeader = |
| 523 | std::make_unique<InMemChunkedCSRHeader>(*state.mm, false, StorageConfig::NODE_GROUP_SIZE); |
| 524 | // TODO(Guodong): Find max node offset in the node group. |
| 525 | csrState.newHeader->setNumValues(StorageConfig::NODE_GROUP_SIZE); |
| 526 | csrState.newHeader->copyFrom(*csrState.oldHeader); |
| 527 | auto leafRegions = collectLeafRegionsAndCSRLength(lock, csrState); |
| 528 | DASSERT(std::is_sorted(leafRegions.begin(), leafRegions.end(), |
| 529 | [](const auto& a, const auto& b) { return a.regionIdx < b.regionIdx; })); |
| 530 | const auto regionsToCheckpoint = mergeRegionsToCheckpoint(csrState, leafRegions); |
| 531 | if (regionsToCheckpoint.empty()) { |
| 532 | // No csr regions need to be checkpointed, meaning nothing is updated or deleted. |
| 533 | // We should reset the version and update info of the persistent chunked group. |
| 534 | persistentChunkGroup->resetVersionAndUpdateInfo(); |
| 535 | if (csrState.columnIDs.size() != persistentChunkGroup->getNumColumns()) { |
| 536 | // The column set of the node group has changed. We need to re-create the persistent |
| 537 | // chunked group. |
| 538 | persistentChunkGroup = createNewPersistentChunkGroup( |
| 539 | persistentChunkGroup->cast<ChunkedCSRNodeGroup>(), csrState); |
| 540 | } |
| 541 | return; |
| 542 | } |
| 543 | if (regionsToCheckpoint.size() == 1 && |
| 544 | regionsToCheckpoint[0].level > DEFAULT_PACKED_CSR_INFO.calibratorTreeHeight) { |
| 545 | // Need to re-distribute all CSR regions in the node group. |
| 546 | redistributeCSRRegions(csrState, leafRegions); |
| 547 | } else { |
| 548 | for (auto& region : regionsToCheckpoint) { |
| 549 | csrState.newHeader->populateRegionCSROffsets(region, *csrState.oldHeader); |
| 550 | // The left node offset of a region should always maintain stable across length and |
| 551 | // offset changes. |
| 552 | DASSERT(csrState.oldHeader->getStartCSROffset(region.leftNodeOffset) == |
| 553 | csrState.newHeader->getStartCSROffset(region.leftNodeOffset)); |
| 554 | } |
| 555 | } |
| 556 | |
| 557 | // Count tuples across ALL node offsets, not just the regions being checkpointed. |
| 558 | // A region may have no changes (so it is not in regionsToCheckpoint) yet still hold tuples. |
| 559 | // If we only count tuples in regionsToCheckpoint we may incorrectly conclude that the node |
| 560 | // group is empty and set persistentChunkGroup to nullptr, losing those untouched tuples. |
| 561 | uint64_t numTuplesAfterCheckpoint = 0; |
| 562 | const auto numNodeOffsets = csrState.newHeader->length->getNumValues(); |
| 563 | for (auto i = 0u; i < numNodeOffsets; ++i) { |
| 564 | numTuplesAfterCheckpoint += csrState.newHeader->getCSRLength(i); |
| 565 | } |
| 566 | if (numTuplesAfterCheckpoint == 0) { |
| 567 | reclaimStorage(csrState.pageAllocator, lock); |
| 568 | persistentChunkGroup = nullptr; |
| 569 | } else { |
| 570 | DASSERT(csrState.newHeader->sanityCheck()); |
| 571 | for (const auto columnID : csrState.columnIDs) { |
| 572 | checkpointColumn(lock, columnID, csrState, regionsToCheckpoint); |
| 573 | } |
nothing calls this directly
no test coverage detected