infino 0.1.6

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

  # 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:
  # Default rerank codec for cosine vector columns. Non-cosine metrics
  # still use locally fitted sq8_residual (values are not bounded to
  # [-1, 1]). Override per column at table-create time when needed.
  #   sq8_fixed_residual — fixed absolute grid (default; portable bytes)
  #   sq8_residual       — per-cluster fitted quantizer
  #   fp32               — full-precision rerank
  #   rabitq_only        — no rerank column
  rerank_codec: sq8_fixed_residual

  # 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: ~

  # 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 compaction splits it into two. A cell this
  # size serves fine on its own (bench: ~500K docs/cell → recall 0.994, ~17 MiB
  # per query, since the per-cell fine IVF prunes within the cell), so the cap is
  # set high to keep the grid coarse and split-free at ≤10M (at 64 cells, cells
  # stay ~156K even at 10M). The split only engages at 100M/1B, where it bounds
  # cell size so build/query cost stays flat; lower this if higher-scale recall
  # needs a finer grid.
  cell_split_doc_cap: 500000

  # 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

  # 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.
  #
  # 0 disables the byte floor, so a cell consolidates on fragment count alone
  # (the >= 2 merge floor): drain generations collapse and post-compact cold
  # GET stays at the post-drain level. This is safe now that the post-compaction
  # cell split is disabled (see cell_split_doc_cap) — merging no longer triggers
  # the broken split path. Contrast the user table, which keeps an 80% floor to
  # amortize writes on its transient data.
  compaction_target_mb: 2048
  compaction_min_fill_percent: 0
  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