dbit.one© 2026
000
booting_
Loading experience0%
dbit.one

Takroriy so‘rov puldan ikki marta yechmasligi kerak

Qisqacha: tarmoq to‘lov o‘tgan-o‘tmaganini aytmaydi — u shunchaki jim qoladi. Mijoz tugmani ikkinchi marta bosadi, mobil ilova so‘rovni o‘zi takrorlaydi, va himoyasiz siz ikki marta yechasiz. Idempotentlik jadvallar va kod darajasida qanday ishlashini hamda uni odatda qayerda buzishlarini ko‘rib chiqamiz.

6 daqiqa o‘qish

Qisqacha

  • Idempotentlik kalitini mijoz yaratadi va u foydalanuvchiga emas, niyatga bog‘lanadi.
  • Poygadan himoya — bazadagi unikal indeks, qo‘shishdan oldingi «bunday amal bormi» tekshiruvi emas.
  • Bir xil kalit, boshqa tana — bu takror emas, xato: aks holda summani almashtirib qo‘yishadi.
  • Provayderga murojaat outbox ga chiqariladi, aks holda tranzaksiya ortga qaytadi, pul esa allaqachon ketgan bo‘ladi.

Nega «yechishdan oldin tekshirish» ishlamaydi

Bu fintech mahsulotga keyinchalik qo‘shib bo‘lmaydigan uchta tugundan biri (qolgan ikkitasi — o‘zgarmas jurnal va solishtirish; umumiy manzara fintech mahsulot yaratish qo‘llanmasida). Batafsil ko‘rib chiqamiz. Mijoz yechish so‘rovini yubordi. Server uni qabul qildi, amalni o‘tkazdi — va shu payt aloqa uzildi. Mijoz javob olmadi. U yechish sodir bo‘ldimi yoki yo‘qmi bilmaydi va barcha qoidalar bo‘yicha so‘rovni takrorlashi shart: POST qo‘shimcha kelishuvsiz idempotent emas (RFC 9110).

Birinchi bo‘lib xayolga keladigan fikr — yechishdan oldin o‘xshash amal yo‘qligini tekshirish: bir xil summa, bir xil oluvchi, oxirgi besh daqiqa. Bu ikki sababga ko‘ra ishlamaydi. Birinchidan, odam do‘stiga bir xil summani ikki marta o‘tkazishga to‘liq haqli. Ikkinchidan, tekshiruv bilan qo‘shish orasida vaqt o‘tadi, va ikkita parallel so‘rov tekshiruvdan ikkalasi ham o‘tadi.

MijozQayta so‘rovTo‘lovni qabul qilishIdempotentlik kalitiAmallar jurnaliBank / ekvayringBir xil javob · Bir marta yechiladi
Takroriy so‘rov bir xil idempotentlik kaliti bilan keladi, o‘sha jurnal yozuviga tushadi va o‘sha javobni oladi. Provayderga bitta yechish ketadi.

Idempotentlik kaliti: uni kim yaratadi va nimaga ishora qiladi

Kalitni mijoz — ilova yoki frontend — foydalanuvchi niyat shakllantirgan paytda yaratadi. Server emas: server takrorni yangi niyatdan ajrata olmaydi. Va so‘rov yuborilgan paytda emas, shakl ochilgan paytda: aks holda takroriy bosish yangi kalit yaratadi va butun konstruksiya buziladi.

  • Bitta kalit — foydalanuvchining bitta niyati. O‘tkazma shaklini ochdi — kalit oldi; «yuborish» ni uch marta bosdi — kalit o‘sha.
  • Kalit tasodifiy bo‘lishi kerak (UUID), summa va vaqtdan hosil qilingan emas: ketma-ket ikkita bir xil o‘tkazma — qonuniy stsenariy.
  • Kalit cheklangan vaqt yashaydi. Bir sutka — oqilona muddat: u har qanday takrorni qoplaydi va jadvalni abadiy omborga aylantirmaydi.

Bu bazada qanday ko‘rinadi

Minimal sxema — bitta jadval. Unda uchta narsa muhim: idempotentlik kaliti bo‘yicha birlamchi kalit, so‘rov izi va saqlangan javob.

sql
create table payment_request (
  idempotency_key uuid        primary key,
  account_id      bigint      not null,
  request_hash    text        not null,
  status          text        not null
    check (status in ('in_progress', 'succeeded', 'failed')),
  response        jsonb,
  operation_id    bigint      references operation(id),
  created_at      timestamptz not null default now()
);

-- Amallar jurnali: faqat qo'shish. Bekor qilish — teskari ishorali yangi
-- yozuv, update emas va, albatta, delete emas.
create table operation (
  id         bigserial   primary key,
  account_id bigint      not null,
  amount     numeric(20, 4) not null,
  kind       text        not null,
  reverses   bigint      references operation(id),
  created_at timestamptz not null default now()
);
So‘rov tanasining izi halol takrorni almashtirishdan ajratish uchun kerak: bir xil kalit boshqa summa bilan — mijoz xatosi, takror emas.

Balans bu yerda ustun emas, hisob bo‘yicha sum(amount). Balans kimdir yangilaydigan saqlanadigan qiymatga aylanishi bilanoq, ikkinchi haqiqat manbai va «nega amallar yig‘indisi balansga to‘g‘ri kelmayapti» degan savol paydo bo‘ladi, unga yaxshi javob yo‘q.

Ishlov beruvchi: amallar tartibi muhim

Poygadan himoya — bu unikal kalit bilan insert, insert dan oldingi select emas. Qo‘shishdan oldingi tekshiruv qutqarmaydi: ikkita parallel so‘rov undan ikkalasi ham o‘tadi, chunki oralarida vaqt bor. Baza buni biz uchun hal qila oladi — unikal indeks atomar.

typescript
async function charge(req: ChargeRequest) {
  const hash = sha256(canonicalJson(req.body));

  // Kalitni band qilishga urinamiz. Agar u band bo'lsa — bu takror.
  const claimed = await db.insertIgnoreConflict('payment_request', {
    idempotency_key: req.key,
    account_id: req.accountId,
    request_hash: hash,
    status: 'in_progress',
  });

  if (!claimed) {
    const prev = await db.get('payment_request', req.key);

    // Bir xil kalit, boshqa tana — mijoz xato qildi yoki summani
    // almashtirmoqchi.
    if (prev.request_hash !== hash) throw new Conflict('key_reused');

    // Birinchi so'rov hali ishda: keyinroq takrorlashni so'raymiz,
    // lekin ikkinchi marta o'tkazmaymiz.
    if (prev.status === 'in_progress') throw new Retry('in_progress');

    // Tugagan so'rovning takrori — YANGI emas, O'SHA javobni qaytaramiz.
    return prev.response;
  }

  const result = await db.transaction(async (tx) => {
    const op = await tx.insert('operation', {
      account_id: req.accountId,
      amount: -req.body.amount,
      kind: 'charge',
    });

    // Provayderga murojaat BU YERDA emas: tranzaksiya ichidagi tarmoq
    // chaqiruvi — bu pul allaqachon ketgan holda bazada ortga qaytishni
    // olish usuli. Vazifani o'sha tranzaksiya bilan chiquvchi navbatga
    // qo'yamiz.
    await tx.insert('outbox', { type: 'provider.charge', operation_id: op.id });

    return { operationId: op.id };
  });

  await db.update('payment_request', req.key, {
    status: 'succeeded',
    response: result,
    operation_id: result.operationId,
  });

  return result;
}
Asosiy joy — onConflict bo‘lgan satr: poygani bizning kodimiz emas, baza hal qiladi.

Solishtirish: siz ko‘zda tutmagan xatolarni topadigan narsa

Idempotentlik dublikatlardan himoya qiladi, lekin farqlardan emas: provayder «qabul qilindi» deb javob bergandan keyin amalni rad etgan bo‘lishi mumkin, komissiya alohida satr bilan kelgan bo‘lishi mumkin, qaytarish sizning tizimingizdan tashqarida o‘tgan bo‘lishi mumkin. Bu haqda bilishning yagona yo‘li — o‘z jurnalingizni provayder ko‘chirmasi bilan har kuni taqqoslash.

Tungi solishtirish qanday ishlaydi
  1. 01

    Sutkalik ko‘chirmani olamiz

    Provayder qanday bersa, shundayligicha, va har qanday ishlovdan oldin xom holda saqlaymiz. Solishtirish natijasi savol tug‘dirganda asl nusxa kerak bo‘ladi.

  2. 02

    Amal identifikatori bo‘yicha moslashtiramiz

    Summa va vaqt bo‘yicha emas: summalarning mos kelishi amallarning mos kelishi emas. Provayder identifikatori sizning amalingiz yonida boshidanoq saqlanishi kerak.

  3. 03

    Farqlarni turlarga ajratamiz

    Bizda bor, ularda yo‘q. Ularda bor, bizda yo‘q. Ikkalasida ham bor, lekin summalar boshqa. Uchinchi tur — eng yoqimsizi va eng ma’lumot beruvchisi.

  4. 04

    Xat emas, vazifa ochamiz

    Mas’ul va muddatsiz farq bir haftadan keyin o‘qilmay qo‘yiladigan kundalik bildirishnomaga aylanadi.

Idempotentlik nima bermaydi

U «kamida bir marta» yetkazishni «roppa-rosa bir marta» ga aylantirmaydi — taqsimlangan tizimda bu imkonsiz. U qayta ishlashni xavfsiz qiladi, bu esa boshqa va’da: tizim so‘rovni baribir ikki marta oladi, shunchaki ikkinchi marta hech nima sodir bo‘lmaydi. Pul atrofida quradigan hamma narsangiz har qanday xabar bir martadan ko‘p kelishidan kelib chiqishi kerak.

Ko‘p so‘raladigan savollar

So‘rovlar jadvalisiz unikal indeksning o‘zi yetarlimi?

Unikal indeks ikkinchi amaldan himoya qiladi, lekin mijoz birinchisining natijasi o‘rniga xato oladi — va «allaqachon o‘tdi» ni «o‘tmadi» dan ajrata olmaydi. So‘rovlar jadvalining ma’nosi faqat himoyada emas, takroriy so‘rovga birinchisi olgan javobning aynan o‘zini qaytarishda ham.

Idempotentlik kalitlarini qancha saqlash kerak?

Bir sutka amalda har qanday takrorni qoplaydi: undan uzoqroq mijoz takrorlamaydi. Muddatdan oshgan yozuvlar jadval bo‘yicha o‘chiriladi, aks holda jadval abadiy o‘sadi. Amalning o‘zi esa hisob talab qilgancha saqlanadi — bu turli jadvallar uchun turli muddatlar.

Birinchi so‘rov hali bajarilayotgan bo‘lsa nima qaytarish kerak?

409 kodi, tushunarli tana va keyinroq takrorlash ko‘rsatmasi bilan. 200 qaytarib bo‘lmaydi: mijoz amal o‘tdi deb hisoblaydi va foydalanuvchiga hali sodir bo‘lmagan muvaffaqiyatni ko‘rsatadi. Ishlov beruvchi ichida tugashini kutish ham kerak emas — shunday qilib bloklangan ulanishlar navbatini olasiz.

Bu faqat to‘lovlarda keraksmi?

Yo‘q. Tashqi ta’sirga ega har qanday harakat — xat yuborish, bonus hisoblash, buyurtma yaratish, hujjat chop etish — xuddi shu usuldan yutadi. To‘lovlarda xato narxi shunchaki eng yuqori, shuning uchun u yerda bu haqda birinchi bo‘lib o‘ylashadi.

Manbalar

Maqoladagi da’volarni tekshirish mumkin: quyida qayta hikoya emas, birlamchi manbalar.

  1. 01RFC 9110 — HTTP Semantics, idempotent metodlarnega POST takrorlar haqida alohida kelishuvni talab qiladi
  2. 02Stripe API — Idempotent requestskalit yashash muddati va kalit mos, tana farqli bo‘lgandagi xatti-harakat
  3. 03Transactional outbox patternprovayderga tranzaksiya ichidan murojaat qilmaslik uchun
  4. 04PostgreSQL — Transaction Isolationnega «tekshirib qo‘shish» atomar amal emas
dbit.one injiniring tahririyati
Bu tizimlarni quradigan muhandislar

Maqolalarni loyihalarda ishlayotgan muhandislar yozadi — lekin ism bilan imzolanmaydi. Sabab saytda mijoz logotiplari yo‘qligi bilan bir xil: deyarli barcha loyihalar NDA yoki white-label ostida boradi, va to‘lov yadrosi haqidagi maqola muallifi mijozni logotipdan kam bo‘lmagan darajada ko‘rsatadi. Ism o‘rniga matn uchun qoidalar bilan javob beramiz — bundan mustasno: o‘z ochiq kodimiz haqidagi maqolalar muallif ismi bilan imzolanadi.

Qanday yozamiz va nimani tekshiramiz

Tegishli xizmatlar

Keyingi o‘qish

Loyihangiz bo‘yicha savollar bormi?

Vazifangizni tasvirlang — 24 soat ichida baho, muddat va reja bilan qaytamiz.

[email protected]