infino 0.5.2

A fast retrieval engine that stores data on object storage and runs SQL, full-text search, and vector search over it from a single system — search-on-Parquet.
Documentation
# infino default configuration.
#
# This file is compiled into the binary via include_str! and acts as
# the floor of the config stack. Override at runtime via (in order of
# increasing precedence):
#
#   1. /etc/infino/config.yaml             (system-wide)
#   2. $XDG_CONFIG_HOME/infino/config.yaml (user; falls back to
#                                            $HOME/.config/infino/...)
#   3. ./infino.yaml                       (per-project / per-cwd)
#
# Each layer is a partial override — keys not set in a higher layer
# fall through to lower layers.
#
# Environment variables never override this configuration: engine
# behavior is set in YAML only, so a run's effective config is
# readable from files instead of reconstructed from process env.

# Supertable runtime knobs. Reader fan-out (skip + per-superfile
# search + top-k merge) and writer commit-time rayon-shard run on
# separate pools so a long-running commit can't spike reader p99 or
# vice versa.
#
# Thread-count values:
#   - `auto` — resolves at runtime to num_cpus for reader_threads
#     and max(1, num_cpus / 2) for writer_threads. Hardware-portable
#     default; the same shipped config works on a 4-core dev laptop
#     and a 96-core production node.
#   - a positive integer — explicit override. Use when you've
#     measured a workload-specific tuning.
supertable:
  reader_threads: auto
  writer_threads: auto

  # Name of the system-managed primary-key column the supertable
  # injects on every append(). The column type is fixed by the
  # supertable layer; only the name is configurable. Leading
  # underscore signals a system-owned field. Change this if
  # `_id` collides with a business field name; otherwise leave
  # as default.
  id_column: _id

  # Threshold above which the supertable's writer triggers an
  # internal commit() to flush the in-memory buffer to disk-
  # equivalent (one superfile per writer-pool thread). Specified
  # in mebibytes (1 MiB = 1024 × 1024 bytes). Set 0 to disable
  # auto-flush — only caller-driven commit() will produce
  # superfiles.
  commit_threshold_size_mb: 1024

  # Verify the trailing whole-blob CRC and per-subsection CRCs
  # on every SuperfileReader::open. Defaults to true. Set to
  # false only when the underlying storage already validates
  # checksums (content-addressed object store, ZFS, etc.) —
  # skipping the scan trades that storage-layer guarantee for
  # faster cold opens.
  verify_crc_on_open: true

# Persistent storage wiring. Defaults to in-memory-only. Set
# `backend: s3` plus `bucket` / `prefix` to make
# SupertableOptions::apply_config attach an S3StorageProvider.
# `backend: azure` attaches an AzureStorageProvider, with `bucket`
# naming the Azure container. `backend: gcs` attaches a
# GcsStorageProvider, with `bucket` naming the GCS bucket and
# `storage_options` carrying `google_*` config keys.
storage:
  backend: none
  local_root:
  bucket:
  prefix: ""

  # Object-store credentials and tuning, keyed by object_store's config
  # strings. Infino reads no credentials from the environment — set them
  # here (or via ConnectOptions). Empty → ambient cloud identity (IAM
  # instance role / managed identity). Examples:
  #   backend: s3
  #   storage_options:
  #     aws_access_key_id: ...
  #     aws_secret_access_key: ...
  #     aws_region: us-east-1
  #     # aws_endpoint: http://localhost:9000   # MinIO / R2 / Ceph
  #   backend: azure   # `bucket` names the container
  #   storage_options:
  #     azure_storage_account_name: ...
  #     azure_storage_account_key: ...
  storage_options: {}

  # When set, storage-backed reads use DiskCacheStore. For S3,
  # lazy_foreground_with_background_fill is the object-store-native
  # cold path: foreground opens/searches with exact range GETs (or zero
  # superfile GETs at open when manifest open-batch bytes are present)
  # while background fill promotes the full superfile to mmap.
  disk_cache_root:
  disk_budget_bytes: 10737418240
  # Byte budget for the content-addressed manifest-part cache, held in a
  # manifest-parts/ subdirectory of disk_cache_root. On a hit the part
  # loader reads bytes from local disk instead of object storage; parts
  # are content-addressed, so cached files are never stale and survive
  # restarts. Independent of disk_budget_bytes. Default 2 GiB.
  manifest_disk_budget_bytes: 2147483648
  cold_fetch_mode: lazy_foreground_with_background_fill
  cold_fetch_streams: 8
  cold_fetch_chunk_bytes: 4194304
  mmap_cold_threshold_secs: 300
  mmap_sweep_interval_secs: 75

# Compaction merges the small superfiles produced by individual
# commits into one target-sized superfile, cutting query fan-out.
# Sizes are in mb (1 MiB = 1024 × 1024 bytes).
compaction:
  # Size a compacted output aims for.
  target_superfile_size_mb: 1024

  # Minimum estimated live bytes to trigger a merge,
  # as a percentage of `target_superfile_size_mb`.
  # E.g. at 80% with a 1 GiB target, two 200 MiB superfiles (400 MiB) do not compact.
  min_fill_percent: 80

  # Fragment-count merge trigger: merge once a partition has this many
  # sub-target superfiles, even below the size floor. Fires on size OR count,
  # so many tiny appends consolidate without lowering the floor. Min 2.
  min_superfiles_for_merge: 50

  # Maximum memory budget for materializing inputs during a single merge, in MiB.
  max_memory_mb: 3072

# Vector-index build / search / drain tuning. Every value below
# defaults to the engine's built-in behavior — a fresh install never
# needs to touch this section. These are the levers that used to be
# one-off INFINO_* env vars; they are YAML-only now.
vector:
  # Absolute cap on fine IVF centroids probed per vector search.
  # Empty (~) derives the budget from nprobe × eligible superfiles at
  # query time; set an integer to force exactly that many.
  inner_budget: ~

  # Per-cell fine-probe floor: the minimum fine IVF clusters probed inside
  # each selected cell. The user-table routing default takes this; the
  # hidden index's per-table stamp in the manifest overrides it. Pairs with
  # fine_nprobe_pct as max(floor, floor(pct × cell fine-cluster count)).
  fine_nprobe_floor: 4

  # Proportional fine-probe fraction for UNFILTERED vector search: probe
  # floor(pct × the cell's fine-cluster count), so depth tracks cell size.
  # 0.0 turns the proportional depth off (fixed floor); > 0 opts in — the
  # recall lever for large cells. Filtered queries ignore this.
  fine_nprobe_pct: 0.0

  # Serve-time near-tie window on the exact-fine cell ranking, law-served
  # default path only (#515): selection follows the ranking past the stamped
  # width while each next cell's exact fine score stays within this relative
  # window of the winner's. Decisive geometry cliffs below any sane value and
  # serves identically; flat-scored corpora follow their own evidence. Default
  # is the measured real-query truth-cell slack p99.
  serve_near_tie_slack: 0.30

  # K-means training points per centroid for the drain's per-cell
  # sub-builds.
  kmeans_pts_per_centroid: 64

  # Doc count above which a merged cell superfile is split into two
  # sub-cells during hidden-index maintenance.
  #
  # Max docs in a global cell before it is split (hard ceiling). With the
  # modality trigger on by default (cell_split_modality_d > 0) this cap is the
  # upper backstop; set cell_split_modality_d = 0 to make the cap the sole
  # trigger.
  #
  # 500K, deliberately large. A brief experiment lowered this to 40K (≈ the size
  # 256 cells reach at 10M) on the theory that cells must be kept small — that was
  # wrong. On a synthetic multi-modal stress corpus at nprobe=1, native no-split
  # cells at 100M/256 (~390K docs/cell) hold ~0.98 recall@10 — stable, not
  # decaying — so the split is NOT needed to keep recall from cratering at these
  # sizes; forcing a split down to 40K instead over-fragments the grid under
  # streaming (cell count runs away → boundary loss → recall craters). The large
  # cap therefore makes the default path native-ish: it splits only genuinely
  # huge cells (100M/256 = 390K < 500K → no split; ~1B/256 = 3.9M → splits).
  # ~0.98 here is the nprobe=1 floor on this stress corpus, not a target — to lift
  # it, raise fine_nprobe_pct (within-cell depth) or enable cell_split_modality_d
  # (finer grid, ~0.99 measured). Lower this cap only if a measured within-cell
  # ceiling shows up at some large docs/cell.
  cell_split_doc_cap: 500000

  # Ashman-D threshold for a modality-driven split. Spread measured on the axis
  # between a tentative two-means partition's centroids (so it isn't diluted by
  # the ~1023 dimensions the split doesn't separate). A k=2 split of a single mode
  # scores ~3 (half-normal baseline); two separated modes score in the
  # tens-to-hundreds. With this > 0, cell_split_doc_cap becomes a hard ceiling and
  # cells track natural cluster boundaries: the recursion counts modes K, a cell
  # with K > MODALITY_MODES_PER_CELL (=4) splits into ceil(K/4) children, and the
  # executor self-tunes k up for route fidelity.
  #
  # 8.0 (ON) is the default. The modality split lifts nprobe=1 recall above the
  # fixed 256-cell grid's ~0.98 floor where the grid is too coarse — most clearly
  # at small scale (~0.998 vs ~0.94 at 100K, ~0.997 vs ~0.962 at 1M) and slightly
  # ahead at scale (0.991 vs 0.983 at 10M). The finer grid (~14x the cells) adds
  # per-cell drain generations, but compaction consolidates them
  # (compaction_min_superfiles_for_merge = 2), so post-compact cold GET stays bounded.
  # Set 0.0 to fall back to the fixed-grid path (cap trigger only).
  cell_split_modality_d: 8.0

  # How user-superfile clusters align to the global cell grid:
  #   local  — per-superfile k-means (default).
  #   global — cluster c == cell c, so the drain routes cluster → cell
  #            without re-scoring.
  user_centroids: local

  # User superfiles the hidden-index drain materializes per batch
  # before publishing that batch's cell superfiles (bounds drain RAM
  # to O(batch)). -1 = unbounded (one merge, O(corpus) RAM);
  # 0 = skip the drain entirely.
  drain_batch_superfiles: 64

  # Target storage amplification for boundary-only replication (shared
  # by commit and drain). <= 1.0 disables replication (the default).
  # Replication was measured a net loss at 10M: its extra boundary copies
  # inflated cell size (159K -> 232K rows/cell), which crowded the RaBitQ
  # shortlist and displaced true neighbors before rerank — dropping
  # post-drain recall 0.997 -> 0.975 while adding ~50% storage and ~35%
  # more GETs/query. Grid+fine union routing carries boundary coverage
  # instead. Raise above 1.0 only with evidence it helps at your scale.
  drain_replica_target_factor: 1.0

  # Per-cell consolidation op the drain applies:
  #   kmeans — re-cluster each cell's rows (default).
  #   splice — keep each superfile's local clusters verbatim.
  drain_consolidate: kmeans

  # Read fan-out for the drain's superfile opens. `auto` resolves to
  # one in-flight read per hardware thread, floored at the
  # background-fill default and capped at 64; or set a positive
  # integer.
  drain_read_concurrency: auto

  # CPU threads for maintenance-compaction compute (cell splits'
  # k-means and child builds, probe-law recalibration) — the `optimize()` path; the
  # ingest commit path does not ride this pool. `auto` = all hardware
  # threads (an explicit optimize owns its machine). Set an integer to
  # cap it when optimize runs concurrently with latency-critical
  # foreground work.
  maintenance_threads: auto

  # Cell count for the USER table's grid, trained at the first commit —
  # controls user-superfile cell packing and pre-drain query routing. Finer
  # than the hidden grid on purpose: smaller cells make the single pre-drain
  # probe more precise and cheaper. Stamped at create; changing it later
  # affects new tables only.
  user_cell_count: 256

  # Cell count for the HIDDEN vector index grid, trained at the same first
  # commit. The drain reads this grid verbatim; post-drain routing runs at
  # this granularity. Equal to user_cell_count: one 256-cell grid drives
  # packing, pre-drain routing, the drain, and post-drain routing (user_grid
  # degenerates to None when the counts match).
  hidden_cell_count: 256

  # Hidden vector-index compaction (distinct from the top-level
  # `compaction:` section, which governs the user table). A packed cell
  # shard stays a merge candidate until it reaches compaction_target_mb,
  # so incremental deltas are absorbed into it instead of being sealed
  # off as separate fragments. compaction_max_memory_mb caps the input
  # bytes packed into one merge pass and must stay >= the target, or the
  # target is never reached.
  #
  # The hidden index has no byte floor of its own: it derives min_fill_percent
  # from the top-level `compaction:` section, and compaction_min_superfiles_for_merge
  # (2 by default) dominates it — a cell consolidates on any two shards, so
  # drain generations collapse and post-compact cold GET stays at the
  # post-drain level. Safe because the cell split is now the in-place
  # append-child split (validated), so a merge that pushes a cell over the cap
  # (or into multimodality with the trigger on) splits it correctly rather than
  # hitting the old broken post-merge split path.
  compaction_target_mb: 2048
  compaction_min_superfiles_for_merge: 2
  compaction_max_memory_mb: 4096

# Diagnostic and hardware-capability toggles. Each gates
# instrumentation or forces a slower code path for A/B measurement;
# none of them change query results. Leave everything off unless you
# are profiling or benchmarking.
diagnostics:
  # Accumulate per-phase timers during the vector drain build.
  drain_build_timers: false

  # Emit the FTS builder's finish-phase profile.
  fts_profile: false

  # Capture the object-store I/O timeline.
  io_timeline: false

  # Force the AVX2 vector-distance path even where AVX-512 is available.
  disable_avx512: false

  # Force the scalar vector-distance path even where AVX2 is available.
  disable_avx2: false

  # Skip the disk cache's lazy background fill so foreground-only read
  # behavior can be measured.
  disable_background_fill: false

# Per-connection memory budget: a ceiling on the anonymous heap the query
# and ingest paths allocate (result batches, the vector shortlist, ingest
# buffers), so one connection can't grow memory until the process is OOM-
# killed. The memory-mapped superfiles and the disk cache are bounded
# separately; this bounds heap only.
#
# Applies to connections built from this config file. Code that opens a
# connection programmatically sets the budget on ConnectOptions
# (with_connection_memory_budget_bytes) instead.
memory:
  # Bytes. 0 (default) is measure-only: usage is tracked but never refused.
  # A positive value enforces the ceiling (the engine reserves at 90% of it,
  # leaving headroom for small untracked allocations).
  connection_budget_bytes: 0