Meilisearch Under the Hood: Architecture, Database, Algorithms, and Language Processing

Meilisearch is far more than a fast search tool—it is a sophisticated system built on deeply considered architectural decisions. From using Rust to ensure memory safety and performance, to choosing LMDB as the storage engine, to designing a bucket sort ranking algorithm instead of traditional BM25, and crafting a multilingual tokenizer with Charabia—each decision reflects Meilisearch’s core philosophy: speed, simplicity, and accuracy. This article dives deep into every layer of Meilisearch, explaining how data is stored, processed, indexed, and returned to users in under 50 milliseconds.

Meilisearch Under the Hood: Architecture, Database, Algorithms, and Language Processing

System Architecture: The Three Pillars

Meilisearch is designed around three pillars: performance, relevance, and developer experience. The source code structure is divided into three main components: the meilisearch repository serves as the HTTP API layer and manages multiple indexes; milli is the core engine handling search and indexing; and Charabia is the tokenizer library built for multilingual processing. This separation allows each component to be optimized independently while maintaining clear interfaces between layers.

The main meilisearch repository is responsible only for receiving HTTP requests, managing multiple indexes concurrently, and handling the update store where asynchronous tasks are queued. All actual search logic and indexing is delegated to milli. This means that if you want to understand how Meilisearch works internally, you need to focus on milli and Charabia. Meilisearch is written entirely in Rust, a systems programming language that delivers performance close to C/C++ while providing compile-time memory safety through its ownership model. This eliminates entire classes of errors related to use-after-free, double-free, and buffer overflows that search engines written in C/C++ commonly suffer from.

Storage Layer: LMDB and Heed—Memory Mapping Over Traditional I/O

Why Meilisearch Chose LMDB

Meilisearch uses the Lightning Memory-Mapped Database (LMDB) as its underlying storage engine. LMDB is a transactional key-value store written in C, originally developed for OpenLDAP, with ACID properties. Meilisearch evaluated alternatives such as Sled and RocksDB but ultimately chose LMDB for the best combination of performance, stability, and features. The core of LMDB is its memory mapping mechanism: instead of reading data from disk into memory through read system calls, LMDB maps the data file directly into the process’s virtual address space. When Meilisearch requests a document, data is returned straight from the memory map without memory allocation or data copying.

The result is that all documents stored on disk are automatically loaded into memory when Meilisearch requests them. This ensures LMDB always makes the best use of available RAM for document retrieval. A RAM-to-disk ratio of about 1/3 does not materially impact performance, and for many workloads even a ratio of 1/10 works well. Importantly, Meilisearch will not crash when the dataset size on disk exceeds available RAM—the operating system manages page loading and eviction transparently. However, disk latency still matters: using an NVMe SSD yields significantly better results than HDD or network-mounted storage.

Heed: A Type-Safe Rust Wrapper for LMDB

Meilisearch does not use LMDB directly through raw FFI bindings. Instead, the team developed heed—a Rust crate providing a fully typed wrapper for LMDB with minimal overhead. Heed allows storing and retrieving Rust data types directly in LMDB, including types compatible with Serde. This means that instead of working with raw bytes, you can define databases with specific key and value types, and Rust ensures correctness at compile time.

Heed supports both LMDB branches: mdb.master (via the heed crate) and mdb.master3 (via the heed3 crate). The master3 branch adds encryption-at-rest and checksumming features that the standard heed crate lacks. Both crates share the same codebase and can be switched using the convert-to-heed3.sh script. Using heed instead of raw LMDB bindings brings significant benefits: type-safe transactions, typed iterators, and the ability to open multiple sub-databases within the same environment without managing raw pointers.

Storage Characteristics and Disk Space Management

An important characteristic of LMDB that Meilisearch users need to understand is how disk space is managed when deleting documents. When you delete a document from an index, disk usage may not decrease. This happens because LMDB internally marks that space as free but does not return it to the operating system. This design choice leads to better performance since no periodic compaction is needed, but the result is that Meilisearch’s disk space tends to grow over time. It is not possible to calculate the precise maximum amount of space a Meilisearch instance can occupy.

In terms of database size, with the movies.json dataset of about 8.6 MB containing 19,553 documents, the LMDB size after indexing is approximately 224 MB on disk and uses about 305 MB of RAM. Virtual memory allocated reaches up to 205 GB (memory map), but this is only virtual address space, not actual memory in use. These numbers depend heavily on the running machine and are very difficult to predict accurately because database size is affected by settings, ranking rules, facets, the number of languages, and many other factors.

Tokenization Layer: Charabia and the Art of Language Segmentation

What Is a Tokenizer and Why Does It Matter?

Tokenization is the first and most critical step in the document indexing process. It is the act of taking a sentence or phrase and splitting it into smaller units of language called tokens. When you index a document containing “Freshly baked croissants,” Meilisearch splits it into three tokens: freshly, baked, croissants. These tokens are what Meilisearch stores and matches against when a user performs a search query. Breaking sentences into smaller chunks requires understanding where one word ends and another begins, making tokenization a highly complex and language-dependent task.

Meilisearch solves this problem with a modular tokenizer that follows different processes called pipelines based on the language it detects. This allows Meilisearch to work with many different languages with zero setup. Charabia is the open-source tokenizer library written in Rust, purpose-built for Meilisearch’s multilingual needs.

The Tokenization Process: Script Detection and Pipeline Application

Meilisearch’s tokenization process happens in two main steps. First, the tokenizer crawls through the document and splits each field by writing system (script)—for example, Latin alphabet, Chinese hanzi, Cyrillic script. Second, it goes back over the document part by part, running the corresponding tokenization pipeline if one exists. Each pipeline includes many language-specific operations.

Currently Charabia has dedicated pipelines for: Latin (including English, French, Spanish, Italian, Portuguese, etc.), German (with compound word segmentation), Chinese (using jieba dictionary segmentation), Japanese (lindera IPA dictionary), Korean (lindera KO dictionary), Thai (dictionary-based), Khmer, Arabic (with article ال segmentation), Hebrew, Greek, Turkish, Armenian, Persian, and Swedish. Languages without a dedicated pipeline still work with the default whitespace-based pipeline, though results may be less relevant for languages that do not use whitespace to separate words.

Segmentation and Normalization: The Two Pillars of Language Processing

The two main aspects of improving language support are segmentation and normalization. Segmentation is understanding where a word begins and ends. This seems simple for English or European languages—”split by whitespace and you have your words”—but becomes complex for Chinese, where there are no clear delimiters between characters. Normalization includes standardization modifications (uppercase/lowercase), compatibility equivalence (recognizing the same character in different forms, e.g., ツ and ッ), and transliteration (converting from one alphabet to another).

A concrete example: when a user types “mais” in French, Meilisearch will return results for both “but” and “corn” because it accounts for the possibility that the user forgot or did not use the appropriate diacritics. For Vietnamese, the tokenizer removes tone marks during normalization, allowing searches for “học sinh” to match both “học sinh” and “hoc sinh.” However, this can also cause issues when tone marks change the meaning of a word—this is one of the challenges the Meilisearch team is actively researching to improve.

Customizing Tokenization: Separators, Non-Separators, and Dictionary

Meilisearch provides three settings to control how text is split into tokens. Separator tokens allow adding custom characters or strings as word boundaries. For example, if your dataset uses | as a delimiter, you can add it so Meilisearch treats “red|green|blue” as three separate tokens. Non-separator tokens do the opposite: preventing characters like + or # from acting as separators, useful for programming terms like C++ or C#. Dictionary allows defining custom word boundaries for strings that Meilisearch would not otherwise split correctly, for example adding “icecream” so it is treated as “ice cream.”

Indexing Layer: Inverted Index and Internal Data Structures

Inverted Index: The Heart of Full-Text Search

After tokenization is complete, Meilisearch builds the inverted index—the core data structure of every full-text search engine. An inverted index maps each unique token to the list of documents containing that token, along with metadata such as position within the document. Instead of scanning the entire collection to find documents containing a keyword, the search engine only needs to look up the token in the inverted index and retrieve the corresponding list of document IDs.

Meilisearch stores the inverted index in LMDB as key-value pairs. The key is the normalized token, and the value is the list of document IDs along with position information. This structure enables extremely fast lookups: for each token in a query, Meilisearch needs only a single read from LMDB to retrieve the entire list of relevant documents. When multiple tokens are present in a query, the engine performs intersection or union operations on the posting lists depending on the search operator.

Multi-Threaded Indexing and Field-Level Update Optimization

One of Meilisearch’s notable recent improvements is indexing speed optimization. When updating a single field in a document, instead of reindexing the entire document, Meilisearch only reindexes the changed field. This significantly reduces processing time and system resources, which is especially important for e-commerce applications that need to update stock quantities in real time. The indexing process is multi-threaded, leveraging the full CPU power to process documents and tokens in parallel.

Vector Store and HNSW: Semantic Search Inside LMDB

When integrating semantic search, Meilisearch does not use a separate vector database. Instead, it stores embeddings directly in LMDB alongside document data. For efficient vector search, Meilisearch uses the HNSW (Hierarchical Navigable Small World) algorithm—an approximate nearest neighbor graph structure that enables vector search with logarithmic complexity.

HNSW builds a hierarchical graph where each layer is a sparser nearest-neighbor graph than the layer below. Search starts from the top layer, descends to a lower layer once the nearest point in the current layer is found, and finally performs detailed search in the bottom layer. This approach allows Meilisearch to search millions of vectors in milliseconds without scanning the entire vector space. Notably, Meilisearch has optimized its HNSW implementation on LMDB using nested read transactions and thread-local storage, achieving a 3x speedup in vector store performance.

Ranking Layer: Bucket Sort Instead of BM25

The Problem with BM25 in Application Search

Most traditional search engines rank results by computing a single numeric score for each document, then sorting by that number. BM25—the industry standard for full-text search—scores based on term frequency (TF), inverse document frequency (IDF), and document length normalization. PostgreSQL ts_rank, MongoDB Atlas Search, Elasticsearch, and OpenSearch all use variants of this model.

However, BM25 was designed for information retrieval—finding research papers, legal documents, or web pages—where term frequency genuinely signals relevance. But for application search (e-commerce, media catalogs, SaaS dashboards), this model has significant limitations: typos are completely invisible to BM25—”iPhone” and “iPhoone” are treated as entirely different terms; word order is ignored—searching “dark knight” and “knight dark” produce identical scores; field importance is flattened—a match in a product title should matter more than a match in a review comment, but BM25 requires complex manual weight tuning; and scoring is opaque—a score of 8.72 tells a developer nothing about why result A appears before result B.

Multi-Criteria Bucket Sort: Meilisearch’s Ranking Philosophy

Meilisearch replaces the single-score model with a multi-criteria bucket sort system. Instead of computing one number, the engine evaluates documents through a sequence of ranking rules, each acting as a successive filter. When a user searches for “badman dark knight returns,” Meilisearch applies rules in order:

The first rule, words, sorts documents by the number of query terms matched—documents containing all 4 words go into Bucket A, 3 words into Bucket B, 1 word into Bucket C. The second rule, typo, only applies to documents within the same bucket—0 typos go to sub-bucket A.1, 1 typo to A.2. The third rule, proximity, considers the distance between matched terms—adjacent terms go to A.1.1, terms 3 words apart to A.1.2. This continues through attributeRank, sort, wordPosition, and finally exactness.

Each rule only operates on documents that tied in all previous rules. This means a document matching 4/4 words with 2 typos always ranks above a document matching 3/4 words with 0 typos. The words rule has absolute priority over typo, typo over proximity, and so on. There is no way for a high score in one dimension to compensate for a low score in another. This is called lexicographic ordering—the same logic humans use to sort words in a dictionary, applied to search ranking.

The Seven Default Ranking Rules

Meilisearch has seven built-in ranking rules in default order. Words sorts by the number of matched query terms in descending order, operating from right to left so the order of the query string affects result ordering. Typo sorts by the number of typos in ascending order, with a maximum of 2 typos per query word. Proximity sorts by the distance between matched terms in descending order—”dark knight” adjacent always ranks above “dark … knight” far apart. AttributeRank sorts by attribute importance order—a match in title always ranks above a match in description if title is listed first in searchableAttributes. Sort only activates when the query includes a sort parameter. WordPosition sorts by the position of the matched term within the attribute—appearing at the start of the field ranks above the end of the field. Exactness sorts by exact match similarity—”knight” matching exactly ranks above “knights” (prefix match).

You can reorder, add, or remove any of these rules. You can also add custom ranking rules such as popularity:desc or release_date:desc to incorporate business logic into ranking. The order of rules determines which criteria are prioritized—the first rule has the greatest impact, the last rule the least.

Why Bucket Sort Is Better for Application Search

Compared to BM25, Meilisearch’s bucket sort system has clear advantages for application search. First, typo tolerance is deeply integrated—not as a separate fuzzy query step but as a first-class ranking criterion. Second, word order and proximity are respected—”new york pizza” in an adjacent cluster ranks above “new … york … pizza” scattered apart. Third, field importance is explicit and predictable—the first field in searchableAttributes always beats the second field, with no complex weight tuning needed. Fourth, ranking is transparent and debuggable—through showRankingScoreDetails, you can see exactly why a document ranks where it does. Fifth, prefix search works natively—the last word in a query is always treated as a prefix without needing n-gram or edge-gram configuration. Finally, no per-query tuning is required—rules are configured once at the index level and work consistently across all queries.

Search Layer: Hybrid Search and Combining Keyword with Semantic

The Algorithm for Merging Keyword and Vector Results

When an embedder is enabled, Meilisearch combines keyword-based search (via bucket sort) with vector similarity search in a single query. The semanticRatio parameter controls the blend: 0.0 uses only multi-criteria ranking rules, 1.0 uses only vector similarity, and values in between merge both result sets. This delivers the best of both worlds: keyword precision combined with semantic understanding, without managing two separate search systems.

The workflow operates as follows: when a user submits a query, Meilisearch runs both keyword search and semantic search concurrently. Keyword search uses the inverted index and bucket sort as described. Semantic search uses HNSW to find the nearest vector embeddings to the query vector. The two result sets are then merged using an intelligent scoring system, and the most relevant results appear first, regardless of whether they matched by exact keyword or by semantic meaning.

Auto-Embeddings and AI Provider Integration

Meilisearch supports automatic embedding generation through multiple providers: OpenAI, Cohere, Mistral, Google Gemini, Cloudflare, Voyage AI, Jina, AWS Bedrock, and HuggingFace. When configured with an embedder, Meilisearch automatically generates vector embeddings for documents during indexing and for queries during search. This eliminates the need to manage a separate vector store—embeddings are stored directly in LMDB alongside document data. Many multilingual embedding models can process over 100 languages, enabling cross-lingual search: an English query can match documents written in French, German, or Japanese.

Scaling Layer: Sharding, Replication, and the Future

Horizontal Scaling Through Sharding

Meilisearch’s Enterprise edition introduces sharding—the ability to distribute data across multiple nodes. Sharding enables handling massive datasets that exceed the capacity of a single server. Additionally, replicated sharding has been integrated into the engine, enabling fault-tolerant distributed search. When one node fails, replica nodes can continue serving requests without interruption. This marks a significant step in moving Meilisearch from a single-node solution to a truly distributed architecture.

RAG and Conversational Search

Meilisearch has expanded beyond traditional search to support Retrieval-Augmented Generation (RAG). In an RAG architecture, when a user asks a question in natural language, Meilisearch first performs hybrid search to find the most relevant documents. These documents are then fed into the LLM’s prompt as context, allowing the AI to generate answers based on your actual data rather than general knowledge. Meilisearch provides granular control over what content is sent to the LLM—you can choose which fields to include in the prompt, use simple templates, and include metadata. Integration with LangChain and MCP (Model Context Protocol) allows Meilisearch to serve as the retrieval layer in AI pipelines seamlessly.

Conclusion: The Architectural Philosophy Behind Meilisearch

Meilisearch is proof that sound architectural decisions can create a superior product. By choosing Rust for safety and performance, LMDB for zero-copy storage, bucket sort instead of BM25 for transparent ranking, and Charabia for multilingual processing, Meilisearch has built a search engine that is not only fast but also understandable and controllable.

The core strength of this architecture is the clear separation between layers: storage (LMDB via heed), tokenization (Charabia), indexing (milli inverted index), ranking (bucket sort), and search (hybrid keyword + vector). Each layer can be optimized, replaced, or extended independently. This explains why Meilisearch can add semantic search, RAG, and sharding without breaking APIs or requiring major restructuring.

For developers, understanding Meilisearch’s internals leads to better design decisions: when to split documents for better proximity, how to arrange ranking rules to reflect business priorities, how to configure tokenization for specialized data, and when to scale RAM versus sharding. Meilisearch is not just a tool—it is a system you can understand deeply, control completely, and customize for your specific needs.

Comments


  • No comments yet.

Web-Based Tools

Press Ctrl + \ on desktop, or swipe left anywhere on mobile.

Login