Skip to content

Fix ice-disk table scans#491

Open
aheev wants to merge 9 commits into
LadybugDB:mainfrom
aheev:fix-icedisk-scans
Open

Fix ice-disk table scans#491
aheev wants to merge 9 commits into
LadybugDB:mainfrom
aheev:fix-icedisk-scans

Conversation

@aheev
Copy link
Copy Markdown
Contributor

@aheev aheev commented May 15, 2026

  • fixed nodeID offset in node table scan by calculating calc global row offset using parquet metadata
  • fixed early break issue in rel table scans by refactoring ice-disk internal scan to full table based rather than rowGroup based
  • enumized STORAGE_FORMAT

context: #476 (review)

@aheev
Copy link
Copy Markdown
Contributor Author

aheev commented May 15, 2026

@adsharma could you PTAL?

Re: duplicate boundNodes in unordered_map

Two cases:

 1. Source mode (MATCH (a:user)-[:follows]->(b) — direct node scan child): fetchNextBoundNodeBatch generates unique sequential offsets [nextOffset, nextOffset+N). No duplicates by construction.
 2. Non-source mode (multi-hop (a)-[r1]->(b)-[r2]->(c)):
 - r1's scan processes one source node a at a time (the break when boundOffset != activeBoundOffset)
 - So each call to r1.getNextTuple produces neighbors of exactly one a
 - A single source node's neighbor list has no duplicates in a well-formed CSR file
 - IceDisk node table emits 1 node per scan call. Even if it emits in a batch they would be distinct
 - Therefore r2's bound node vector always has distinct b values in each batch

@aheev
Copy link
Copy Markdown
Contributor Author

aheev commented May 15, 2026

dataset PR: LadybugDB/dataset#3

@aheev
Copy link
Copy Markdown
Contributor Author

aheev commented May 16, 2026

@adsharma should we add a get_icebug_disk_supported_version CALL?

@adsharma
Copy link
Copy Markdown
Contributor

We already have db_version() and storage_version(). Users can detect if icebug-disk is supported by trying ATTACH.

std::string primaryKeyName;
std::string storage;
std::string storageFormat;
common::StorageFormat storageFormat = common::StorageFormat::NONE;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a backward incompatible change. But since we don't have any known prod usage of previous string storage format, I think we can survive without a storage version bump

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this fails to open a version 41 database:

./build/release/tools/shell/lbug -r /tmp/test1-save.db
Terminate called after throwing an instance of std::bad_alloc: std::bad_alloc
<while deserializing catalog entry>

Either revert or implement in-place upgrade.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it created with the bin of this branch?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dbs created from 0.16.0 and from a bin from main (without this PR) will have the same outcome.

The way kuzu dealt with it was to ask users to export/import db after every release. Since we went several releases with version 40 and backward compat, this would be a good contract to maintain going forward.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loads up fine for me with both 0.16.1 and 0.15.3. I haven't removed upgradeLegacyStorageFormat from my previous PR

Comment thread src/storage/table/ice_disk_rel_table.cpp
}

// Load shared indptr data - thread-safe to read
if (!indptrFilePath.empty()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard was significant?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indptr and indices path validation is done during table creation phase

// calc current global row index based on assigned row group and local row index within that
// group
auto metadata = iceDiskScanState.parquetReader->getMetadata();
offset_t startOffset = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startOffset for a given nodeGroupIdx is constant?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startOffset for a nodeGroupIdx(rowGroup) is calc just below. We can avoid this repeated calc by populating startOffsets in initGlobalStateInternal. I will add it in refactor post release. Keeping changes minimal right now

Comment thread src/processor/operator/scan/scan_rel_table.cpp Outdated
Comment thread docs/icebug-disk.md

File paths can be relative or absolute and are resolved as `<path-to-dir>/nodes_{tableName}.parquet` for node tables, and `<path-to-dir>/indices_{tableName}.parquet` and `<path-to-dir>/indptr_{tableName}.parquet` for relationship tables.

Object-store URIs (e.g. `s3://bucket/path`, `https://host/path`) are supported as `storage` values and are passed through unchanged.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why drop this? Should still hold afterwards?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can add it later once we support URIs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The VFS has supported URIs for a long time. We haven't touched that code, but we haven't verified them either (because all the s3/httpfs assets were not accessible after kuzu archiving).

I'm sending a small PR to thread the shell through VFS and change path mangling to restore URIs. Fortunately most of it seems to be intact and functional.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you saying parquet_reader code would work with URIs too?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - tested via tooling in #493. Same result local file or http:// url.

s3:// should work too once we figure out ordering of lbug -i processing vs cred handling.

Comment thread src/common/enums/storage_format.cpp Outdated

// Create DataChunk matching the indices parquet file schema
auto numIndicesColumns = indicesReader->getNumColumns();
cachedBatchData = std::make_unique<DataChunk>(numIndicesColumns);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these allocations be done once on reset() and reused?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DataChunk doesn't offer a reset out of the box. All it offers is resetAuxiliaryBuffer. We need to manually reset state in DataChunk and other state objects in ValueVectors which requires tinkering with ParquetReader and/or ValueVector. Maybe refactor it later?

for (uint32_t colIdx = 0; colIdx < numIndicesColumns; ++colIdx) {
const auto& columnTypeRef = indicesReader->getColumnType(colIdx);
auto columnType = columnTypeRef.copy();
auto vector = std::make_shared<ValueVector>(std::move(columnType), memoryManager);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

@aheev
Copy link
Copy Markdown
Contributor Author

aheev commented May 17, 2026

new dataset PR: LadybugDB/dataset#4

@aheev aheev requested a review from adsharma May 17, 2026 05:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants