Detailed Component Design
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
First let's design the database schema.
- Collection 1: URLs
- Key: short ID (length 8 string in base-62)
- Field: long URL (full URL)
- We create an index on the long URL field so we can do a bidirectional mapping
- Field: created
- Field: lastAccessed
- Optional - for garbage collection feature, if we want to implement it later
- Collection 2: Banned domains
- Key: UUID
- Field: Normalized domain (string, indexed)
- Field: Reason (human-readable ban reason string)
- Field: Audited By (email of admin who banned the domain)
- Collection 3: Blocks
- Key: Block start (always a multiple of the configured block size)
- Don't need to store block_end, since it's just block_start + block_size
- Field: allocationPointer, integer, increments with each allocation
- Status: AVAILABLE, IN_USE, FULL, NOT_RETURNED
Block allocation:
- This is an allocator. We need a central repository of blocks with strong consistency.
- Each server asks for a block at startup. When it fills a block, it asks for another.
- Each block has a status. We mark a block as IN_USE while we're allocating with it, so another server doesn't use the same block.
- The server writes back the incremented allocationPointer whenever it hands out a URL. It will write this back periodically. However, to avoid congesting the ~20k writes/second for a MongoDB shard, which likely will contain all of the blocks since it's a small collection, we want to durably store the write pointer in a Redis cluster, and periodically write it back to the database (say, every 5 seconds).
- We still write back the URLs immediately, since this is a large collection and writes will go to different shards.
- If a server crashes, we can recover the allocation pointer state from Redis and fall back to rebuilding it from the URL database.
- Blocks have a timeout. If a server crashes and a block isn't returned, it is marked as NOT_RETURNED, meaning we have to do recovery.