Designing a Distributed Search System
Designing a Distributed Search System
Learn about
System how aThe
Design: search system works and understand our high-level plan for
Distributed
designing
Search a distributed search system.
Let’s take another example. There are billions of videos uploaded and stored on
YouTube. Imagine if YouTube didn’t provide us with a search bar. How would we
find a specific video among the millions of videos that have been posted on
YouTube over the years? It would take months to navigate through all of those
videos and find the one we need. Users find it challenging to find what they’re
looking for simply by scrolling around.
Search
Search bar
Search engines are an even bigger example. We have billions of websites on the
Internet. Each website has many web pages and there is plenty of content on
each of these web pages. With so much content, the Internet would practically be
useless without search engines, and users would end up lost in a sea of
irrelevant data. Search engines are, essentially, filters for the massive amount of
data available. They let users quickly obtain information that is of true interest
without having to sift through too many unnecessary web pages.
• Requirements
• Functional requirements
• Non-functional requirements
• Resource estimation
• Number of servers estimation
• Storage estimation
• Bandwidth estimation
• Building blocks we will use
Requirements
Let’s understand the functional and non-functional requirements of a distributed search system.
Functional requirements
The following is a functional requirement of a distributed search system:
Search: Users should get relevant content based on their search queries.
Search
Search system
Non-functional requirements
Here are the non-functional requirements of a distributed search system:
Search system
Resource estimation
Let’s estimate the total number of servers, storage, and bandwidth that is required by the distributed search system. We’ll
calculate these numbers using an example of a YouTube search.
Number of servers estimation
To estimate the number of servers, we need to know how many daily active users per day are using the search feature on
YouTube and how many requests per second our single server can handle. We assume the following numbers:
The number of daily active users who use the search feature is three million.
The number of requests a single server can handle is 1,000.
If three million users are searching concurrently, three million search requests are being generated at one time. A single
server handles 1,000 requests at a time. Dividing three million by 1,000 gives us 3,000 servers.
3000 servers
Storage estimation
Each video’s metadata is stored in a separate JSON document. Each document is uniquely identified by the video ID. This
metadata contains the title of the video, its description, the channel name, and a transcript. We assume the following
numbers for estimating the storage required to index one video:
The following formula is used to compute the storage required to index one video:
f
200 1000 100 300
In the table above, we calculate the storage required to index one video. We have already seen that the total storage
required per video is 300 KB. Assuming that, on average, the number of videos uploaded per day on YouTube is 6,000, let’s
calculate the total storage required to index the videos uploaded per day. The following formula is used to compute the
storage required to index the videos uploaded to YouTube in one day:
No. of videos per day Total storage per video (KB) Total storage per day(GB)
f
6000 300 1.8
The total storage required to index 6,000 videos uploaded per day on YouTube is 1.8 GB. This storage requirement is just an
estimation for YouTube. The storage need will increase if we provide a distributed search system as a service to multiple
tenants.
= 1.8 GB/day
Requirements of a Distributed
Search System's Design
Summarizing the storage requirement of a distributed search system for videos uploaded to YouTube per day
Bandwidth estimation
The data is transferred between the user and the server on each search request. We estimate the bandwidth required for
the incoming traffic on the server and the outgoing traffic from the server. Here is the formula to calculate the required
bandwidth:
Incoming traffic
We can use the formula given above to calculate the bandwidth required for the incoming traffic.
f
1736.11 100 1.39
Outgoing traffic
Outgoing traffic is the response that the server returns to the user on the search request. We assume that the number of
suggested videos against a search query is 80, and one suggestion is of the size 50 Bytes. Suggestions consist of an ordered
list of the video IDs.
We can use the same formula to calculate the bandwidth required for the outgoing traffic.
f
1736.11 4000 55.56
+ = 56.95 Mbps
Incoming traffic Outgoing traffic
bandwidth = 1.39 Mbps bandwidth = 55.56 Mbps
Note: The bandwidth requirements are relatively modest because we are assuming text results. Many search
services can return small thumbnails and other media to enhance the search page. The bandwidth needs per page
are intentionally low so that the service can provide near real-time results to the client.
To conclude, we explained what the search system’s requirements are. We made resource estimations. And lastly, we Ctrl
+ >
mentioned the building block that we’ll use in our design of a distributed search system.
• Indexing
• Build a searchable index
• Inverted index
• Searching from an inverted index
• Factors of index design
• Indexing on a centralized system
We’ll first describe what indexing is, and then we’ll make our way toward
distributing indexes over many nodes.
Indexing
Indexing is the organization and manipulation of data that’s done to facilitate
fast and accurate information retrieval.
ID Document Content
3 Elasticsearch is a distributed
search and analytics engine built
on Apache Lucene.
The size of the table given above would vary, depending on the number of
documents we have and the size of those documents. The table above is just an
example, and the content from each document only consists of one or two
sentences. With an actual, real-world example, the content of every document in
the table could be pages long. This would make our table quite large. Running a
search query on the document-level index given above isn’t a fast process. On
each search request, we have to traverse all the documents and count the
occurrence of the search string in each document.
Inverted index
Term Mapping
( [doc], [freq], [[loc])
In the table above, the “Term” column contains all the unique terms that are
extracted from all of the documents. Each entry in the “Mapping” column
consists of three lists:
Inverted index is one of the most popular index mechanisms used in document
retrieval. It enables efficient implementation of boolean, extended boolean,
proximity, relevance, and many other types of search algorithms.
There is storage overhead for maintaining the inverted index along with
the actual documents. However, we reduce the search time.
Maintenance costs (processing) on adding, updating, or deleting a
document.
Indexing To add aSearch
in a Distributed document, we extract terms from the document. Then,
for each extracted term, we either add a new row in the inverted index or
update an existing one if that term already has an entry in the inverted
index. Similarly, for deleting a document, we conduct processing to find the
entries in the inverted index for the deleted document’s terms and update
the inverted index accordingly.
Consider a system that has the following mappings when we search for the word
“search engine:”
Term Mapping
Both of these words are found in documents 1, 2, and 3. Both words appear once
in each document.
A single term can appear in millions of documents. Thus, the list of documents
returned against a search query could be very long.
Question
Would this technique work when too many documents are found
against a single term?
Hide Answer
It probably wouldn’t work to return all the documents that are found.
Instead, we should sort them based on the relevance to the search
query. The top results should be returned to the user, instead of
returning all the documents.
Size of the index: How much computer memory, and RAM, is required to
keep the index. We keep the index in the RAM to support the low latency of
the search.
Search speed: How quickly we can find a word from an inverted index.
Maintenance of the index: How efficiently the index can be updated if we
add or remove a document.
Fault tolerance: How critical it is for the service to remain reliable. Coping
with index corruption, supporting whether invalid data can be treated in
isolation, dealing with defective hardware, partitioning, and replication are
all issues to consider here.
Resilience: How resilient the system is against someone trying to game the
system and guard against search engine optimization (SEO) schemes, since
we return only a handful of relevant results against a search.
In light of the design factors listed above, let’s look at some problems with
building an index on a centralized system.
Single server
Textual query
Binary file
Indexing process Query result
containing Query processing
(batch) (suggestions)
inverted index
Documents to index
The indexing process takes the documents as input and converts them into
an inverted index, which is stored in the form of a binary file.
The query processing or search process interprets the binary file that
contains the inverted index. It also computes the intersection of the
inverted lists for a given query to return the search results against the
query.
These are the problems that come with the architecture of a centralized search
system:
Server overload: If numerous users perform queries and the queries are
complicated, it stresses the server (node).
Large size of the index: The size of the inverted index increases with the
number of documents, placing resource demands on a single server. The bigger
the computer system, the higher the cost and complexity of managing it.
An inverted index needs to be loaded into the main memory when adding a
document or running a search query. A large portion of the inverted index must
fit into the RAM of the machine for efficiency.
In this lesson, we learned about indexing, and we looked into the problems of
indexing on a centralized system. The next lesson presents a distribution
solution for indexing.
• High-level design
• API design
• Detailed discussion
• Distributed indexing and searching
• Replication
• Replication factor and replica distribution
• Summary
High-level design
Let’s shape the overall design of a distributed search system before getting into a
detailed discussion. There are two phases of such a system, as shown in the
illustration below. The offline phase involves data crawling and indexing in
which the user has to do nothing. The online phase consists of searching for
results against the search query by the user.
Distributed storage
Index Documents
Term Mapping
Search
query
Search Documents
Search 2
results 1
1 4 3
Mappings Index Documents
5 2
Index Data
4 3
Distributed data
processing system
(MapReduce)
Online Offline
The crawler collects content from the intended resource. For example, if
we build a search for a YouTube application, the crawler will crawl through
all of the videos on YouTube and extract textual content for each video. The
content could be the title of the video, its description, the channel name, or
maybe even the video’s annotation to enable an intelligent search based not
only on the title and description but also on the content of that video. The
crawler formats the extracted content for each video in a JSON document
and stores these JSON documents in a distributed storage.
The indexer fetches the documents from a distributed storage and indexes
these documents using MapReduce, which runs on a distributed cluster of
commodity machines. The indexer uses a distributed data processing
system like MapReduce for parallel and distributed index construction. The
constructed index table is stored in the distributed storage.
The distributed storage is used to store the documents and the index.
The user enters the search string that contains multiple words in the
search bar.
The searcher parses the search string, searches for the mappings from the
index that are stored in the distributed storage, and returns the most
matched results to the user. The searcher intelligently maps the incorrectly
spelled words in the search string to the closest vocabulary words. It also
looks for the documents that include all the words and ranks them.
API design
Since the user only sends requests in the form of a string, the API design is quite
simple.
Search: The search function runs when a user queries the system to find some
content.
search(query)
Parameter Description
query This is the textual query entered by the user in the search bar, based on which
found.
Detailed discussion
Since the indexer is the core component in a search system, we discussed an
indexing technique and the problems associated with centralized indexing in the
previous lesson. In this lesson, we consider a distributed solution for indexing
and searching.
Hide Tip
We use numerous small nodes for indexing to achieve cost efficiency. This
process requires us to partition or split the input data (documents) among these
nodes. However, a key question needs to be addressed: How do we perform this
partitioning?
The two most common techniques used for data partitioning in distributed
indexing are these below:
In term partitioning, a search query is sent to the nodes that correspond to the
query terms. This provides more concurrency because a stream of search
queries with different query terms will be served by different nodes. However,
term partitioning turns out to be a difficult task in practice. Multiword queries
necessitate sending long mapping lists between groups of nodes for merging,
which can be more expensive than the benefits from the increased concurrency.
In document partitioning, each query is distributed across all nodes, and the
results from these nodes are merged before being shown to the user. This
method of partitioning necessitates less inter-node communication. In our
design, we use document partitioning.
Following document partitioning, let’s look into a distributed design for index
construction and querying, which is shown in the illustration below. We use a
cluster that consists of a number of low-cost nodes and a cluster manager. The
cluster manager uses a MapReduce programming model to parallelize the
index’s computation on each partition. MapReduce can work on significantly
larger datasets that are difficult to be handled by a single large server.
Textual query
Node 1
Partition 1
Inverted
Indexing index Query
process 1 (partition 1) processing
Node 2
Partition 2
Cluster
Inverted
manager Indexing Query
index Merger Query result
Process 2 processing
(partition 2)
Node 3
Partition 3
Inverted
Indexing Query
index
Process 3 processing
(partition 3)
Indexing
Searching
In the search phase, when a user query comes in, we run parallel searches
on each tiny inverted index stored on the nodes’ local storage generating N
queries.
The search result from each inverted tiny index is a mapping list against
the queried term (we assume a single word/term user query). The merger
aggregates these mapping lists.
After aggregating the mapping lists, the merger sorts the list of documents
from the aggregated mapping list based on the frequency of the term in
each document.
The sorted list of documents is returned to the user as a search result. The
documents are shown in sorted (ascending) order to the user.
The proposed design works, and we can replicate it across the globe in various
data centers to facilitate all users. Thus, we can achieve the following
advantages:
Replication
We make replicas of the indexing nodes that produce inverted indices for the
assigned partitions. We can answer a query from several sets of nodes with
replicas. The overall concept is simple. We continue to use the same architecture
as before, but instead of having only one group of nodes, we have R groups of
nodes to answer user queries. R is the number of replicas. The number of
replicas can expand or shrink based on the number of requests, and each group
of nodes has all the partitions required to answer each query.
Each group of nodes is hosted on different availability zones for better
performance and availability of the system in case a data center fails.
To illustrate, let’s divide the data, a document set, into four partitions. Since the
replication factor is three, one partition will be hosted by three nodes. We’ll
assume that there are two availability zones (AZ1 and AZ2 ). And in each
availability zone, we have two nodes. Each node acts as a primary for only one
partition (For example, Node 1 in AZ1 is the primary node for partition P1 ). The
three copies (pink, blue, and purple) for a partition are shared between the two
AZ instances so that two copies are in one zone and the third copy is in another
zone. Three colors represent three replicas of each partition. For example, the
following is true for partition P4 :
The first replica, represented by the color pink, is placed in Node 2 of AZ2
The second replica, represented by the color blue, is placed in Node 1 of
AZ2
The third replica, represented by the color purple, is placed in Node 2 of
AZ1
Each group in the illustration below consists of one replica from all of the four
partitions (P1 , P2 , P3 , P4 )
Partitions
AZ1 AZ2
Node 1 Node 2 Node 1 Node 2
P1 P1 P1 P2
P2 P3 P2 P3
P3 P4 P4 P4
The replica distribution: Each node contains one primary partition and two replicas
In the above illustration, the primary replica for P1 is indicated by the dark
purple color, the primary replica for P2 is represented by the dark blue color,
and the primary replica for P3 and P4 is represented by the dark pink color.
Now that we have completed replication, let’s see how indexing and searching
are performed in these replicas.
From the diagram above, we assume that each partition is forwarded to each
replica for index computation. Let’s look at the example where we want to index
partition P1 . This means that the same partition will be forwarded to all three
replicas in both availability zones. Therefore, each node will compute the index
simultaneously and reach the same state.
The advantage of this strategy is that the indexing operation will not suffer if the
primary node fails.
We have three copies of each partition’s index. The load balancer chooses one of
the three copies of each partition to perform the query. An increased number of
copies improves the scalability and availability of the system. Now, the system
can handle three times more queries in the same amount of time.
Summary
In this lesson, we learned how to handle a large number of data, and a large
number of queries with these strategies:
We successfully designed a system that scales with read (search) and write
(indexing) operations colocated on the same node. But, this scaling method
brings some drawbacks. We’ll look into the drawbacks and their solutions in the
next lesson.
Solution
Rather than recomputing the index on each replica, we compute the inverted
index on the primary node only. Next, we communicate the inverted index
(binary blob/file) to the replicas. The key benefit of this approach is that it avoids
using the duplicated amount of CPU and memory for indexing on replicas.
Point to Ponder
Question
Hide Answer
Since the inverted index will be transferred to the replicas, this will
introduce a transmission latency to copy the inverted index file because
the size of the index file can be very large.
When the primary node receives new indexing operations, the inverted
index file changes. Each replica needs to fetch the latest version of the
file after a certain amount of indexing operations reaches a defined
threshold.
We’ll use these technologies to redesign our distributed indexing and searching
system. There are three components involved in this search system design:
The illustration below depicts the generation and transfer of an inverted index
between an indexer and a searcher node:
Distributed storage
Local index
Local index (optional)
The indices produced by the indexing nodes are stored on the distributed storage, and the
nodes involved in the search reads indices from the distributed storage to produce a result
for the user's query
In the above illustration, a single node is shown for each indexing and searching
operation. But, in reality, there would be an N number of nodes in the indexing
phase, one node per partition (set of documents), that produces inverted indices.
The inverted index is stored in the form of binary files on the nodes’ local
storage. Caching these blob files will result in performance improvement. These
binary files are also pushed to a distributed storage. In the case of a hardware
failure, a new searcher or indexer machine is added, and a copy of the data is
retrieved from the distributed storage.
When the upload is complete, the searcher nodes download the index files.
Depending upon user search patterns, the searching nodes will maintain a cache
of frequently asked queries and serve data from RAM. A user search query will
be extended to all searcher nodes, which will generate responses according to
their respective indices. A merger node in the front-end servers will combine all
search results and present them to the user.
The indexing process indexes the new documents as soon as they are available.
At the same time, the searcher nodes fetch the updated indices to provide
improved search results.
Indexing explained
Until now, we have explained the development of a highly scalable and
performant design using low-cost nodes. However, we are unaware of the
internals of the indexing nodes. In this section, we’ll learn how indexing is
performed with a MapReduce distributed model and parallel processing
framework.
The cluster manager ensures that all worker nodes are efficiently utilized in the
cluster. The MapReduce is built to work under partial failures. If one node fails,
it reschedules the work on another node.
Note that the Reducers cannot start as long as the Mappers are working. This
means that the cluster manager can use the same node as a Mapper as well as a
Reducer.
The slides below depict a simplified setup of how MapReduce can be used to
generate an inverted index:
Cluster manager
1 of 5
p
n ma
Assig
Cluster manager
Map phase
Mapper
Mapper
Mapper
The Map phase starts and the cluster manager assigns documents partitions to the idle
nodes in the cluster. We call these nodes Mappers
2 of 5
p
n ma
Assig
Cluster manager
Map phase
Server ([8,12],[3,2])
3 of 5
p Assig
n ma n red
Assig uce
Cluster manager
Map phase Reduce phase
The Reduce phase starts, the cluster manager identifies the idle nodes for running the
Reduce function, and assigns work to the Reducers
4 of 5
p Assig
n ma n red
Assig uce
Cluster manager
Map phase Reduce phase
Distributed storage
distributed ( [1, 3], [1, 1] )
( [3], [1] )
Apache
Apache ( [11,6], [1, 1] ) ( [11,6], [1, 1] )
Reducer
Server ([8,12],[3,2]) Server ([8,12],[3,2])
The reducers combine similar terms from the assigned terms from all the mappers and put
all the entries for a term together on the distributed storage
5 of 5
To keep it simple, we have just shown two indicators for each term in the above
illustration: the list of documents in which the term appears and the list of the
frequency of the term in each document (refer to Indexing for details).
Summary
In this lesson, we have resolved two key problems of scalability (due to colocated
indexing and searching) and resource wastage (due to index recomputation) by
using dedicated nodes for indexing and searching. Both operations rely on
distributed storage. Furthermore, we presented a simplified description of the
MapReduce framework to parallelize the indexing process.
• Availability
•Evaluation
Scalability
of a Distributed Search's
•Design
Fast search on big data
• Reduced cost
• Conclusion
Availability
We utilized distributed storage to store these items:
The indexing is performed offline, not on the user’s critical path. We don’t need
to replicate the indexing operations synchronously. It is unnecessary to respond
to the user search queries with the latest data that has just been added to the
index. So, we don’t have to wait for the replication of the new index to respond
to the search queries. This makes the search available to the users.
Note: Once we replicate the latest data in all groups of indexing nodes
and the search nodes have downloaded it, then the search queries are
performed on the latest data.
Availability
Distributed
Reduced cost search system
Scalability
Fast search
Scalability
Partitioning is an essential component of search systems to scale. When we
increase the number of partitions and add more nodes to the indexing and
search clusters, we can scale in terms of data indexing and querying.
The strong isolation of indexing and search processes help indexing and search
scale independently and dynamically.
Conclusion
A search system is required for almost every application. We have seen that it
isn’t possible to develop a search system that can run on a single node. We
utilized a parallel computation framework and low-cost machines to build a
search system that is available, scalable, and highly performant.
The cluster manager orchestrates the indexing process by splitting the document set into partitions and assigning them to different nodes. It monitors node health, manages load balancing by redistributing tasks when necessary, and uses heartbeats to track node status. It also reassigns failed tasks to other nodes to ensure efficiency and continuity .
An inverted index uses a HashMap-like data structure to map terms to their occurrences in documents. This structure allows efficient retrieval of documents containing specific terms by storing the document IDs, frequencies, and positions of terms. The benefit lies in enabling quick lookups, reducing the time needed for search queries, as the search doesn’t require scanning the entire document but only the terms' index .
The main advantages of using an inverted index include facilitating full-text searches, reducing search time by eliminating the need to count occurrences of words in documents during runtime, and enabling efficient implementation of boolean and proximity searches . The disadvantages include storage overhead for maintaining the index along with actual documents, increased processing time for updating, deleting, or adding documents, and higher maintenance costs .
Low latency is achieved through distributed indexing and searching, which involves partitioned data processing and simultaneous searches on the nodes hosting tiny inverted indices. The co-location of indexing and searching on the same nodes reduces data transit times. Load balancing and query caching also contribute to faster query response times .
The MapReduce framework improves efficiency by parallelizing the indexing process. It divides the document set into partitions, with the cluster manager assigning these to Mappers. The Mappers extract terms and create partial inverted indexes in parallel, which are then processed by Reducers to generate a summarized index. This approach enhances scalability, as it enables handling partial failures by reassigning tasks if a node fails .
Colocation is beneficial because it allows parallel indexing and searching on the same nodes, improving efficiency and reducing latency. However, it can lead to resource contention, as both indexing and searching are resource-intensive processes. This can cause performance bottlenecks and scalability issues due to the uneven distribution of resources needed over time .
A centralized search system has all components on a single node, leading to scalability issues as it can become overloaded and suffers from a single point of failure (SPOF). Conversely, a distributed search system distributes tasks across multiple nodes, which improves scalability and fault tolerance. By using replication and partitioning, it reduces the impact of individual node failures and enables scaling by adding more nodes .
Returning a subset of documents based on relevance improves user experience by prioritizing the most pertinent results, reducing information overload. Sorting by relevance considers factors such as term frequency and document authority, ensuring users receive a manageable number of relevant documents, rather than an unfiltered large set which might not be useful .
Key considerations include optimizing the size of the index to minimize memory usage while ensuring fast access times, ensuring resilience and fault tolerance by handling data corruption and partitioning, and maintaining quick search speeds. Index maintenance efficiency, for updating and deleting entries, and guarding against SEO manipulation by returning only relevant results, also need to be carefully managed .
Separating indexing and searching is crucial for scalability as both processes are resource-intensive and can impede each other's performance when colocated. If not separated, resource contention can arise, leading to inefficiencies and scalability bottlenecks where neither process can operate optimally due to shared resource constraints .