My Solution for Design Typeahead Suggestion with Score: 6/10

by zephyr_cosmos528

System requirements


Functional:

  • give the user 10 suggestions that's most popular, sort desc by popularity
  • as we build up the user history, we should return the customized suggestions
  • words are in English


Non-Functional:

  • latency - low latency, the suggestions should be returned as the user types in real time
  • availability - highly available
  • consistency - can be relaxed a little bit, it's acceptable if the popularity is updated with a delay




Capacity estimation

assume 100M DAU, average query length is 15 characters, we store 100 personalized words for each user.


TPS: 100M/24/60/60 = 1000 TPS

Storage: 100 * 100M * 365 days * 15b = 5475GB



API design

  • get_customized_suggestions(user_id, k, prefix)
  • get_suggestions(k, prefix)




Database design

We don't require strict consistency and the data is not super relational, and consider the data size we can use NoSQL.


General list

  • word
  • frequency
  • created_at
  • last_updated_at



Personalized list

  • user_id
  • word
  • frequency
  • created_at
  • last_updated_at




High-level design

In order to return customized suggestions, we store the general words and words by user in different tables. The general service will read from the general words table while the customize table will read from the words by user table from the database. Word service query both the general and customize servers, and combine the two lists together, returns the top k words to the user.


Each user query will go to the aggregation service to update the frequency in database.




Request flows

The client sends a query to LB, the LB distributes the request to a word service. The word service queries the general service to get the top k general words, and queries the customize service to get the top k customized words. It then combines the two lists and returns to the client. Afterwords, this query is sent to the aggregation service, saved to the aggregation database temporarily. A set of workers read from the aggregation database and update the main database/redis nightly.




Detailed component design

Trie

Find the top k words with a prefix from the database directly is inefficient, so we'll make use of the trie data structure.


a-p-p-l-e

-i-c-a-t-i-o-n


This Trie gives three choices: app, apple, application, when a user types the first letter 'a'. It quickly cuts off possibilities of any words starting from other letters.


On service boot up, the general services will read all general words, and build the trie in memory. If the data size is too large, we can put the general service on a consistent hash ring, managed by a zookeeper so each service handles a set of prefix. Same as the customize services, each service can store the trie for multiple users.





Trade offs/Tech choices

Explain any trade offs you have made and why you made certain tech choices...






Failure scenarios/bottlenecks

Try to discuss as many failure scenarios/bottlenecks as possible.






Future improvements

The general word list probably don't change that often, it's possible to put that on a CDN, and have an offline job to update it nightly instead of creating a set of general services.