# visage $ make bench

Compact by design

The data store

// minimal acyclic DAFSA · interned symbols · mmap + WAL

Aliases live in a datalog-dafsa store built on a minimal acyclic DAFSA — a graph that shares common prefixes and suffixes across every key, so the store stays tiny even as you add thousands of aliases. Strings are interned to 32-bit symbol ids; each relation is one on-disk DAFSA that is mmap'd for read-only serving and durably WAL'd (single-writer, flock).

Measured (make bench): on-disk store size is ~480 B/alias at 1,000 aliases and ~542 B/alias at 1,000,000 — roughly constant across three decades, with no per-alias index bloat. The footprint is dominated by the string interner (every distinct address stored once), not per-alias metadata.

Store size in bytes per alias vs alias count: roughly constant near 500 bytes

Prefix search, not indexes

// forward + reverse routing are byte-prefix walks

Email routing needs exactly two lookups, and both are native DAFSA primitives — so visage carries no separate index, and lookup cost scales with address length, not alias count.

  • Forward routing — resolving alias@domain → its destinations is a prefix walk binding the (domain, local) columns of the alias relation.
  • Reverse reply routingreply+<token>@domain → (original sender, alias) is a prefix walk binding the token column of the revmap relation.
  • O(prefix), not O(aliases) — alias resolution and reply-token routing are O(prefix length) regardless of how many aliases exist.

Measured (make bench): warm store_resolve/store_revmap_resolve latency is ~0.6 µs at 1,000 aliases and ~3 µs at 1,000,000 — sub-linear (≈5× over three decades of scale), i.e. the cost tracks address length, not alias count.

Resolve latency vs alias count: sub-linear, about 0.6 microseconds at 1k aliases to about 3 microseconds at 1M