Problem

Marketinq komandası https://sho.rt/aZ3kP9x kimi linklər istəyir ki, uzun kampaniya URL-lərinə yönləndirsin, üstəlik neçə nəfərin klik etdiyini görmək istəyir. Servis sadə görünür və məhz buna görə populyar dizayn məsələsidir: nüvə çox kiçikdir, ona görə müzakirə onun ətrafındakı seçimlər üzərində gedir. Oxuma yazmadan qat-qat çoxdur, redirect gecikməsini hər istifadəçi hiss edir, sui-istifadə (spam, phishing) isə real təhlükədir.

Tələblər

Əvvəlcə nəyin daxil, nəyin kənar olduğunu yazın.

Funksional

  • Uzun URL üçün qısa link yaratmaq, istəyə görə fərdi alias və bitmə tarixi ilə.
  • Qısa linki hədəfinə yönləndirmək.
  • Əsas klik analitikası: link üzrə say, gün üzrə təxmini bölgü.

Qeyri-funksional

  • Redirect-lər sürətli və yüksək əlçatan olmalıdır; link yaratmaq bir qədər çox gecikməyə dözə bilər.
  • Qısa kodlar ardıcıllıqla təxmin edilməməlidir (gizli linklərin siyahılanmasının qarşısını almaq üçün).
  • Link səssizcə hədəfini dəyişməməlidir.

Bu versiyada kənarda qalanlar: istifadəçi hesabları, billing, klik üzrə fərdiləşdirmə.

Təxmini hesablamalar

Bu rəqəmlər metodu göstərmək üçün fərziyyələrdir, hansısa real servisin faktları deyil. Ayda 10 milyon yeni link və oxuma/yazma nisbəti 100:1 götürək.

  • Yazma: 10 000 000 / (30 x 86 400 san) təxminən saniyədə 4.
  • Oxuma: bunun 100 mislidir, orta hesabla saniyədə təxminən 400. Pik üçün 10 qat fərz etsək, saniyədə təxminən 4 000.
  • Yaddaş: ayda 10 milyon link, indekslər daxil sətir başına təxminən 500 bayt, ayda təxminən 5 GB, ildə 60 GB edir. Bu, ilk illərdə tək PostgreSQL primary-yə rahat sığır.

Nəticə faydalıdır: bu, data həcmi mülayim olan, oxuma ağırlıqlı sistemdir. Yükü cache və read replica-lar həll edir; sharding birinci gündən lazım deyil.

API

POST /api/v1/links
{ "targetUrl": "https://shop.test/very/long?utm=spring",
  "alias": "spring-sale",          (optional)
  "expiresAt": "2027-01-01T00:00:00Z" }   (optional)
-> 201 { "code": "aZ3kP9x", "shortUrl": "https://sho.rt/aZ3kP9x" }

GET /{code}
-> 302 Location: https://shop.test/very/long?utm=spring
-> 404 if unknown, 410 if expired or disabled

GET /api/v1/links/{code}/stats?from=2026-09-01&to=2026-09-30
-> 200 { "total": 1234,
         "byDay": [ { "date": "2026-09-01", "clicks": 40 } ] }

Hər klikin servisinizə gəlməsini (analitika üçün) və hədəfi sonradan dəyişmək imkanını saxlamaq istəyirsinizsə, 302 (müvəqqəti) işlədin. 301 brauzerlər tərəfindən agressiv cache olunur; bu yükü azaldır, amma klikləri gizlədir və sonrakı dəyişiklikləri çətinləşdirir.

Data modeli

CREATE TABLE short_link (  code        varchar(12)  PRIMARY KEY,  target_url  text         NOT NULL,  owner_id    bigint,  created_at  timestamptz  NOT NULL DEFAULT now(),  expires_at  timestamptz,  disabled    boolean      NOT NULL DEFAULT false);

code üzərindəki primary key əsas yolu, yəni dəqiq açarla axtarışı təmin edir. Klik datası başqa formadadır (yalnız əlavə olunan, yüksək həcmli), ona görə onu ayrı saxlayın, məsələn asinxron doldurulan click_daily(code, day, clicks) cədvəlində.

Kodların generasiyası

62 simvoldan (a-z, A-Z, 0-9) ibarət 7 simvollu kod 62^7, yəni təxminən 3,5 trilyon kombinasiya verir; bu, fərz etdiyimiz həcmdən çox böyükdür. Üç geniş yayılmış yanaşma:

YanaşmaGüclü tərəfiZəif tərəfi
Təsadüfi kod, insert, toqquşmada təkrarSadə, təxmin edilməzCədvəl dolduqca təkrarlar artır; unique constraint lazımdır
Base62-yə çevrilmiş sayğacToqquşma yoxdur, qısadırArdıcıl və təxmin edilə bilər, əgər rəqəm qarışdırılmasa
Bloklarla paylanan əvvəlcədən yaradılmış açar hovuzuSürətli, qaynar nöqtə yoxdurİdarə edilməli əlavə komponent

Bu həcm üçün unique constraint ilə təsadüfi kodlar kifayətdir: yarat, INSERT et, unique pozulanda yeni kodla təkrarla. Kodlar təxmin edilməməlidirsə, sadə sayğacdan çəkinin; işlədirsinizsə, onu geri qaytarıla bilən qarışdırma addımından keçirin.

Oxuma yolu və cache

Redirect axını açarla axtarışdır, ona görə cache-ə uyğundur. Caching strategiyaları məqaləsindəki kimi cache-aside işlədin:

Browser -> CDN/edge -> Redirect service -> Redis -> PostgreSQL
                              |  miss: read DB, populate Redis (TTL)
                              |  hit: respond 302 immediately
                              v
                  Click event -> Kafka -> aggregator
                                          -> click_daily

Skanerlər bazanı sıxmasın deyə mövcud olmayan kodların nəticəsini də (negative cache) qısa müddətə saxlayın. TTL-i söndürülmüş linkin əlçatan qala biləcəyi müddətdən qısa seçin və ya link söndürüləndə açarı açıq şəkildə silin.

Redirect-i yavaşlatmadan analitika

Hər klikdə verilənlər bazasına sinxron yazmayın. Kiçik klik hadisəsini (kod, vaxt, qaba metadata) Kafka kimi broker-ə göndərin, aggregator isə gündəlik sayğacları yeniləsin. Redirect sürətli qalır; analitika eventual consistent olur, bu da dashboard-lar üçün məqbuldur. Şəxsi datayla bağlı ehtiyatlı olun: yalnız məhsulun ehtiyac duyduğunu saxlayın, IP və user agent barədə qərarı toplamazdan əvvəl verin.

Oxumanın miqyaslanması və əlçatanlıq

  1. Load balancer arxasında bir neçə stateless redirect instance işlədin.
  2. Verilənlər bazasının qarşısına Redis qoyun; cache miss-lər üçün read replica-ları düşünün.
  3. CDN və ya edge cache-i yalnız söndürülmüş linklərin daha gec yayılmasına razısınızsa qoyun.
  4. Yük profilləri fərqlənirsə, yaratma və redirect-i ayrı deploy vahidləri saxlayın.

code hash-ı üzrə sharding yalnız hesablamalar ciddi səhv çıxsa aktuallaşır; hash əsaslı açar bu sonrakı keçidi asanlaşdırır.

Sui-istifadənin qarşısını almaq

Qısa linklər təyinatı gizlədir, ona görə phishing üçün cəlbedicidir. Ağlabatan nəzarətlər:

  • yaratmanı hesab və IP üzrə rate-limit etmək
  • URL sintaksisini yoxlamaq və yalnız http və https sxemlərinə icazə vermək
  • komandanın saxladığı və ya abunə olduğu reputasiya siyahısı ilə məlum pis domenləri bloklamaq
  • linki tez bildirmək və söndürmək yolu, yuxarıda qeyd olunan cache silinməsi ilə
  • servisin özünə işarə edən hədəfləri rədd edərək redirect dövrlərinin qarşısını almaq

Ucadan deyilməli güzəştlər

  • 301 və 302: performans, nəzarət və analitikaya qarşı.
  • Təsadüfi və sayğac kodları: sadəlik və gizlilik, toqquşmaların idarəsinə qarşı.
  • Sinxron və asinxron analitika: anlıq dəqiqlik, redirect gecikməsinə qarşı.
  • Fərdi alias-lar: daha dostcasına linklər, amma ad sahəsi tutula bilər və yaradılmış kodlarla toqquşur; fərqli uzunluq və ya prefiks ayırın.
  • Bitmə tarixi: təmizləmə job-u və ya oxuma zamanı lazy yoxlama tələb edir; ikisi də burada ucuzdur.

Uğursuzluq ssenariləri

Nə pozula biləcəyini də yazın. Redis əlçatmazdırsa, redirect servisi birbaşa PostgreSQL-ə düşür; bazanın bu yükü daşıya bilməsi üçün qısa timeout və rate limit lazımdır. Verilənlər bazasının primary-si dəyişirsə, yaratma bir müddət uğursuz ola bilər, amma oxuma replica və cache-dən davam edir. Kafka əlçatmaz olarsa, klik hadisələri yerli bufer və ya itkiyə razılıqla atılmalıdır; redirect bunu gözləməməlidir. Hər halda əsas qayda budur: yalnız analitika xarab olmalı, redirect yox.

Tez-tez edilən səhvlər

  1. Yükü hesablamadan sharding və multi-region replikasiya dizayn etmək.
  2. Verilənlər bazasını klik sayğacı kimi işlətmək və populyar linklərdə yazma qaynar nöqtəsi yaratmaq.
  3. Yaratmanın idempotent olmadığını unutmaq; client təkrarı iki kod yarada bilər. Idempotency pattern-ləri kimi idempotency key qəbul edin.
  4. Sui-istifadəni buraxılışdan sonraya saxlamaq.

Yoxlama siyahısı

  • Tələblər və kənarda qalanlar yazılıb
  • Hesablamalar fərziyyə kimi işarələnib və nəticə çıxarılıb
  • Naməlum və vaxtı bitmiş linklər üçün status kodlu API
  • Kod generasiyası strategiyası və toqquşma idarəsi seçilib
  • Söndürülmüş linklər üçün invalidation ilə cache strategiyası
  • Asinxron analitika yolu
  • Sui-istifadə nəzarətləri və linki götürmə yolu

Növbəti addım

Bu nümunənin arxasındakı təkrarlanan metodu system design müsahibə metodu məqaləsində öyrənin, caching strategiyalarına baxın və ya Senior Backend Developer roadmap-ını izləyin.