Compact a DuckDB database (ChunkHound index or otherwise) by rebuilding it into a fresh file
Project description
ChunkHound Index Compactor
Compact a DuckDB database by rebuilding it into a fresh file. The motivating use case was shrinking a bloated ChunkHound index (whose per-batch HNSW re-serialization leaves large amounts of orphaned-but-counted blocks), but the implementation is fully generic; it works on any single-schema DuckDB file.
⚡ Quick Start
uvx chunkhound-index-compactor path/to/db.duckdb
# writes path/to/db.duckdb.compacted
uvx chunkhound-index-compactor path/to/db.duckdb --replace
# swaps in the compacted copy and keeps the original at path/to/db.duckdb.bak
uvx chunkhound-index-compactor path/to/db.duckdb --skip-hnsw
# skips rebuilding vector indexes (RAM-flat, smallest output); restore them later
uvx chunkhound-index-compactor restore path/to/db.duckdb.compacted
The source is opened read-only, but an active writer holds the file lock. Close any process writing to the database before running.
🖥️ CLI Usage
$ chunkhound-index-compactor --help
Usage: chunkhound-index-compactor [OPTIONS] COMMAND [ARGS]...
Commands:
compact Compact a DuckDB database by rebuilding it into a fresh file. (default)
restore Rebuild HNSW vector indexes in a --skip-hnsw artifact, in place.
A bare invocation routes to compact, so chunkhound-index-compactor SOURCE still works:
chunkhound-index-compactor SOURCE [TARGET] [--replace] [--skip-hnsw]
chunkhound-index-compactor restore DATABASE
| Argument / Option | Meaning |
|---|---|
SOURCE |
Path to the existing DuckDB file (required) |
TARGET |
Path for the compacted output [default: <source>.compacted] |
--replace |
After success, replace source with the compacted file (original → <source>.bak) |
--skip-hnsw |
Do not rebuild vector indexes; write a recipe table for later restore |
With --skip-hnsw, the output has no vector index and falls back to a brute-force scan (correct, just unaccelerated) until you run restore. Rebuilding the HNSW is the memory-dominant step, so --skip-hnsw lets you compact on a small machine and restore on a RAM-capable one. See docs/benchmarks.md for peak-RAM numbers and docs/architecture.md §RAM cost asymmetry for why.
🐍 Library Usage
from pathlib import Path
from chunkhound_index_compactor import compact_database, restore_indexes, replace_with_compacted
result = compact_database(Path("big.duckdb"), Path("small.duckdb"))
print(f"{result.source_size} -> {result.target_size} ({result.delta_pct:+.1f}%)")
# Small-RAM path: skip the vector index, restore it later on a bigger machine.
compact_database(Path("big.duckdb"), Path("small.duckdb"), skip_hnsw=True)
restored = restore_indexes(Path("small.duckdb"))
print(f"restored: {restored.restored}")
# Optional: swap in place with .bak backup
backup = replace_with_compacted(result.source, result.target)
compact_database() raises:
ValueError:targetresolves to the same path assource, the source has a non-mainschema or a view, or the FK graph has a cycle.FileNotFoundError:sourcedoes not exist.FileExistsError:targetalready exists.
restore_indexes() raises:
FileNotFoundError:databasedoes not exist.ValueError:databasehas no_compactor_hnsw_recipetable (not a--skip-hnswartifact).
replace_with_compacted() raises FileExistsError if <source>.bak already exists. It refuses to overwrite an existing backup.
🚫 Not Supported
The tool fails hard rather than silently dropping anything it cannot reproduce:
- Non-
mainschemas and views (raiseValueError). - Foreign-key cycles among tables (raise
ValueError). - HNSW tuning parameters other than
metric(M,M0,ef_construction,ef_search); they are not recoverable from a built index and are rebuilt at thevssdefaults.
See docs/architecture.md for the reasoning, and docs/out-of-scope.md for approaches considered and not pursued.
🏗️ Development
Setup, local checks, CI, and release process: CONTRIBUTING.md.
⚖️ License
MIT
[!NOTE] Yes, an AI wrote this README. And the code, the docs, the tests, and the
.claude/skillsit now uses to write the next round. Yes, a human told it to keep the emojis. The human has ADHD, which, as it turns out, means his brain was already doing attention re-routing and context-window thrashing before LLMs made it cool. They call him ... LLMartin. The emojis are a feature.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file chunkhound_index_compactor-0.1.1.tar.gz.
File metadata
- Download URL: chunkhound_index_compactor-0.1.1.tar.gz
- Upload date:
- Size: 54.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e52a837ae792c973e460b4732663acd9b373046f170c9be76889f02017800873
|
|
| MD5 |
0be2c3f52c2731f18189c71c0eed9764
|
|
| BLAKE2b-256 |
33cf531f8a369a3d8b4400ba6270ca78bbc4fdc32aba808fc0fd883a1dbd9340
|
Provenance
The following attestation bundles were made for chunkhound_index_compactor-0.1.1.tar.gz:
Publisher:
release.yml on it-bens/chunkhound-index-compactor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chunkhound_index_compactor-0.1.1.tar.gz -
Subject digest:
e52a837ae792c973e460b4732663acd9b373046f170c9be76889f02017800873 - Sigstore transparency entry: 1587123970
- Sigstore integration time:
-
Permalink:
it-bens/chunkhound-index-compactor@8dd1a1d807340b231b1bfd6547c57264d66033f8 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/it-bens
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8dd1a1d807340b231b1bfd6547c57264d66033f8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file chunkhound_index_compactor-0.1.1-py3-none-any.whl.
File metadata
- Download URL: chunkhound_index_compactor-0.1.1-py3-none-any.whl
- Upload date:
- Size: 11.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48ea1414a1d28b69f8ef6c8248ea7903031382c615a8c711168af7035b8ab779
|
|
| MD5 |
cc09cec491b01a34bffa759eed34cdc2
|
|
| BLAKE2b-256 |
42e192fa9270689e11d7145668eee8b93fda705767698df2d59a1658bcac1387
|
Provenance
The following attestation bundles were made for chunkhound_index_compactor-0.1.1-py3-none-any.whl:
Publisher:
release.yml on it-bens/chunkhound-index-compactor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chunkhound_index_compactor-0.1.1-py3-none-any.whl -
Subject digest:
48ea1414a1d28b69f8ef6c8248ea7903031382c615a8c711168af7035b8ab779 - Sigstore transparency entry: 1587124152
- Sigstore integration time:
-
Permalink:
it-bens/chunkhound-index-compactor@8dd1a1d807340b231b1bfd6547c57264d66033f8 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/it-bens
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8dd1a1d807340b231b1bfd6547c57264d66033f8 -
Trigger Event:
push
-
Statement type: