{"slug": "my-note-server-has-no-search-engine-search-still-answers-in-0-4-ms", "title": "My note server has no search engine. Search still answers in 0.4 ms.", "summary": "A developer built Vellum, a self-hosted markdown note server that performs search via a ranked in-memory scan instead of using a search engine library. On a 2,000-note vault, queries return in under a millisecond, and a single optimization pass made the search about 5× faster than the initial implementation. The developer argues that for small-scale personal knowledge bases, a simple scan over RAM is more efficient than adding a full search engine.", "body_md": "[vellum MCP](https://github.com/freema/vellum) is a self-hosted server over a folder of markdown, your agent's memory as plain `.md`\n\nfiles you own. Search is the feature everyone expects to be \"solved\" by a library: bolt on [bleve](https://github.com/blevesearch/bleve) or a vector index and move on.\n\nI never did. Search is a ranked scan over the notes, held in RAM. No engine, no index to build. Bragging about *not* adding a dependency is a strange flex, so let me show you what that plain scan actually costs, and how one optimization pass later it runs about **5× faster** than my first honest cut of it. This is the case for the boring option.\n\nA bundled search engine sounds like the responsible choice, and for a large enough corpus it is. But look at what it actually drags in for a personal note vault:\n\nFor a vault of a few thousand notes, that's a lot of moving parts to answer \"which notes mention OAuth.\"\n\nThe whole content is already in memory. Vellum holds the vault in an in-RAM index it rebuilds from disk in about **50 ms at startup**. So search is just a **ranked scan** over that: walk the notes, score each against the query, sort, return the top slice. No separate index, nothing to warm up, nothing to corrupt.\n\nHere's what that costs on a 2,000-note vault (Apple M4 Pro, warm):\n\n| Query type | Time |\n|---|---|\n| exact term | 0.38 ms |\n| accent-folded, multi-term | 0.74 ms |\n| worst-case typo match | 0.39 ms |\n\nSub-millisecond, every time. Nothing to invalidate except when a file actually changes. The cache keys off each file's **mod-time and size**, so an edit through the MCP layer, the web editor, or your text editor all invalidate the same way.\n\nThe reason I don't miss the library is that writing the matcher myself made it *more* forgiving, not less. Three things it does that matter for real notes:\n\n**Folds diacritics both ways.** `ukol`\n\nfinds `úkol`\n\n; `poznamka`\n\nfinds `poznámka`\n\n; and the reverse. When half your notes are in a language with accents and you're typing on muscle memory, this is the difference between \"found it\" and \"search is broken.\"\n\n**Tolerates typos, scaled to word length.** For a 4 to 7 character word it allows one edit; for 8+ it allows two. So `preklapy`\n\nfinds `překlepy`\n\n. Short words stay strict (you don't want `cat`\n\nmatching `car`\n\n), long words get slack where a fat-fingered character is likely.\n\n**Ranks by where the match lands.** A hit in the **title** beats a hit in a **tag**, beats one in the **path**, beats one buried in the **body**. And a match at the *start of a word* beats one mid-word. This is the part a generic engine gets generically right and a hand-written one gets *right for your data*. The ranking encodes how you actually think about your own notes.\n\nI'm not claiming a linear scan is the answer for everyone. It's the answer for *this size of problem*. So the search sits behind a `Searcher`\n\ninterface. The day a vault gets big enough that a scan hurts, I can drop a real engine in behind the same seam without touching the callers.\n\nThat day hasn't come. Two thousand notes in, a full ranked scan is still sub-millisecond, and honestly it would take an order of magnitude more before the constant factors start to matter. When you do cross that line (tens or hundreds of thousands of documents, or you genuinely need semantic similarity rather than lexical matching), reach for the index. Below it, the index is the heavy option pretending to be the safe one.\n\n\"Use a library for search\" is good advice that stops being good at small scale, and small scale is where a lot of self-hosted tools live. Before you add the dependency, the index, the warm-up and the embedding pipeline, check whether a scan over what's already in RAM answers in under a millisecond. For a personal knowledge base, it very likely does.\n\nSometimes the library is the heavy option.\n\n👉 [github.com/freema/vellum](https://github.com/freema/vellum). MIT, one 24 MB container.\n\n*Where's your line for \"just scan it\" vs. \"build an index\"? I'd put it somewhere north of 50k documents for lexical search. Curious where others draw it.*", "url": "https://wpnews.pro/news/my-note-server-has-no-search-engine-search-still-answers-in-0-4-ms", "canonical_source": "https://dev.to/freema/my-note-server-has-no-search-engine-search-still-answers-in-04-ms-3abd", "published_at": "2026-07-16 13:00:00+00:00", "updated_at": "2026-07-16 13:41:42.908121+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "ai-agents"], "entities": ["Vellum", "bleve", "Apple M4 Pro", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/my-note-server-has-no-search-engine-search-still-answers-in-0-4-ms", "markdown": "https://wpnews.pro/news/my-note-server-has-no-search-engine-search-still-answers-in-0-4-ms.md", "text": "https://wpnews.pro/news/my-note-server-has-no-search-engine-search-still-answers-in-0-4-ms.txt", "jsonld": "https://wpnews.pro/news/my-note-server-has-no-search-engine-search-still-answers-in-0-4-ms.jsonld"}}