Search Semantics and System Architecture
A search system must define text equality before choosing an index. This design decodes input as strict UTF-8, preserves the original text for display, and indexes a separate NFC-normalized representation. Case-insensitive queries use NFC plus full Unicode casefold() in a distinct index because folding can change length. Exact and prefix queries operate on normalized code points; grapheme clusters are display units, and returned half-open spans are mapped back to the original document before highlighting.
The service supports exact substring, prefix, tagged multi-pattern, approximate, and constrained regular-expression queries. Immutable segments provide compact high-throughput search, while recent writes enter a dynamic delta. A planner chooses compatible indexes, unions candidates from visible segments, verifies probabilistic or approximate candidates, maps offsets to original text, and removes stale document versions.
flowchart TD
I["Documents and updates"] --> N["Strict decode, normalize, tokenize"]
N --> S["Immutable segments and dynamic delta"]
Q["Typed query and text contract"] --> P["Query planner"]
P --> S
S --> V["Verify, map offsets, rank, deduplicate"]
V --> R["Results with original-text spans"]
Index Portfolio and Query Planning
No single structure efficiently supports every query and update pattern. The planner first resolves normalization, case, tokenization, distance, and overlap semantics. It then selects an index that returns exact answers or a candidate superset. Hashes, q-grams, and BK-trees require exact verification whenever their guarantees do not establish the requested match directly.
Mutable data favors radix tries, small rebuilt Aho-Corasick automatons, n-gram postings, and direct scans. Sealed segments use positional indexes, suffix arrays, or FM-indexes. Every segment records its normalization profile, Unicode-data version, tokenizer version, and index parameters so incompatible results cannot be combined.
| Query type | Required semantics | Primary index | Dynamic path | Verification | Expected cost |
|---|---|---|---|---|---|
| Exact substring | Normalized code-point equality | FM-index or suffix array | Direct scan or n-gram postings | Hash candidates only | FM count in $O(m)$ rank steps; locating adds output and sampling cost |
| Exact token | Versioned tokenizer and normalized equality | Inverted index | Mutable postings | Compare text if hashes are stored | Posting lookup plus reported documents |
| Prefix | Normalized token prefix | Radix trie or compact FST | Mutable radix trie | None | $O(m + z)$ for prefix length $m$ and output $z$ |
| Tagged multi-pattern | Shared normalization and overlap policy | Aho-Corasick | Small rebuilt automaton | None for literals | $O(n + L + z)$ |
| Edit distance at most $k$ | Declared code-point or grapheme units | q-gram filter plus bounded DP | BK-tree for small dictionaries | Always | Candidate work plus $O(km)$ per verified candidate |
| Safe regex | Declared regular feature subset | Thompson NFA or DFA | Same engine over delta | Engine is exact | Linear in text times active-state representation |
| Phrase | Token order and gap policy | Positional inverted index | Mutable positional postings | Verify positions and visibility | Posting intersection plus candidates |
Static and Incremental Data Paths
Each update receives a monotonically increasing document version. Original bytes, decoded text, normalized text, offset map, and searchable fields are committed together before becoming visible. Deletions create tombstones so older copies in immutable segments cannot reappear.
When a delta reaches a measured age or size threshold, it is frozen and converted into an immutable segment. The builder validates the new indexes against naive oracles before atomically publishing a new segment manifest. Compaction merges live versions, removes tombstones, rebuilds compressed indexes, and swaps one generation pointer; readers retain their pinned generation until completion.
Result Location and Highlighting
Normalization is not position preserving: a decomposed base-plus-mark sequence may compose into one code point, while case folding may expand one code point into several. Ingestion therefore stores a monotone mapping from each normalized position to the original half-open span that produced it. Overlapping mapped matches are merged before markup is generated, preventing duplicate or nested highlights.
The educational implementation below groups a starter with following combining marks, applies NFC, maps each output code point to the whole source group, and safely escapes untrusted text. Production grapheme segmentation should use a Unicode Standard Annex #29 implementation with a pinned Unicode-data version.
Correctness, Security, and Adversarial Testing
Correctness starts with exhaustive tiny corpora checked against naive algorithms. Property, differential, fuzz, and metamorphic suites vary empty inputs, overlaps, normalization forms, combining marks, supplementary characters, malformed encodings, replacements, deletions, and compaction. Canonically equivalent inputs must yield equivalent normalized matches while mapping to their own original spans.
Malformed bytes are rejected or handled by an explicitly named replacement policy before indexing. Hash candidates are verified unless an API explicitly offers quantified probabilistic results. User regexes run in a supported regular subset under an automata engine, or under strict time, memory, and input limits. Repetitive text, match storms, adversarial hash keys, and pathological edit queries exercise backpressure and resource caps.
Production invariants: query and index must share normalization, Unicode-data, tokenizer, and offset-map versions; every result must belong to the visible document version and map to a valid original span; probabilistic candidates never become exact results without verification. Decode failure, truncation, and resource exhaustion are explicit outcomes, never silent partial success.
Benchmarking and Design Defense
Evaluation separates correctness, retrieval quality, latency, throughput, memory, update cost, and output volume. Exact tests report p50, p95, and p99 latency for present and absent patterns across natural, random, multilingual, and repetitive inputs. Approximate search reports candidate count, verification time, recall, and false positives by threshold. Static-index experiments vary rank checkpoints and suffix-array sampling; update experiments measure acknowledgement, visibility, recovery, compaction amplification, and query latency during publication.
Every run records the corpus digest, Unicode and tokenizer versions, hardware, warm-up policy, cache state, segment layout, parameters, and confidence intervals. A threshold is accepted only when it satisfies a stated objective on representative and adversarial workloads.
Architecture Review
Assume 10 million multilingual documents, a 100:1 read/write ratio, exact-search p95 below 100 ms, prefix-search p95 below 40 ms, new documents visible within 2 seconds, and index storage below 60% of normalized corpus size.
- Declare strict UTF-8, NFC code-point matching, optional NFC-plus-casefold indexes, versioned tokenization, and original-text result spans.
- Defend FM-index segments for exact substring search, positional postings for phrases, a compact trie or FST for prefixes, Aho-Corasick for managed signatures, and q-gram filtering with bounded-DP verification for approximate search.
- Route writes through a durable delta; freeze by measured size or age; validate new segments; publish with an atomic manifest swap.
- Sweep checkpoint spacing, suffix-array sampling, delta size, q-gram length, and edit thresholds. Report latency distributions, memory, update visibility, candidate amplification, compaction cost, and output-sensitive behavior.
- Demonstrate malformed-input handling, normalization-equivalence tests, exact candidate verification, regex resource controls, version filtering, and bounded result queues.
- Choose the cheapest configuration meeting every objective and explain rejected alternatives with measurements.
The defense succeeds only if another engineer can reproduce the results, trace every answer to its declared text contract, and identify which benchmark would justify changing the architecture.