RSS Amplifier

jdrouet · Apr 5, 2025

Building a search engine from scratch, in Rust: part 3

0
Sign in to vote or save

This page cannot be shown here. You can still read it on the original site — the toolbar below keeps your place in the directory.

In the previous article , we implemented the core document structure and indexes for our search engine. Now, let's tackle how we'll make our search engine scalable through sharding and reliable through transactions. Shard Definition Here's how our sharding architecture organizes data across multiple shards: Manifest +--------+ | shards | +--------+ | +----------+-----------+ v v v…

In the previous article</a>, we implemented the core document structure and indexes for our search engine. Now, let's tackle how we'll make our search engine scalable through sharding and reliable through transactions.</p>

Shard Definition</h3>

Here's how our sharding architecture organizes data across multiple shards:</p>

                 Manifest
</span>                +--------+
</span>                | shards |
</span>                +--------+
</span>                     |
</span>          +----------+-----------+
</span>          v          v           v
</span>    +---------+ +---------+ +---------+
</span>    | Shard 0 | | Shard 1 | | Shard 2 |
</span>    |---------| |---------| |---------|
</span>    | 0 - 100 | | 101-200 | | 201-300 |
</span>    +---------+ +---------+ +---------+
</span>        |           |           |
</span>    Collection  Collection  Collection
</span>     Indexes     Indexes     Indexes
</span></code></pre>

At this point, we have everything we need to build a shard: a collection to list all the documents in the shard and an index for each type. A simple implementation of that shard would look like this.</p>

// an abstraction to be able to map the indexes
</span>enum </span>AnyIndex </span>{
</span>    Boolean</span>(</span>BooleanIndex</span>),
</span>    Integer</span>(</span>IntegerIndex</span>),
</span>    Tag</span>(</span>TagIndex</span>),
</span>    Text</span>(</span>TextIndex</span>),
</span>}
</span>
</span>struct </span>Shard </span>{
</span>    collection</span>:</span> Collection,
</span>    indexes</span>: </span>HashMap</span><</span>Kind, AnyIndex</span>></span>,
</span>}
</span></code></pre>

But this is a single shard representation, we might have several and need to have a representation for all of them.</p>

struct </span>Manager </span>{
</span>    shards</span>: </span>BTreeMap</span><</span>u64</span>, Shard</span>></span>,
</span>}
</span></code></pre>

With this representation, the u64</code> in the BTreeMap</code> will represent the minimum in the range of partition handled by that shard. When initialized, the first shard key will be 0</code>.</p>

But the two previous representations are actually wrong: this would mean that we'll load in memory the entire search engine, which doesn't scale. Instead, the Shard</code> structure will only contain the filenames of the collection and indexes, which will be loaded in memory only when needed, and written to disk when they are not needed anymore.</p>

The Manager</code> structure can then be renamed to Manifest</code> and will be, as well, persisting on disk, representing the state of the search engine at a given point in time.</p>

struct </span>Manifest </span>{
</span>    shards</span>: </span>BTreeMap</span><</span>u64</span>, Shard</span>></span>,
</span>}
</span>
</span>struct </span>Shard </span>{
</span>    collection</span>:</span> Filename,
</span>    indexes</span>: </span>HashMap</span><</span>Kind, Filename</span>></span>,
</span>}
</span></code></pre>

This manifest will be stored in the working directory as manifest.bin</code> and every file (collections and indexes) will have a random name.</p>

Sharding architecture highlights:</p>

Read on /posts/202503231000-search-engine-part-3/

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.