System Design
  • Introduction
  • Glossary of System Design
    • System Design Basics
    • Key Characteristics of Distributed Systems
    • Scalability - Harvard lecture
      • Scalability for Dummies - Part 1: Clones
      • Scalability for Dummies - Part 2: Database
      • Scalability for Dummies - Part 3: Cache
      • Scalability for Dummies - Part 4: Asynchronism
    • Trade-off
      • CAP Theorem
      • Performance vs scalability
      • Latency vs throughput
      • Availability vs consistency
    • Load Balancing
      • Load balancer
    • Proxies
      • Reverse proxy
    • Cache
      • Caching
    • Asynchronism
    • Processing guarantee in Kafka
    • Database
      • Relational database management system (RDBMS)
      • Redundancy and Replication
      • Data Partitioning
      • Indexes
      • NoSQL
      • SQL vs. NoSQL
      • Consistent Hashing
    • Application layer
    • DNS
    • CDN
    • Communication
      • Long-Polling vs WebSockets vs Server-Sent Events
    • Security
    • Lambda Architecture
  • OOD design
    • Concepts
      • Object-Oriented Basics
      • OO Analysis and Design
      • What is UML?
      • Use Case Diagrams
    • Design a parking lot
  • System Design Cases
    • Overview
    • Design a system that scales to millions of users on AWS
    • Designing a URL Shortening service like TinyURL
      • Design Unique ID Generator
      • Designing Pastebin
      • Design Pastebin.com (or Bit.ly)
    • Design notification system (scott)
      • Designing notification service
    • Designing Chat System
      • Designing Slack
      • Designing Facebook Messenger
    • Design Top K System
    • Designing Instagram
    • Design a newsfeed system
      • Designing Facebook’s Newsfeed
      • Design the data structures for a social network
    • Designing Twitter
      • Design the Twitter timeline and search
      • Designing Twitter Search
    • Design Youtube - Scott
      • Design live commenting
      • Designing Youtube or Netflix
    • Designing a Web Crawler
      • Designing a distributed job scheduler
      • Designing a Web Crawler/Archive (scott)
      • Design a web crawler
    • Designing Dropbox
    • Design Google Doc
    • Design Metrics Aggregation System
      • Design Ads Logging System
    • Design Instacart
    • Design a payment system
      • Airbnb - Avoiding Double Payments in a Distributed Payments System
    • Design Distributed Message Queue
      • Cherami: Uber Engineering’s Durable and Scalable Task Queue in Go
    • Design Distributed Cache
      • Design a key-value cache to save the results of the most recent web server queries
    • Design a scalable file distribution system
    • Design Amazon's sales ranking by category feature
    • Design Mint.com
    • Design Autocomplete System
      • Designing Typeahead Suggestion
    • Designing an API Rate Limiter
      • Designing Rate Limiter
    • Design Google Map
      • Designing Yelp or Nearby Friends
      • Designing Uber backend
    • Designing Ticketmaster
      • Design 12306 - Scott
    • Design AirBnB or a Hotel Booking System
  • Paper Reading
    • MapReduce
  • Other Questions
    • What happened after you input the url in the browser?
Powered by GitBook
On this page
  • Design Mint.com (a financial website)
  • Step 1: Outline use cases and constraints
  • Use cases
  • Constraints and assumptions
  • Step 2: Create a high level design
  • Step 3: Design core components
  • Use case: User connects to a financial account
  • Use case: Service extracts transactions from the account
  • Use case: Service recommends a budget
  • Step 4: Scale the design
  • Additional talking points

Was this helpful?

  1. System Design Cases

Design Mint.com

Design Mint.com (a financial website)

Step 1: Outline use cases and constraints

Use cases

  • User connects to a financial account

  • Service extracts transactions from the account

    • Updates daily

    • Categorizes transactions

      • Allows manual category override by the user

      • No automatic re-categorization

    • Analyzes monthly spending, by category

  • Service recommends a budget

    • Allows users to manually set a budget

    • Sends notifications when approaching or exceeding budget

  • Service has high availability

Out of scope

  • Service performs additional logging and analytics

Constraints and assumptions

State assumptions

  • Traffic is not evenly distributed

  • Automatic daily update of accounts applies only to users active in the past 30 days

  • Adding or removing financial accounts is relatively rare

  • Budget notifications don't need to be instant

  • 10 million users

    • 10 budget categories per user = 100 million budget items

    • Example categories:

      • Housing = $1,000

      • Food = $200

      • Gas = $100

    • Sellers are used to determine transaction category

      • 50,000 sellers

  • 30 million financial accounts

  • 5 billion transactions per month

  • 500 million read requests per month

  • 10:1 write to read ratio (write-heavy usecase)

    • Write-heavy, users make transactions daily, but few visit the site daily

Calculate usage

  • Size per transaction:

    • user_id - 8 bytes

    • created_at - 5 bytes

    • seller - 32 bytes

    • amount - 5 bytes

    • Total: ~50 bytes

  • 250 GB of new transaction content per month

    • 50 bytes per transaction 5 billion transactions per month

    • 9 TB of new transaction content in 3 years

    • Assume most are new transactions instead of updates to existing ones

  • 2,000 transactions per second on average

  • 200 read requests per second on average

Handy conversion guide:

  • 2.5 million seconds per month

  • 1 request per second = 2.5 million requests per month

  • 40 requests per second = 100 million requests per month

  • 400 requests per second = 1 billion requests per month

Step 2: Create a high level design

Step 3: Design core components

Use case: User connects to a financial account

We could store info on the 10 million users in a relational database. We should discuss the use cases and tradeoffs between choosing SQL or NoSQL.

  • The Client sends a request to the Web Server, running as a reverse proxy

  • The Web Server forwards the request to the Accounts API server

  • The Accounts API server updates the SQL Database accounts table with the newly entered account info

The accounts table could have the following structure:

id int NOT NULL AUTO_INCREMENT
created_at datetime NOT NULL
last_update datetime NOT NULL
account_url varchar(255) NOT NULL
account_login varchar(32) NOT NULL
account_password_hash char(64) NOT NULL
user_id int NOT NULL
PRIMARY KEY(id)
FOREIGN KEY(user_id) REFERENCES users(id)

We'll use a public REST API:

$ curl -X POST --data '{ "user_id": "foo", "account_url": "bar", \
    "account_login": "baz", "account_password": "qux" }' \
    https://mint.com/api/v1/account

For internal communications, we could use Remote Procedure Calls.

Next, the service extracts transactions from the account.

Use case: Service extracts transactions from the account

We'll want to extract information from an account in these cases:

  • The user first links the account

  • The user manually refreshes the account

  • Automatically each day for users who have been active in the past 30 days

Data flow:

  • The Client sends a request to the Web Server

  • The Web Server forwards the request to the Accounts API server

  • The Accounts API server places a job on a Queue such as Amazon SQS or RabbitMQ

  • The Transaction Extraction Service does the following:

    • Pulls from the Queue and extracts transactions for the given account from the financial institution, storing the results as raw log files in the Object Store

    • Uses the Category Service to categorize each transaction

    • Uses the Budget Service to calculate aggregate monthly spending by category

      • The Budget Service uses the Notification Service to let users know if they are nearing or have exceeded their budget

    • Updates the SQL Database transactions table with categorized transactions

    • Updates the SQL Database monthly_spending table with aggregate monthly spending by category

    • Notifies the user the transactions have completed through the Notification Service:

      • Uses a Queue (not pictured) to asynchronously send out notifications

The transactions table could have the following structure:

id int NOT NULL AUTO_INCREMENT
created_at datetime NOT NULL
seller varchar(32) NOT NULL
amount decimal NOT NULL
user_id int NOT NULL
PRIMARY KEY(id)
FOREIGN KEY(user_id) REFERENCES users(id)

We'll create an index on id, user_id , and created_at.

The monthly_spending table could have the following structure:

id int NOT NULL AUTO_INCREMENT
month_year date NOT NULL
category varchar(32)
amount decimal NOT NULL
user_id int NOT NULL
PRIMARY KEY(id)
FOREIGN KEY(user_id) REFERENCES users(id)

We'll create an index on id and user_id.

Category service

For the Category Service, we can seed a seller-to-category dictionary with the most popular sellers. If we estimate 50,000 sellers and estimate each entry to take less than 255 bytes, the dictionary would only take about 12 MB of memory.

Clarify with your interviewer how much code you are expected to write.

class DefaultCategories(Enum):

    HOUSING = 0
    FOOD = 1
    GAS = 2
    SHOPPING = 3
    ...

seller_category_map = {}
seller_category_map['Exxon'] = DefaultCategories.GAS
seller_category_map['Target'] = DefaultCategories.SHOPPING
...

For sellers not initially seeded in the map, we could use a crowdsourcing effort by evaluating the manual category overrides our users provide. We could use a heap to quickly lookup the top manual override per seller in O(1) time.

class Categorizer(object):

    def __init__(self, seller_category_map, seller_category_crowd_overrides_map):
        self.seller_category_map = seller_category_map
        self.seller_category_crowd_overrides_map = \
            seller_category_crowd_overrides_map

    def categorize(self, transaction):
        if transaction.seller in self.seller_category_map:
            return self.seller_category_map[transaction.seller]
        elif transaction.seller in self.seller_category_crowd_overrides_map:
            self.seller_category_map[transaction.seller] = \
                self.seller_category_crowd_overrides_map[transaction.seller].peek_min()
            return self.seller_category_map[transaction.seller]
        return None

Transaction implementation:

class Transaction(object):

    def __init__(self, created_at, seller, amount):
        self.created_at = created_at
        self.seller = seller
        self.amount = amount

Use case: Service recommends a budget

To start, we could use a generic budget template that allocates category amounts based on income tiers. Using this approach, we would not have to store the 100 million budget items identified in the constraints, only those that the user overrides. If a user overrides a budget category, which we could store the override in the TABLE budget_overrides.

class Budget(object):

    def __init__(self, income):
        self.income = income
        self.categories_to_budget_map = self.create_budget_template()

    def create_budget_template(self):
        return {
            DefaultCategories.HOUSING: self.income * .4,
            DefaultCategories.FOOD: self.income * .2,
            DefaultCategories.GAS: self.income * .1,
            DefaultCategories.SHOPPING: self.income * .2,
            ...
        }

    def override_category_budget(self, category, amount):
        self.categories_to_budget_map[category] = amount

For the Budget Service, we can potentially run SQL queries on the transactions table to generate the monthly_spending aggregate table. The monthly_spending table would likely have much fewer rows than the total 5 billion transactions, since users typically have many transactions per month.

As an alternative, we can run MapReduce jobs on the raw transaction files to:

  • Categorize each transaction

  • Generate aggregate monthly spending by category

Running analyses on the transaction files could significantly reduce the load on the database.

We could call the Budget Service to re-run the analysis if the user updates a category.

Sample log file format, tab delimited:

user_id   timestamp   seller  amount

MapReduce implementation:

class SpendingByCategory(MRJob):

    def __init__(self, categorizer):
        self.categorizer = categorizer
        self.current_year_month = calc_current_year_month()
        ...

    def calc_current_year_month(self):
        """Return the current year and month."""
        ...

    def extract_year_month(self, timestamp):
        """Return the year and month portions of the timestamp."""
        ...

    def handle_budget_notifications(self, key, total):
        """Call notification API if nearing or exceeded budget."""
        ...

    def mapper(self, _, line):
        """Parse each log line, extract and transform relevant lines.

        Argument line will be of the form:

        user_id   timestamp   seller  amount

        Using the categorizer to convert seller to category,
        emit key value pairs of the form:

        (user_id, 2016-01, shopping), 25
        (user_id, 2016-01, shopping), 100
        (user_id, 2016-01, gas), 50
        """
        user_id, timestamp, seller, amount = line.split('\t')
        category = self.categorizer.categorize(seller)
        period = self.extract_year_month(timestamp)
        if period == self.current_year_month:
            yield (user_id, period, category), amount

    def reducer(self, key, value):
        """Sum values for each key.

        (user_id, 2016-01, shopping), 125
        (user_id, 2016-01, gas), 50
        """
        total = sum(values)
        yield key, sum(values)

Step 4: Scale the design

Important: Do not simply jump right into the final design from the initial design!

It's important to discuss what bottlenecks you might encounter with the initial design and how you might address each of them. For example, what issues are addressed by adding a Load Balancer with multiple Web Servers? CDN? Master-Slave Replicas? What are the alternatives and Trade-Offs for each?

We'll introduce some components to complete the design and to address scalability issues. Internal load balancers are not shown to reduce clutter.

We'll add an additional use case: User accesses summaries and transactions.

User sessions, aggregate stats by category, and recent transactions could be placed in a Memory Cache such as Redis or Memcached.

  • The Client sends a read request to the Web Server

  • The Web Server forwards the request to the Read API server

    • Static content can be served from the Object Store such as S3, which is cached on the CDN

  • The Read API server does the following:

    • Checks the Memory Cache for the content

      • If the url is in the Memory Cache, returns the cached contents

      • Else

        • If the url is in the SQL Database, fetches the contents

          • Updates the Memory Cache with the contents

Instead of keeping the monthly_spending aggregate table in the SQL Database, we could create a separate Analytics Database using a data warehousing solution such as Amazon Redshift or Google BigQuery.

We might only want to store a month of transactions data in the database, while storing the rest in a data warehouse or in an Object Store. An Object Store such as Amazon S3 can comfortably handle the constraint of 250 GB of new content per month.

To address the 200 average read requests per second (higher at peak), traffic for popular content should be handled by the Memory Cache instead of the database. The Memory Cache is also useful for handling the unevenly distributed traffic and traffic spikes. The SQL Read Replicas should be able to handle the cache misses, as long as the replicas are not bogged down with replicating writes.

2,000 average transaction writes per second (higher at peak) might be tough for a single SQL Write Master-Slave. We might need to employ additional SQL scaling patterns:

We should also consider moving some data to a NoSQL Database.

Additional talking points

Additional topics to dive into, depending on the problem scope and time remaining.

NoSQL

Caching

  • Where to cache

  • What to cache

  • When to update the cache

Asynchronism and microservices

Communications

  • Discuss tradeoffs:

Security

Latency numbers

Ongoing

  • Continue benchmarking and monitoring your system to address bottlenecks as they come up

  • Scaling is an iterative process

PreviousDesign Amazon's sales ranking by category featureNextDesign Autocomplete System

Last updated 4 years ago

Was this helpful?

We'll create an on id, user_id , and created_at to speed up lookups (log-time instead of scanning the entire table) and to keep the data in memory. Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.

Extracting transactions could take awhile, we'd probably want to do this , although this introduces additional complexity

State you would 1) Benchmark/Load Test, 2) Profile for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat. See as a sample on how to iteratively scale the initial design.

To avoid repeating discussions, refer to the following for main talking points, tradeoffs, and alternatives:

Refer to for tradeoffs and alternatives. The approach above describes .

External communication with clients -

Internal communications -

Refer to the .

See .

index
asynchronously with a queue
Design a system that scales to millions of users on AWS
system design topics
DNS
CDN
Load balancer
Horizontal scaling
Web server (reverse proxy)
API server (application layer)
Cache
Relational database management system (RDBMS)
SQL write master-slave failover
Master-slave replication
Asynchronism
Consistency patterns
Availability patterns
When to update the cache
cache-aside
Federation
Sharding
Denormalization
SQL Tuning
Key-value store
Document store
Wide column store
Graph database
SQL vs NoSQL
Client caching
CDN caching
Web server caching
Database caching
Application caching
Caching at the database query level
Caching at the object level
Cache-aside
Write-through
Write-behind (write-back)
Refresh ahead
Message queues
Task queues
Back pressure
Microservices
HTTP APIs following REST
RPC
Service discovery
security section
Latency numbers every programmer should know