Username Band Qilish Qanday Ishlaydi?

username already taken meme
Siz hali yozib tugatmasizdan "Username band" deydigan tizimlar sahna ortida qanday ishlaydi?

Tasavvur qiling Instagram yoki Telegram'da o'zingizga yangi username tanlayapsiz. akarshiev deb yozdingiz - srazi qizil bo'lib "Bu band" chiqdi. Orqasiga 27 raqamini qo'shgan edingiz yashil yonib "Bo'sh" deb turibdi.

Tizim siz hali "Tasdiqlash" tugmasini bosmasangiz ham hamma narsani tekshirib ulgurdi!

Backend muhandisi sifatida ichimizdan bir ovoz:

"Bitta foydalanuvchi har bitta harfni bosganda bazaga so'rov ketayotgan bo'lsa, bir vaqtning o'zida 1 million odam yozganda baza portlab ketmaydimi?"

Bugun ushbu tizim ortidagi sirlarni birma-bir ochamiz.


Birinchi himoya: Debouncing

Agar foydalanuvchi akarshiev27 deb tez yozsa, a, ak, aka, akar... har bir harf uchun alohida so'rov yuborsak tarmoqni o'ldiramiz.

Debouncing - foydalanuvchi yozishdan to'xtagandan so'ng, ma'lum millisekund kutib, keyin serverga yagona so'rov yuborish usulidir.

Qanday ishlaydi? Tasavvur qiling siz liftga kiryapsiz. Eshik yopilmoqchi lekin boshqa odam kelyapti. Lift eshikni qayta ochadi kutadi. Yana odam kelyapti. Lift yopilmaydi kutaveradi. Faqat hamma kirganidan keyin yo'lga chiqadi.

Debouncing xuddi shunday:

Siz yozmoqdasiz:       a -> k -> a -> r -> ...
So'rovlar (debounce'siz): * -- * -- * -- * -- *  (har harf = 1 so'rov)
So'rovlar (debounce bilan): . -- . -- . -- . -- [300ms sukut] -> *  (bitta so'rov)

Kodni JavaScript'da tahminan shunday ko'rinadi:

let timer;
input.addEventListener('input', () => {
  clearTimeout(timer); // oldingi taymerni o'chiramiz
  timer = setTimeout(() => {
    checkUsername(input.value); // faqat 300ms to'xtaganidan keyin
  }, 300);
});

Foydalanuvchi yana harf bosdi taymer qaytadan boshlanadi. Faqat 300ms sukut bo'lgan sayin bitta so'rov ketadi. Bu bir feature bilan server yukini 10-20 barobarga kamaytiradi.


Ikkinchi himoya: Bloom Filter

So'rov serverga keldi. Tizimda 1 milliard username bor. Har safar SQL bazaga kirib SELECT * FROM users WHERE username = ? qilsak, baza qiynaladi.

Mana shu yerda Bloom Filter qo'llaniladi.

ByteByteGo Bloom Filter diagrammasi

Bloom Filter - bu elementning tizimda bor yoki yo'qligini deyarli xotirasiz tekshirib beradigan ehtimoliy algoritm.

Tasavvur qiling katta qog'oz va bir nechta shtamp bor. Username qo'shilganda shtamplar uni belgilaydi, bir nechta joy belgilanadi. Keyingi tekshiruvda o'sha joylarni qaraymiz.

  • Biror joy belgilanmagan -> username aniq YO'Q (100%)
  • Barchasi belgilangan -> "bo'lishi mumkin" (lekin kafolat emas)

Mana shu yerda qiziq paradoks:

Bloom Filter sizga hech qachon "Bu username aniq bor" deb 100% kafolat berolmaydi. Lekin "Bu username tizimda mutlaqo YO'Q" deb 100% aniqlik bilan ayta oladi.

Nima uchun? Ba'zan turli username'lar bir xil joyni belgilaydi (false positive). Lekin agar joy belgilanmagan bo'lsa - username hech qachon qo'shilmagan, bunga amin bo'lsa bo'ladi.

Bloom Filter so'rovlarning ~99% ini bazaga yetib bormasdan hal qiladi. Server "Bu aniq yo'q brauzerga yashil chiroq!" deydi. Baza bilan umuman aloqa bo'lmaydi!


uchinchi himoya: Redis Cluster

Agar Bloom Filter "bo'lishi mumkin" desa, biz asosiy bazaga hali bormaymiz. Redis RAM'da ishlaydigan super-tezkor kalit-qiymat ombori.

Barcha username'lar Redis'da saqlanadi. RAM bilan ishlash mikrosekundlarda o'lchanadi - PostgreSQL'dagi diskdan o'qishdan 100+ barobara tezroq.

1 million concurrent user uchun bitta Redis yetarli emas. Shuning uchun Redis Cluster quriladi:

hash("akarshiev") % 3 = Node 1
hash("alice")     % 3 = Node 2  
hash("chempion")  % 3 = Node 0

Redis 16,384 ta hash slot ishlatadi. Consistent Hashing algoritmi har bir username'ni aniq bir node'ga yo'naltiradi. Node qo'shilsa/o'chirilsa, minimal miqdordagi kalit ko'chadi.


Race Condition (eng katta daxshat)

Ikki kishi bir xil millisekundda chempion degan username'ni bosdi. Ikkalasiga ham "bo'sh" ko'rindi. Ikkalasi ham ayni paytda "Tasdiqlash" bosdi.

Dasturlashdagi eng mashhur hazil:

Knock knock!

Race condition.

Who's there?

Race Condition meme

Bu muammoni Race Condition deyiladi. Yechish uchun ikkita yo'l bor:

A. Distributed Lock (Redis SET NX EX)

Redis'da vaqtincha qulf qo'yamiz. Qulf olishda muhim nuance bor - userId emas, UUID ishlatish kerak:

// Java - to'g'ri yondashuv
String token = UUID.randomUUID().toString(); // har attempt'ga noyob ID

// SET NX EX - atomik: "agar yo'q bo'lsa qo'y, 5s keyin o'chir"
// SETNX alohida + EXPIRE alohida - NOTO'G'RI!
// (process crash bo'lsa lock abadiy qoladi)
Boolean acquired = redis.setIfAbsent("lock:" + username, token, 5, TimeUnit.SECONDS);

if (!acquired) {
    return "Bu nom band!"; // kimdir millisekund oldin oldi
}

try {
    saveToDatabase(username); // faqat biz kiritamiz
} finally {
    // faqat o'z tokenimizni o'chiramiz (boshqa clientning lockini yo'q qilmaslik uchun)
    releaseLock("lock:" + username, token);
}

Nima uchun UUID muhim?

A foydalanuvchining lock muddati tugadi, B uni oldi, keyin A ishini tugatib "men oxirgiman" deb lockni o'chirishga urindi - bu B ning lockini o'chiradi! UUID bilan bu muammo yo'q - har kim faqat o'zining tokenini o'chiroladi.

B. Database Unique Constraint - Oxirgi qalqon

Hamma himoyadan o'tib ketgan taqdirda ham, asosiy bazada username ustuniga UNIQUE INDEX qo'yilgan:

CREATE UNIQUE INDEX idx_username ON users(username);

Baza darajasida bir xil ikkita qiymat kirib qolishi imkonsiz. Ikkinchi so'rovga UniqueConstraintViolationException otiladi va tranzaksiya bekor qilinadi.

Bloom Filter, Redis, Lock - bularning barchasi optimizatsiya vositalari. Asosiy haqiqat manbasi - har doim bazadagi UNIQUE INDEX. Barcha yuqoridagi qatlamlar faqat "baza portlashini oldini olish" uchun mavjud.


Tizimning to'liq manzarasi

[Foydalanuvchi yozmoqda]
        |
   [Debounce 300ms]      <- keraksiz so'rovlarni kamaytiradi
        |
  [API Gateway]
        |
  [Bloom Filter]         <- 99% so'rovni bazasiz hal qiladi
   /          \
"Aniq yo'q"  "Bo'lishi mumkin"
   |                 |
[Yashil chiroq]  [Redis Cluster]  <- mikrosekundlar
                 /          \
              "Band"    Cache miss
                              |
                      [PostgreSQL]  <- UNIQUE INDEX
                      /          \
                   "Band"      "Bo'sh" -> Lock -> DB Write

Siz shunchaki harf yozayotganingizda, sahna ortida tarmoq so'rovlarini tejovchi Debounce, milliardlab ma'lumotni sekundiga filtrlovchi Bloom Filter, chaqmoqdek tez Redis Cluster va xavfsizlikni ta'minlovchi Distributed Lock hamda DB Unique Index parallel ravishda ishlayotgan bo'ladi.

Mana shu sababli Instagram, Telegram, GitHub kabi platformalar real-time username tekshiruvini sehrsiz amalga oshiradi.

O'ylab ko'ring: foydalanuvchi username'ini o'zgartirganida eski nom srazi bo'shashi kerakmi yoki bir muddat muzlatib qo'yilgani tizim uchun yaxshimi?

Bu maqola sizga foydali bo'ldi deb umiq qilaman. Do'stlarga ulashishni unutmang.


Manbalar