the service will be rate limited using the token bucket algorithm and throttle by endpoints:
The main endpoints (cf API design) free user will have a limit of 100 req/hour and premium 200/hour,if user is reaching the limit to many
time, a notification will be sent to the user by mail.
API Gateaway: Entry point of the service managing auth,rate limiting,load balacing and routing to either the write or read service
AUTH: User will be authenticated with technology like JWT/OAUTH2
Write Service:
Shorten URL:From a long URL return a unique shortened URL, have some restriction : 20 URL generation/per user/30minute
Read Service:
Free plan: 30 links max generated and 300 click tracked and one custom domains
paid plan: unlimited
Performance: The service should respond to shortening requests
General constraint :
Scalability: The system should be able to scale horizontally to handle increased load and volume of URLs without significant degradation in performance.
Availability: The service should have an uptime of 99.9%, ensuring it is available to users most of the time.
Data Consistency: The mapping between shortened URLs and original URLs must be consistent, especially during concurrent access.
Security: The service should implement mechanisms for protecting against malicious use, such as preventing spam or URL abuse.
Maintainability: The codebase should be modular and organized to facilitate maintenance and future feature additions.
Usability: The user interface for shortening URLs should be user-friendly, providing an intuitive experience.
Monitoring: Monitor those metrics:
Analystics:
The app will collect usage statics which will be shown to the user
dashboard.
usage statics :
Track how many times each short URL has been accessed.
Overview: The URL Shortener API service consists of three primary components:
POST /api/auth/login: Authenticate a user and provide access tokens.POST /api/auth/logout: End user session and invalidate tokens.POST /api/auth/signup: Register a new user account.GET /api/analytics/url/{id}: Retrieve analytics for a specific shortened URL.POST /api/shorten: Create a new shortened URL.GET /api/users/{id}/urls: Retrieve all URLs associated with the user.DELETE /api/shorten/urls/{id}: Delete a specific shortened URL.PUT /api/shorten/urls/{id}: Update a specific shortened URL or its alias.Overall Design Considerations:
A URL shortener receives a long URL, returns a short code, and redirects anyone who visits that code to the original URL.
Short codes are base62 encodings of a globally unique counter (Redis INCR), so two concurrent requests can never receive the same code.
Components:
Load Balancer: distributes traffic across service instances
API Gateway: validate request
Write Service (creates short codes) : create short codes
ID Generator: generate a unique short code.
Read Service (redirects): redirect to the target url from the shorten url
Cache: store the mapping
Database:the primary database durably persists the code→URL mapping
Analytics: collect divers metrics
Storage: Aerospike CE — memory-first key-value store optimized for single-record access. Its in-memory primary index (digest, storage location, expiration metadata) gives direct get(short_code) lookups, and native TTL makes expired records invisible to clients instantly (12:00:01 → NOT_FOUND, even before background cleanup). CE lacks cross-DC replication, so multi-region needs per-region clusters. Partitioning: Fixed shard count via consistent hashing on hash(short_code); read key = shard key → single-node lookups, even spread. Consistency: Strong on write (unique, durable codes); eventual on reads, with read-your-own-write for the creator. Schema: url_mappings — short_code PK, long_url, created_at, expires_at, user_id
Component — ID Generator (collision-free + unguessable): An internal Redis INCR counter guarantees uniqueness (no two requests ever get the same number). To stop enumeration, that counter is passed through a reversible, one-to-one permutation — a Feistel network (or AES applied to the counter), keyed with a secret — and encoded base62. Because the permutation is bijective, distinct counters always map to distinct codes (no collisions), but without the secret key the output looks random, so an attacker can't reverse 4c92 into a neighbour. (Simpler alternative: random 7-char codes with a uniqueness check and retry — higher entropy, but needs a lookup per write.)
Component 3 — Redirect / Read path:
Client hits /abc123 → check Redis. Hit → redirect immediately. Miss → primary-key get(short_code) on Aerospike, populate cache, redirect. The creator's first read goes to the primary (read-your-own-write) so a brand-new link never 404s.
Component 4 — ID Generator availability (what if Redis hiccups?)
Failure handling: Redis INCR is a single point of failure in the write path — if it's down, no new codes can be minted. Mitigate with a replicated Redis plus automatic failover. To reduce round-trips at high write volume, each app instance can reserve a block of IDs (e.g. grab 1000 at once) and hand them out locally. If an instance crashes mid-block, those unused IDs are simply wasted — gaps in the code space are harmless, because uniqueness only requires never reusing a number, not using every one. Never let two instances claim the same block: block allocation itself is the atomic INCRBY.
Component 5 — Cache invalidation on edit / disable / delete
Invalidation: The cache holds short_code → long_url, so if a user disables or repoints a link, a cached entry would keep redirecting the old target (or keep a disabled link alive) until its TTL — dangerous for a link someone revoked on purpose. Fix: on every write (create/update/disable/delete), delete the cache key for that short_code (cache-aside invalidation) so the next read repopulates from Aerospike. For a multi-node cache, publish an invalidation event so every cache instance drops it, not just the one that handled the write.
Component 6 — Abuse & security):
URL validation: Accept only http/https schemes — reject javascript:, data:, file:. Resolve and block internal/private ranges (127.0.0.1, 169.254.x, 10.x, 192.168.x) so the service can't be abused as an SSRF proxy to probe our own network.
Destination screening: On create, check the target against a threat feed (Google Safe Browsing / VirusTotal); block or flag known phishing/malware. Re-scan periodically, since a link that was clean at creation can be repointed later.
Open-redirect defense: A shortener is an open redirect by design — so for links flagged suspicious, serve an interstitial warning page ("this link goes to suspect-site.com, continue?") instead of an instant 302.
Spam / enumeration: Anonymous creates require CAPTCHA; per-account + per-IP rate limits (token bucket, already designed); account quotas (already in pricing). Unguessable Feistel codes (already designed) stop bulk enumeration. Custom aliases are checked against a reserved/blocklist to prevent brand squatting.
Per-link TTL: Each link carries an absolute expires_at — a default (e.g. 30 days on free) overridable per link for premium. Stored as Aerospike's native TTL.
Deletion mechanism: Aerospike expires the record automatically — no cron job needed. From the client's view the record vanishes the instant TTL passes (lazy expiry), and background storage reclamation frees the bytes afterward. This is both mechanisms in one: infinite-lifetime cleanup with bounded latency.
Cache TTL ≤ link TTL: the Redis entry's TTL must never exceed the link's, or a revoked/expired link keeps redirecting from cache. On expiry, the cache key ages out on its own.
Response code: serve 410 Gone for a deliberately expired/revoked link (410 says "existed, now intentionally gone") vs 404 for a code that never existed — clearer for API consumers.
Analytics retention: click/analytics data outlives the link (users still want historical stats), so it's stored separately with its own longer retention window (e.g. 13 months), not tied to the link's TTL.