Operations

Native search core

Install the C core that searches vector fields, confirm that the engine loaded it, see how much faster it builds and searches, and keep a bundler from leaving it out in production.

Narsil searches vector fields, builds and compacts their graphs, and writes their codes in a core written in C. Where that core is missing, the engine uses the same search compiled to WebAssembly. The core handles vector fields alone, so a keyword search takes the same path on every installation. You build check-core.ts here, a script that exits with an error on a host where the engine would search through WebAssembly.

Install the core for your platform

@delali/narsil lists six platform packages as optional dependencies, so your package manager installs the one that matches the machine along with the engine:

PlatformPackage
macOS on Apple silicon@delali/narsil-native-darwin-arm64
macOS on Intel@delali/narsil-native-darwin-x64
Linux on ARM, with glibc or musl@delali/narsil-native-linux-arm64
Linux on x64, with glibc or musl@delali/narsil-native-linux-x64
Windows on ARM@delali/narsil-native-win32-arm64
Windows on x64@delali/narsil-native-win32-x64

The Linux packages each hold a second build for musl, the C library that Alpine uses, which the engine loads where the process has no glibc. On every other platform, and in every browser, the engine searches through WebAssembly. Where you install with optional dependencies left out, as npm install --omit=optional does, the package manager skips the platform package, so the engine falls back without an error.

Confirm that the engine loaded the core

By default, the engine loads the core at the first vector search. Where it cannot load the core, it writes one line to standard error at that moment and then searches through WebAssembly:

Narsil searches through WebAssembly on this thread, because this installation has no "@delali/narsil-native-darwin-arm64" package.

With NARSIL_REQUIRE_NATIVE_CORE=1 in the environment, createNarsil loads the core at start-up and fails with CONFIG_INVALID where it cannot load it, so check-core.ts only has to start the engine and shut it down.

check-core.ts
import { createNarsil } from '@delali/narsil'
 
const narsil = await createNarsil()
 
await narsil.shutdown()

Add it to your deployment with the variable set, as a step before the host takes traffic:

NARSIL_REQUIRE_NATIVE_CORE=1 node check-core.ts

The script exits with 0 where the engine loaded the core. Where the package is missing, it exits with 1 and prints an error whose message reads NARSIL_REQUIRE_NATIVE_CORE is 1, yet this installation has no "@delali/narsil-native-darwin-arm64" package, with the platform of that host in the package name.

You can steer the choice with two more variables. Under NARSIL_SEARCH_BACKEND=wasm, the engine searches through WebAssembly without the notice, which helps when you compare the two paths. Under any value other than native or wasm, however, createNarsil fails with CONFIG_INVALID. With NARSIL_NATIVE_CORE_PATH set to the absolute path of a core binary, the engine loads that binary in place of the platform package, although it falls back with a notice where no file exists at that path. The engine also refuses a binary built for a different version of its interface and falls back, naming both versions in the notice.

See what the core changes

The engine returns the same hits from an exact scan on both paths, which a continuous integration job checks on each of the six platforms and on both musl builds. The two paths differ in how long the work takes. The table below lists the times from a measured run on an 11-core Apple silicon machine under Node 24, with 20,000 vectors of 384 dimensions and worker threads turned off.

WorkWebAssemblyNative core
An exact scan while the graph builds, for each query3.50 ms3.24 ms
Building the graph13.4 to 13.7 s5.3 to 5.4 s
A graph search, for each query0.16 ms0.12 ms

The native core builds the graph in less than half the time. Since the engine answers from the exact scan while it builds the graph, the engine with the core also moves searches onto the graph sooner. Vector search covers the graph, the coding modes, and the memory and disk storage modes.

Keep Narsil out of your bundle

The engine resolves the platform package at run time, starting from its own module, so a bundler that follows your imports never includes the package. Where a deployment holds the bundle alone, the engine finds no platform package and falls back to WebAssembly. The bundle also leaves out the file from which the engine starts each worker thread, so a bundled engine answers every query on the main thread and writes a notice to standard error as it starts. Where the configuration sets workers.enabled: true, createNarsil fails with CONFIG_INVALID in place of that notice.

Mark @delali/narsil as external in your bundler, so that the application loads it from node_modules at run time. In Next.js, name it in serverExternalPackages:

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  serverExternalPackages: ['@delali/narsil'],
}
 
export default nextConfig

With esbuild, pass --external:@delali/narsil; with webpack, list the package under externals.

Where to go next

  • Vector search covers the graph that the core builds and the storage modes of a vector field.
  • Configuration covers the worker settings, including the thread budget.