Summa Server

gRPC search, indexing, and maintenance for multiple local indexes.

Run

cargo install summa-server
summa-server --addr 127.0.0.1:50051 --data-dir ./data
summa-server --help

Defaults: 0.0.0.0:50051, data directory ./data. See Python and TypeScript for client examples, metrics for monitoring, and the broker for sharding.

Operations

Search resource controls

--search-threads bounds the shared CPU pool, including nested and cross-index work. Default: detected CPUs / 4, minimum 1.

--max-concurrent-searches bounds admitted search pipelines. Default: CPUs / 8, clamped to 1–8. Overload fails immediately with RESOURCE_EXHAUSTED; retry with bounded backoff. Completion/cancellation releases admission. Document lookup and metadata RPCs do not consume search permits.

Request limits:

Request component Limit
Final search results 10000
Pagination window (offset + limit) 50000
L1 reranker candidates 50000
Fusion candidates fetched per sub-query 50000
Fusion sub-queries 16
Fusion fetch depth x number of sub-queries 200000
Query nesting depth 32
Query nodes / aggregate clauses 256 / 512
Clauses in one Boolean query 128
Aggregate query text 64 KiB
Aggregate query vector payload 1 MiB
Dense dimensions / sparse input dimensions 65536 / 4096
Binary query bytes 256 KiB
Stored fields requested 64
Aggregate requested-field name bytes 16 KiB
Retained response / encoded response (each) 48 MiB

Zero-valued defaults remain supported. Derived reranker and fusion defaults are checked and capped at the corresponding limit; explicit values over a limit return gRPC INVALID_ARGUMENT.

The structural limits are checked iteratively before query conversion and before a search permit is acquired. Requested stored fields are resolved and deduplicated once, and response hydration is charged before field values are cloned into protobuf objects. A response that would exceed its memory/encoding budget fails with RESOURCE_EXHAUSTED; request fewer hits or fields.

Commit completion and timeouts

Once a Commit acquires the index writer, the server owns its completion even if the client disconnects or its RPC deadline expires. A slow worker flush is reported every five minutes and automatically retried against the same paused generation; it never resumes workers early or publishes only the fast workers. Concurrent commits serialize on that writer. Cancellation while waiting to acquire the writer does not start a commit.

A client deadline is therefore an unknown outcome, not an abort. Retry Commit to observe completion. Publication/build errors remain errors and are logged even if the original client is gone; only worker-flush timeouts are retried automatically. Backpressure messages distinguish a full queue from a paused commit. Graceful shutdown waits for an accepted commit to finish, but a forced process kill cannot preserve that guarantee: allow sufficient termination grace and stop ingestion/commit outstanding work before a deployment restart.

Background merge and reorder

The server uses one BP CPU pool and one whole-pass gate across all indexes. These are deliberately separate controls:

Option Default Meaning
--optimizer-threads 0 Shared BP threads; 0 disables periodic scans. Manual/merge BP uses the fallback pool.
--optimizer-concurrent-passes 2 Shared whole-pass limit, clamped to 1–2; automatic merges use at most one slot.
--optimizer-scan-interval-secs 60 Background scan interval.
--optimizer-large-segment-docs 5000000 Large-segment threshold for budgeted first passes.
--optimizer-time-budget-secs 600 Large-segment pass time budget.
--optimizer-partial-min-partition-docs 256 Initial partition floor for large segments.
--optimizer-unconverged-cooldown-secs 600 Completion-to-retry delay for deepening.
--optimizer-max-unconverged-passes 3 Pass limit per truncated lineage, including the first pass; 0 disables follow-up.
--merge-bp-budget-secs 600 Merge BP time budget; 0 means unbudgeted.
--bp-memory-budget-mb 24576 Per-pass scratch bound, not reserved memory or a process RSS cap.

Each pass shares the CPU pool but has its own memory budget. Budget for concurrent-passes * bp-memory-budget, plus readers, indexing, merge state, and page-cache residency. Over-budget BP uses blockwise reorder or a bounded graph and reports incomplete convergence; stored postings are never truncated. Force merge pauses new background BP and reserves foreground capacity after existing merges drain.

Merge failures use exponential retry backoff (30 seconds through 30 minutes). A deterministic missing/corrupt source is quarantined for the process lifetime so the same candidate cannot consume all cores in an immediate loop. The metadata entry remains visible—Summa never silently removes documents. To explicitly remove corrupt entries and their files, stop normal traffic and run:

summa-server --data-dir ./data --doctor

--doctor removes metadata entries and files that cannot be opened. Normal cleanup removes only unowned files. Reorder failures back off; truncated outputs stop optimizer retries at --optimizer-max-unconverged-passes. See the segment lifecycle contract.

gRPC API

The protocol defines the complete wire API:

Service Operations
SearchService Search, retrieve documents, inspect indexes, export BM25 statistics
IndexService Create/list/delete indexes; batch/stream ingest; delete/upsert rows; commit; merge/compact; reorder; retrain/alter vector indexes

Search supports text, phrase, Boolean, range, prefix, sparse/dense/binary vector, fusion, formula ranking, and reranking. Use SDL or the shared JSON schema to create indexes. Document addresses are snapshot-local; use primary keys for durable identity.

RetrainVectorIndex rebuilds ANN segments and publishes the new global artifacts atomically. Training samples are bounded per field by both --vector-training-max-samples (10,000,000) and --vector-training-memory-mb (4096). AlterVectorIndex switches compatible fields between IVF and ScaNN; see vector configuration.

Row compaction

ForceMerge retains tombstones by default. Set compact: true to physically remove deleted rows from its final output, including a singleton. GetIndexInfo returns physical_num_docs, num_deleted_docs, and deleted_ratio; num_docs counts live rows. Broker responses aggregate counts before computing the ratio.

The existing optimizer (--optimizer-threads > 0) also compacts segments with at least --optimizer-compaction-deleted-ratio deleted rows (default 0.30; 0 disables compaction). It uses the existing task slots, maintenance capacity, whole-pass gate and CPU pool. Only one automatic compaction can run globally; --optimizer-compaction-cooldown-secs (default 60) starts at completion. --compaction-memory-budget-mb bounds scratch for manual/API and automatic compaction (default 256 MiB). Busy/foreground-owned segments are skipped; failures use optimizer backoff. Segments selected for compaction are excluded from BP work in the same scan. This applies to indexes without reorder fields too.

Compaction preserves surviving row/BMP record order and the reordered flag, but changes BMP block membership, so it invalidates convergence on previously reordered nonempty BMP layouts. The BP attempt count is retained. See the compaction contract for details.

Primary-key deletion and upserts

IndexService exposes DeleteDocuments { index_name, primary_keys } and UpsertDocuments { index_name, documents }. Both stage mutations and return { accepted_count, errors }; Commit atomically publishes accepted work and reloads the reader. Deletion hides every chunk of a matching document. Upserts replace the complete document and insert missing keys. Batch errors keep their original positions; missing deletes are accepted. Requests require a primary-key schema and are bounded before conversion/admission. The broker routes both operations to the same partition as ingestion. See the mutation contract for limits and failure/cancellation semantics.

Docker

Build the server-only image from the repository root:

docker build -t summa-server -f summa-server/Dockerfile .
docker run --rm -p 50051:50051 -v "$PWD/data:/data" summa-server --data-dir /data

The published image contains both server and broker binaries:

docker run --rm -p 50051:50051 -v "$PWD/data:/data" \
  ghcr.io/spacefrontiers/summa/summa-server:latest \
  summa-server --data-dir /data

Development checks

python3 scripts/check_search.py full

This site uses Just the Docs, a documentation theme for Jekyll.