Designing a Scalable URL Shortener: Snowflake IDs, Base62 Encoding, and Microservices

Designing a Scalable URL Shortener: Snowflake IDs, Base62 Encoding, and Microservices
Designing a Scalable URL Shortener: Snowflake IDs, Base62 Encoding, and Microservices

URL shorteners like Bit.ly or TinyURL are deceptively simple: take a long URL, generate a short one, and redirect. But when you want to scale to billions of URLs with low latency, careful system design is crucial. In this article, we’ll break down a full architecture for a production-ready URL shortener.

1. Requirements

Before we design, let’s define the requirements:

Functional Requirements:

  1. Create short URLs from long URLs.
  2. Redirect short URLs to the original URLs.
  3. Support custom / vanity URLs.
  4. Track analytics (clicks, unique users).

Non-Functional Requirements:

  1. High availability (99.9% uptime).
  2. Low latency for redirects (<50ms median).
  3. Scalability to billions of URLs.
  4. Security (rate limiting, malware detection).

2. Generating Unique Short IDs

We need a unique identifier for each URL. There are multiple approaches:

  • Auto-incrementing IDs: easy but predictable.
  • Random IDs: harder to guess.
  • Snowflake IDs: distributed, sortable, globally unique.

Snowflake IDs

Snowflake is a 64-bit unique ID generator originally used by Twitter.

BitsMeaning
1Sign bit (unused)
41Timestamp in milliseconds since custom epoch
10Machine ID (datacenter + worker)
12Sequence number per millisecond

Example Calculation:

Timestamp = 100,000 ms since epoch
Machine ID = 17
Sequence = 25

ID = (100000 << 22) | (17 << 12) | 25
   = 419,430,469,657

3. Base62 Encoding

The Snowflake ID is a large integer. To make it URL-friendly, we encode it in Base62 (0-9, A-Z, a-z):

Step-by-Step:

  1. Divide the integer by 62, store remainder as character.
  2. Repeat until quotient = 0.
  3. Reverse remainders → short string.

Example:

ID = 419,430,469,657 → Base62 = "7LLBP0o"

This short string becomes the short URL:

https://short.ly/7LLBP0o

4. High-Level Architecture

Here’s the architecture of our system:

          ┌───────────────┐
          │     User      │
          │(Browser/App)  │
          └───────┬───────┘
                  │ POST /shorten or GET /s/:id
                  ▼
          ┌───────────────┐
          │  API Gateway  │
          │  (Auth, Rate  │
          │  Limiting, LB)│
          └───────┬───────┘
                  │
      ┌───────────┴───────────┐
      ▼                       ▼
┌───────────────┐        ┌───────────────┐
│ Shortlink     │        │ Analytics      │
│ Service       │        │ Service        │
│ - Validates   │        │ - Consumes     │
│ - Generates   │        │   click events │
│   Snowflake ID│        │ - Aggregates   │
│ - Base62 enc  │        │ - Stores in OLAP│
│ - Stores DB   │        │                │
│ - Updates Redis│       │                │
└─────┬─────────┘        └───────────────┘
      │
      ▼
┌───────────────┐
│  Redis Cache  │  ← Hot cache for redirects
└─────┬─────────┘
      ▼
┌───────────────┐
│  Primary DB   │  ← Source-of-truth
│ shortlinks    │
│ schema:       │
│ - short_id    │
│ - original_url│
│ - owner_id    │
│ - created_at  │
└───────────────┘

5. Detailed Flow

Creating a Short URL

  1. User sends POST /shorten with URL (and optional custom alias).
  2. API Gateway authenticates, enforces rate limits, forwards request to Shortlink Service.
  3. Service generates a Snowflake ID, converts it to Base62, and stores it in DB and Redis cache.
  4. Returns https://short.ly/{short_id}.

Redirecting a Short URL

  1. User clicks GET /s/{short_id}.
  2. CDN / Redis cache checked for mapping.
  3. Cache hit → HTTP 301 redirect.
  4. Cache miss → query DB → update cache → redirect.
  5. Push click event to Kafka for analytics.

6. Handling Custom / Vanity URLs

  • Validate input (allowed characters, reserved words).
  • Ensure uniqueness using DB constraint.
  • For collisions, return error or ask user to choose another alias.

7. Scaling Considerations

  • Microservices: Shortlink Service, Analytics Service, User Service, etc.
  • DB sharding: shard by short_id hash to scale billions of URLs.
  • Caching: Redis + CDN to reduce latency.
  • Analytics: Kafka for asynchronous event processing.
  • Snowflake IDs: distributed unique ID generation across multiple servers.

8. JavaScript Example

// Base62 encoding
const BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
function encodeBase62(num) {
  if (num === 0) return '0';
  let str = '';
  while (num > 0) {
    str = BASE62[num % 62] + str;
    num = Math.floor(num / 62);
  }
  return str;
}

// Simplified Snowflake ID generator
class Snowflake {
  constructor(machineId = 1) {
    this.machineId = machineId & 0x3FF;
    this.sequence = 0;
    this.lastTimestamp = -1;
    this.epoch = 1700000000000;
  }
  currentTime() { return Date.now() - this.epoch; }
  nextId() {
    let timestamp = this.currentTime();
    if (timestamp === this.lastTimestamp) this.sequence = (this.sequence + 1) & 0xFFF;
    else this.sequence = 0;
    this.lastTimestamp = timestamp;
    return (BigInt(timestamp) << 22n) | (BigInt(this.machineId) << 12n) | BigInt(this.sequence);
  }
}

// Usage
const snowflake = new Snowflake(1);
const id = snowflake.nextId();
const shortId = encodeBase62(Number(id % BigInt(Number.MAX_SAFE_INTEGER)));
console.log('Short ID:', shortId);

9. Key Takeaways

  1. Snowflake IDs + Base62 → scalable, unique, URL-friendly IDs.
  2. Cache first (Redis/CDN) → low-latency redirects.
  3. Microservices + Sharding → handle billions of URLs.
  4. Asynchronous analytics → decoupled pipeline for clicks.
  5. Custom aliases → validated and constrained in DB.

This architecture can support high availability, billions of URLs, and low-latency redirects, making it suitable for production systems like Bit.ly.

List of Some important Leet code Questions:

SystemDesign#BackendEngineering#SoftwareArchitecture#Microservices#Redis#Kafka#NodeJS#Scalability#BackendDeveloper#FullStackEngineer#CloudArchitecture#WebPerformance#InterviewPrep#DistributedSystems#EngineeringDesign

11 thoughts on “Designing a Scalable URL Shortener: Snowflake IDs, Base62 Encoding, and Microservices”

  1. If you’ve built a URL shortener, what storage did you use for lookups (RDBMS, Redis, Cassandra)? Why that choice?

    1. Snowflake-style IDs enable distributed ID generation without a single DB write per new short URL, avoiding a global hotspot/bottleneck and allowing very high write throughput. They also embed time + node info which can help sorting and debugging. The tradeoffs: you need to manage clock skew and pick node IDs carefully; IDs are larger than simple auto-increments so your resulting encoded strings might be longer unless you tune bits vs. base encoding.

    1. Base62 is just an encoding — it’s bijective for integers, so collisions only happen if two different numeric IDs map to the same encoded string, which can’t happen. Real collisions come from two different systems generating the same numeric ID. Avoid that by ensuring a global uniqueness strategy: Snowflake reserves node bits and timestamp bits, or use centralized allocation. Also always verify uniqueness at creation as a safety net (cheap extra DB index check).

    1. Common patterns:

      If the local clock jumps backwards, stall ID generation until the clock catches up (simple but stalls).

      Use a per-node sequence extension to continue issuing IDs for the prior timestamp range (requires careful sequence rollover).

      Use NTP/chrony to minimize skew and monitor time drift aggressively.
      For multi-region, prefer logical time or vector-clock style solutions if monotonic ordering across regions is critical.

    1. Many systems use:

      • Primary datastore: a low-latency key-value store (Redis, DynamoDB, Cassandra) to serve redirects fast.
      • Source-of-truth: relational DB or durable store for writes and archival.
        You can combine: write-through cache with TTLs, or use DynamoDB with DAX (cache). The important part is connection pooling, local caches at CDN/edge, and a simple read path to minimize redirect latency.
    1. It depends on expected QPS and lifetime:

          For purely incremental integer IDs encoded in Base62: length ≈ log_base62(max_id).
      1. If you expect millions of links, 6–7 chars give reasonable space (62^6 ≈ 56.8B? wait — calculate: 62^6 ≈ 56,800,235,584 — enough for many systems). Choose length vs. vanity/custom domains tradeoffs and consider using variable-length encoding (no leading zeroes).

    Comments are closed.

    Scroll to Top