ix
Sub-millisecond code search via sparse trigram indexing.
ix pre-computes a byte-level trigram index to narrow search candidates to a fraction of the total file set, then verifies matches with a memory-constant streaming architecture. This eliminates the linear-scan bottleneck that slows grep and ripgrep on large codebases.
Install
This installs two binaries:
ix— CLI search toolixd— background daemon for continuous indexing (requiresnotifyfeature, enabled by default)
Quick Start
# Index a directory (defaults to current directory if no path given)
# Search
# Search with regex
# Search with context lines
# Negation filter (exclude matches)
# Show query statistics
How It Works
- Index —
ix --buildwalks the directory, extracts byte-level trigrams from every file, and writes a compressed index to.ix/shard.ix. - Plan — On search, the query is decomposed into trigrams. The index is consulted to find candidate files that contain all required trigrams.
- Verify — Candidates are streamed through a regex matcher with constant memory usage, producing precise line-level results.
Index Format (v1.3)
All integers are little-endian, all offsets absolute from file start, 8-byte aligned sections.
| Section | Description |
|---|---|
| Header | 256 bytes: magic IX01, version, flags, section offsets |
| File table | Per-file metadata: path hash, content hash, size, posting offset |
| Trigram table (CDX) | Delta-encoded + varint + ZSTD compressed in 1024-entry blocks |
| Block index | (u32 first_key, u64 block_offset) × N + sentinel |
| Posting lists | Per-trigram file IDs, delta-encoded + varint + ZSTD |
| String pool | Interned file paths |
CDX compression is always-on since v1.3. The reader does a two-level search: block index → decompress block → linear scan.
Not backward compatible with v1.1 or v1.2. Rebuild indexes after upgrading:
Daemon
ixd watches a directory for file changes and incrementally updates the index:
The daemon exposes a Unix domain socket for external consumers (editors, tooling):
$XDG_RUNTIME_DIR/ixd/{hash}.sock
Protocol is NDJSON — one JSON object per newline-terminated line. Push notifications for file changes and status updates; query/response for history and status queries.
ix CLI does not use the socket — it reads the index file directly.
Performance
| Metric | Value |
|---|---|
| Index ratio | ~4× source size (ZSTD level 3) |
| Selective query (10% match) | 40ms — scans 10× fewer files than ripgrep |
| Small dataset (all match) | 305ms — ripgrep wins on small/all-match workloads |
| Cold start | <3s |
| Hot path p99 | <50ms |
ix wins when the trigram index can eliminate most files from scanning. On small repos or queries where every file matches, linear-scan tools like ripgrep are faster.
Feature Flags
| Flag | Default | Description |
|---|---|---|
notify |
yes | File watcher + daemon (ixd) |
decompress |
no | gz/zst/bz2/xz decompression |
archive |
no | zip/tar archive support |
full |
no | All optional features |
Library
ix is also a library (moeix on crates.io, ix as the crate name):
[]
= "0.5"
use ;
let reader = open?;
let mut executor = new;
let matches = executor.execute;
See docs.rs/moeix for full API documentation.
Building
Requires Rust 1.85+.
License
MIT
Clean-Before-Build
The daemon uses a "clean-before-build" pattern to prevent stale file descriptor bugs:
- Old temp files are cleaned at the start of each build (not at the end)
- Fresh writers are initialized for each build
- No temp file accumulation across consecutive builds
- Prevents inode exhaustion on Linux
This fixes the critical bug where incremental rebuilds failed with "I/O: No such file or directory (os error 2)" after the first successful build.