Skip to main content

Compact a bloated ChunkHound DuckDB index 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 and supported use case is shrinking a bloated ChunkHound index, whose drop-and-recreate HNSW churn (above its 50-row write-batch threshold) leaves large amounts of orphaned-but-counted blocks. The rebuild pipeline is structurally generic and works on other single-schema DuckDB files, but only ChunkHound-shaped inputs are promised: any shape outside that scope is refused at the front gate (see the Not Supported section below) rather than silently dropped or rebuilt with loss.

That bloat comes back as ChunkHound keeps indexing, so compaction is periodic maintenance rather than a one-time cleanup.

⚡ 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: target resolves to the same path as source, the FK graph has a cycle, or the source has a shape refused at the front gate (see the Not Supported section below).
  • FileNotFoundError: source does not exist.
  • FileExistsError: target already exists.
  • RuntimeError: the bundled vss extension binary cannot be located (only reachable if the source contains an HNSW index).

restore_indexes() raises:

  • FileNotFoundError: database does not exist.
  • ValueError: database has no _compactor_hnsw_recipe table (not a --skip-hnsw artifact).
  • RuntimeError: the bundled vss extension binary cannot be located.

replace_with_compacted() raises:

  • FileNotFoundError: source or compacted is missing.
  • FileExistsError: <source>.bak already exists (it refuses to overwrite an existing backup).
  • OSError: the move from compacted to source fails even via the cross-filesystem fallback (shutil.move).

🚫 Not Supported

The tool fails hard rather than silently dropping anything it cannot reproduce.

  • Non-main schemas and views (raise ValueError).
  • User-defined types, generated columns, self-referential foreign keys, and HNSW indexes on non-bare-column expressions (raise ValueError).
  • 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 the vss defaults.
  • Table and column comments are not carried across the rebuild.

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/skills it 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

chunkhound_index_compactor-0.2.0.tar.gz (57.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

chunkhound_index_compactor-0.2.0-py3-none-any.whl (13.8 kB view details)

Uploaded Python 3

File details

Details for the file chunkhound_index_compactor-0.2.0.tar.gz.

File metadata

File hashes

Hashes for chunkhound_index_compactor-0.2.0.tar.gz
Algorithm Hash digest
SHA256 8a03ef1694902381471e5c6396b7a6d856178393b58872ace10ec9a5575e928f
MD5 323b5c1c2499e4286914c439cb99b012
BLAKE2b-256 8e944d2c1f07f526bcb2c8e70bfbe6655e8ef01e129b714f0ea171de1c79eb4f

See more details on using hashes here.

Provenance

The following attestation bundles were made for chunkhound_index_compactor-0.2.0.tar.gz:

Publisher: release.yml on it-bens/chunkhound-index-compactor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file chunkhound_index_compactor-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for chunkhound_index_compactor-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 63a25b1219a368458c2244224e332ac3461c80af9d25d8d5c03f08c15216ef4c
MD5 2eb6d08f5b557e9439cde3fab37114f5
BLAKE2b-256 2818c3150d7c3291cdd7ef9602b3ad6f3aa17c792bcd19856ae5bfc64f79295a

See more details on using hashes here.

Provenance

The following attestation bundles were made for chunkhound_index_compactor-0.2.0-py3-none-any.whl:

Publisher: release.yml on it-bens/chunkhound-index-compactor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page