15 β Idempotentlik va parallellik¶
β¬ οΈ Oldingi: 14 β Rate limiting, throttling, kvotalar Β· π README Β· Keyingi: 16 β Keshlash (HTTP caching) β‘οΈ
Bu bobda: Ishonchli API'larning ikki "ko'rinmas" ustuni β idempotentlik (takroriy so'rov zarar keltirmasligi) va parallellik (ikki mijoz bir resursni bir vaqtda o'zgartirsa, biri ikkinchisini bosib ketmasligi). Idempotent metodlar,
Idempotency-Keynaqshi (Stripe uslubi), retry bilan bog'liqligi, va optimistik parallellik βETag+If-Match->412 Precondition Failedorqali lost update muammosini hal qilamiz.Halollik / Eslatma: Metod semantikasi (safe, idempotent) va shartli so'rovlar (
ETag,If-Match,412,428) RFC 9110 ga (2022) asoslanadi β bular barqaror standart.Idempotency-Keyheader esa hali IETF draft (draft-ietf-httpapi-idempotency-key-header) bo'lib, amalda keng tarqalgan konvensiya (Stripe va boshqalar) β buni halol belgilaymiz. Barcha JSON namunalar valid (tekshirilgan). Pessimistik va optimistik tanlovi kontekstga bog'liq trade-off; standartlar versiyasi vaqt bilan o'zgarishi mumkin.
Muammo: tarmoq hech qachon ishonchli emas¶
Tasavvur qiling, mijoz to'lov qiladi:
POST /v1/payments HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <token>
{"amount": 50000, "currency": "UZS", "order_id": 7}
Server to'lovni amalga oshiradi va 201 Created qaytaradi. Lekin javob mijozga yetib bormaydi β Wi-Fi uzildi, mobil tarmoq sekinlashdi yoki timeout bo'ldi. Mijoz nima ko'radi? Hech narsa. U bilmaydi: to'lov o'tdimi yoki yo'qmi?
Mijozning tabiiy reaksiyasi β qayta yuborish. Va mana shu yerda falokat:
- Agar to'lov birinchi marta o'tgan bo'lsa va mijoz qayta yuborsa -> ikkita to'lov, foydalanuvchidan ikki marta pul yechiladi.
- Agar to'lov o'tmagan bo'lsa va mijoz qayta yubormasa -> to'lov yo'qoladi.
Mijoz "o'tdimi yoki yo'q" deb bila olmagani uchun, qayta yuborishni xavfsiz qilishimiz kerak. Ya'ni: "qayta yuborsang, agar birinchisi o'tgan bo'lsa, ikkinchisi zarar qilmaydi". Bu β idempotentlik.
Eslatma: Bu faqat to'lov muammosi emas. Buyurtma yaratish, email yuborish, hisob ochish β har qanday yon ta'sirli (side-effecting) amal tarmoq uzilganda takrorlanish xavfi ostida. Ishonchli tarqalgan tizimda "kamida bir marta" (at-least-once) yetkazish odatiy; "aniq bir marta" (exactly-once) ni biz idempotentlik bilan quramiz.
Bu bobning ikkita asosiy savoli:
- Takroriy so'rov zarar keltirmasligi uchun nima qilish kerak? -> idempotentlik.
- Parallel o'zgarishda bir mijoz boshqasini "bosib ketmasligi" uchun nima qilish kerak? -> optimistik parallellik.
Idempotentlik nima¶
Idempotentlik β matematikadan kelgan tushuncha: bir amalni bir necha marta qo'llash, bir marta qo'llash bilan bir xil natija beradi (server holatida). Misol: x = 5 ni o'rnatish idempotent β necha marta yozsang ham x baribir 5. Lekin x = x + 1 idempotent emas β har takror qiymatni o'zgartiradi.
API kontekstida: bitta so'rovni N marta yuborish = bitta marta yuborish bilan bir xil server holatini qoldiradi.
Bu safe (xavfsiz) tushunchasidan farq qiladi β ko'pincha chalkashtiriladi:
| Tushuncha | Ta'rifi | Misol |
|---|---|---|
| Safe | Server holatini umuman o'zgartirmaydi (faqat o'qiydi) | GET /users/7 |
| Idempotent | O'zgartirishi mumkin, lekin takror = bir marta | PUT /users/7 |
Har bir safe metod idempotentdir (hech narsa o'zgarmasa, takror ham o'zgartirmaydi). Lekin teskarisi noto'g'ri: PUT/DELETE idempotent, ammo safe emas (ular holatni o'zgartiradi).
Metodlar va ularning xususiyatlari (RFC 9110)¶
RFC 9110 har bir HTTP metodining safe va idempotent ekanini aniq belgilaydi. Bu HTTP metodlarini chuqurroq 02 β HTTP'ni chuqurroq bobida ko'rib chiqqansiz; bu yerda idempotentlik nuqtai nazaridan jamlaymiz:
| Metod | Safe? | Idempotent? | Nega |
|---|---|---|---|
GET / HEAD |
HA | HA | Faqat o'qish, holat o'zgarmaydi |
OPTIONS |
HA | HA | Imkoniyatlarni so'raydi |
PUT |
YO'Q | HA | To'liq holatni o'rnatadi β takror bir xil |
DELETE |
YO'Q | HA | O'chgan resurs o'chgancha qoladi |
PATCH |
YO'Q | shart emas | Bog'liq: amalga qarab idempotent bo'lishi ham, bo'lmasligi ham mumkin |
POST |
YO'Q | YO'Q | Har takror yangi resurs/effekt yaratadi |
Standart: "Safe" va "idempotent" ta'riflari RFC 9110 Β§9.2.1β9.2.2 da. Idempotentlik server holatiga tegishli, javobning aynan bir xil bo'lishiga emas. Masalan, birinchi
DELETE204 No Content, ikkinchisi404 Not Foundqaytarishi mumkin β bu hali ham idempotent, chunki resurs holati (o'chgan) bir xil qoladi.
Nega PUT idempotent, POST emas¶
Bu farqni tushunish β kalit. Mohiyat: kim resurs identifikatorini belgilaydi.
PUT /users/7β mijoz aynan/users/7ni to'liq yangi holatga o'rnatadi. "Bu resursni shu holatga qil." Buni 10 marta yuborsang, resurs har safar shu holatga keladi β natija bir xil. Idempotent.
PUT /v1/users/7 HTTP/1.1
Content-Type: application/json
{"name": "Ali", "email": "ali@example.com"}
HTTP/1.1 200 OK
Content-Type: application/json
{"id": 7, "name": "Ali", "email": "ali@example.com"}
10 marta yuborsang ham /users/7 baribir aynan shu β yangi user paydo bo'lmaydi.
POST /usersβ mijoz yangi resurs yaratishni so'raydi, identifikatorni server beradi. "Yangi user qo'sh." Buni 10 marta yuborsang β 10 ta user yaratiladi, har biri yangiidbilan. Idempotent emas.
POST /v1/users HTTP/1.1
Content-Type: application/json
{"name": "Ali", "email": "ali@example.com"}
HTTP/1.1 201 Created
Location: /v1/users/7
Content-Type: application/json
{"id": 7, "name": "Ali", "email": "ali@example.com"}
Takrorlasang -> /users/8, /users/9 ... har safar yangi.
Trade-off: Agar mijoz identifikatorni o'zi yarata olsa (masalan UUID), siz
PUT /orders/{client-uuid}qilib yaratishni ham idempotent qilishingiz mumkin β "bu UUID'li buyurtmani shu holatga o'rnat". Ammo ko'p API'lar yaratish uchunPOST(server ID beradi) ni saqlaydi, chunki bu RESTful va tabiiy. Shu sababPOSTni idempotent qilishning boshqa mexanizmi kerak βIdempotency-Key.
Idempotency-Key β POST'ni idempotent qilish¶
POST (va idempotent bo'lmagan PATCH) tabiatan takrorga xavfli. Lekin to'lov, buyurtma yaratish kabi amallar aynan POST orqali ketadi. Yechim β mijoz har bir mantiqiy operatsiya uchun unikal kalit yuboradi:
POST /v1/payments HTTP/1.1
Host: api.example.com
Content-Type: application/json
Idempotency-Key: 9f8a2c14-7b3e-4d21-a0f6-1e5c8b2d4a90
Authorization: Bearer <token>
{"amount": 50000, "currency": "UZS", "order_id": 7}
G'oya oddiy:
- Mijoz unikal kalit yaratadi (odatda UUID) va
Idempotency-Keyheader'ida yuboradi. - Server kalitni ko'radi: avval ko'rganmi?
- Yo'q -> operatsiyani bajaradi, kalit + natijaviy javobni saqlaydi, javobni qaytaradi.
- Ha -> operatsiyani qayta bajarmaydi; saqlangan o'sha javobni qaytaradi.
- Mijoz tarmoq uzilib qayta yuborsa, aynan o'sha kalit bilan yuboradi -> server takrorlamaydi.
Standart:
Idempotency-Keyheader hali rasmiy RFC emas β u IETF draft (draft-ietf-httpapi-idempotency-key-header). Ammo amalda u de-fakto standart: Stripe, PayPal, Adyen va ko'p to'lov/buyurtma API'lari aynan shu nom va naqshdan foydalanadi. Draft ekanini hujjatingizda halol ayting, lekin naqsh ishonchli va keng qo'llaniladi.
Server tomonida β saqlanadigan narsa¶
Server har kalit uchun kamida quyidagini saqlaydi:
{
"idempotency_key": "9f8a2c14-7b3e-4d21-a0f6-1e5c8b2d4a90",
"request_fingerprint": "sha256:3a1f...",
"status": "completed",
"response_status": 201,
"response_body": {"id": "pay_1001", "status": "succeeded", "amount": 50000},
"created_at": "2026-06-16T10:00:00Z",
"expires_at": "2026-06-17T10:00:00Z"
}
request_fingerprintβ so'rov tanasining xeshi. Agar mijoz bir xil kalit, lekin boshqa tana yuborsa (masalan boshqa summa), bu xato β server422 Unprocessable Contentqaytarishi kerak ("bu kalit boshqa so'rov uchun ishlatilgan"). Bu kalitni tasodifan qayta ishlatishdan himoya qiladi.expires_atβ kalitlar abadiy saqlanmaydi (odatda 24 soat). Muddat o'tgach, o'sha kalit yangi operatsiya sifatida qabul qilinishi mumkin.
Qirra holatlar β bu yerda chinakam injenerlik¶
Sodda ko'rinadi, lekin tarqalgan tizimda qiyin holatlar bor:
1. Parallel kelgan ikki so'rov (bir xil kalit, deyarli bir vaqtda). Mijoz timeout'dan keyin birinchisi hali tugamasdan qayta yuborsa, server ikkala so'rovni bir vaqtda ko'radi. Yechim β kalit bo'yicha lock: birinchisi kalitni "bajarilmoqda" (in-progress) holatida band qiladi; ikkinchisi shu holatni ko'rib, 409 Conflict qaytaradi yoki birinchisi tugashini kutadi.
POST /v1/payments HTTP/1.1
Idempotency-Key: 9f8a2c14-7b3e-4d21-a0f6-1e5c8b2d4a90
Content-Type: application/json
{"amount": 50000, "currency": "UZS", "order_id": 7}
HTTP/1.1 409 Conflict
Content-Type: application/problem+json
{
"type": "https://api.example.com/errors/idempotency-in-progress",
"title": "So'rov hali bajarilmoqda",
"status": 409,
"detail": "Bu Idempotency-Key bilan so'rov hozir qayta ishlanmoqda, biroz kutib qayta urinib ko'ring"
}
2. Birinchi so'rov yarmida o'chib qolsa. Operatsiya boshlandi, lekin javob saqlanmasdan server qulab tushdi. Bu sabab kalit yozuvi va asosiy operatsiya bir atomik tranzaksiyada bo'lishi yoki kalitga aniq "in-progress/completed/failed" holat berilishi muhim.
3. Muddat (TTL). Mijoz juda kech (masalan 2 kundan keyin) o'sha kalit bilan kelsa β kalit allaqachon o'chgan. Server uni yangi operatsiya deb qabul qiladi. Shuning uchun TTL'ni mijoz retry oynasidan kattaroq tanlang.
Trade-off: Idempotency-Key barcha endpoint uchun emas. U idempotent bo'lmagan, yon ta'sirli amallar uchun:
POST /payments,POST /orders,POST /transfers.GETallaqachon safe,PUT/DELETEallaqachon idempotent β ularga kalit kerak emas. Kalitni majburiy qilasizmi yoki ixtiyoriy, bu siyosat masalasi: pul bilan bog'liq kritik endpoint'larda ko'pincha majburiy qilinadi.
Resurs konvensiyasi¶
Idempotency-Key ni mijoz yaratadi va u butun operatsiya umrida o'zgarmaydi. Muhim qoidalar:
- Har yangi mantiqiy operatsiya uchun yangi kalit. "To'lovni qayta urinish" -> o'sha kalit; "yangi to'lov" -> yangi kalit.
- Kalit mijoz tomonida retry'lar orasida saqlanishi kerak (xotirada yoki diskda) β aks holda retry yangi kalit bilan ketib, idempotentlik buziladi.
- UUIDv4 yoki shunga o'xshash global-unikal qiymat tavsiya etiladi (taxmin qilib bo'lmaydigan).
Retry va idempotentlik β bir-birini to'ldiradi¶
Idempotentlik xavfsiz qayta urinish (safe retry) uchun zamin yaratadi. Mijoz quyidagi javoblardan keyin qayta urinishni xohlaydi:
429 Too Many Requestsβ rate limit (qarang 14 β Rate limiting). ServerRetry-Afterbilan qachon urinishni aytadi.503 Service Unavailable,502,504β vaqtinchalik server muammosi.- Tarmoq xatosi / timeout β javob umuman kelmadi.
Lekin idempotentlik bo'lmasa, retry xavfli: POST /payments ni qayta urinish ikkinchi to'lovga olib keladi. Shu sabab qoida:
Faqat idempotent operatsiyalarni (yoki
Idempotency-Keybilan himoyalangan operatsiyalarni) avtomatik qayta urinish mumkin.
Exponential backoff¶
Qayta urinishni darhol, ketma-ket qilish serverni yanada bo'g'adi ("retry storm"). To'g'ri usul β eksponensial backoff + jitter: har urinishda kutish vaqtini oshirib borish, ustiga ozgina tasodif qo'shish.
| Urinish | Kutish (taxminan) |
|---|---|
| 1 | darhol |
| 2 | ~1 soniya |
| 3 | ~2 soniya |
| 4 | ~4 soniya |
| 5 | ~8 soniya (+ jitter) |
Diqqat: Server
Retry-Afterheader bersa, uni hurmat qiling β o'zingizning backoff'ingizdan ustun qo'ying. Bu429va503da odatiy. Cheksiz urinmang: maksimal urinishlar soni va umumiy vaqt chegarasi qo'ying.
Demak: idempotentlik + backoff = ishonchli mijoz. Biri ikkinchisiz to'liq emas.
Ikkinchi muammo: parallel o'zgarish va lost update¶
Endi boshqa muammoga o'tamiz. Faraz qiling, ikki muharrir bir hujjatni tahrirlaydi:
- A hujjatni o'qiydi: sarlavha = "Loyiha".
- B ham o'qiydi: sarlavha = "Loyiha".
- A sarlavhani "Loyiha 2026" ga o'zgartirib saqlaydi.
- B (eski nusxani ko'rib) sarlavhani "Yangi loyiha" ga o'zgartirib saqlaydi.
Natija: A ning o'zgarishi butunlay yo'qoladi β B uni bilmasdan bosib ketdi. Bu lost update (yo'qolgan yangilanish) muammosi. B o'zining yozayotgan nusxasi allaqachon eskirganini bilmasdan yozdi.
Bu β to'lov takrorlanishidan boshqa muammo. Bu yerda gap takror emas, bir vaqtdagi raqobat (concurrency). API stateless bo'lgani uchun (05 β REST tamoyillari) server "kim qachon o'qigan"ini eslamaydi β har so'rov mustaqil. Demak yechim so'rovning o'zida bo'lishi kerak.
Optimistik parallellik β ETag + If-Match¶
Yechim nomi: optimistik parallellik nazorati (optimistic concurrency control). "Optimistik" β chunki biz "konflikt kam bo'ladi" deb taxmin qilamiz va lock qo'yib kutmaymiz; o'rniga yozish paytida tekshiramiz: "men o'qiganimdan beri resurs o'zgardimi?"
Buni HTTP'da shartli so'rovlar (conditional requests) amalga oshiradi (RFC 9110 Β§13):
ETagβ resursning joriy versiyasi (entity tag). Server uniGETjavobida beradi. Bu odatda mazmun xeshi yoki versiya raqami.If-Matchβ mijozPUT/PATCH/DELETEda "men shu versiyani o'zgartiryapman" deb yuboradi. Server tekshiradi: agar joriyETagmos kelmasa (ya'ni resurs orada o'zgardi) ->412 Precondition Failed, yozishni rad etadi.
To'liq oqim¶
Qadam 1 β o'qish, ETag olish:
GET /v1/documents/7 HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
HTTP/1.1 200 OK
ETag: "v1-3a8f"
Content-Type: application/json
{"id": 7, "title": "Loyiha", "body": "..."}
Mijoz endi "v1-3a8f" ni "qo'lida tutadi".
Qadam 2 β shartli yozish:
PUT /v1/documents/7 HTTP/1.1
Host: api.example.com
If-Match: "v1-3a8f"
Content-Type: application/json
Authorization: Bearer <token>
{"id": 7, "title": "Loyiha 2026", "body": "..."}
Server tekshiradi:
- Joriy
ETaghali"v1-3a8f"mi? -> HA -> yozadi, yangiETagqaytaradi:
HTTP/1.1 200 OK
ETag: "v2-9c1d"
Content-Type: application/json
{"id": 7, "title": "Loyiha 2026", "body": "..."}
- Orada kimdir o'zgartirib, joriy
ETagendi"v2-..."bo'lib qolsa -> mos kelmaydi -> rad:
HTTP/1.1 412 Precondition Failed
Content-Type: application/problem+json
{
"type": "https://api.example.com/errors/version-conflict",
"title": "Versiya konflikti",
"status": 412,
"detail": "Hujjat siz o'qiganingizdan beri o'zgargan; eng yangi nusxani qayta oling"
}
Mijoz 412 ni ko'rib, qayta GET qiladi, yangi nusxani oladi, o'zgarishini unga qo'shadi (yoki foydalanuvchidan so'raydi) va qayta urinadi. A ning ishi yo'qolmaydi.
Standart:
If-Matchmos kelmaganda412 Precondition Failedqaytariladi (RFC 9110 Β§13.1.1 / Β§15.5.13).If-Match: *esa "resurs umuman mavjud bo'lsa" degani β yo'q bo'lsa ham 412. Bu yerda biz kuchli (strong) ETag ishlatamiz β bayt-aniq taqqoslash uchun.
428 β shartli so'rovni majburlash¶
Yuqorida muammo: agar mijoz If-Match ni umuman yubormasa, server eski uslubda ko'r-ko'rona yozib qo'yadi va lost update qaytadi. Buni oldini olish uchun server If-Matchsiz yozishni rad etishi mumkin:
PUT /v1/documents/7 HTTP/1.1
Content-Type: application/json
{"id": 7, "title": "..."}
HTTP/1.1 428 Precondition Required
Content-Type: application/problem+json
{
"type": "https://api.example.com/errors/precondition-required",
"title": "If-Match shart",
"status": 428,
"detail": "Bu resursni yangilash uchun If-Match header bilan joriy ETag'ni yuboring"
}
Standart:
428 Precondition Required(RFC 6585, RFC 9110 ham qamraydi) β "shartli so'rovni majburla". Bu lost update'ni strukturaviy ravishda imkonsiz qiladi: mijoz avvalGETqilibETagolmasa, yoza olmaydi.
409 va 412 β qaysi biri qachon¶
Ikkalasi ham "konflikt" haqida, lekin farqi muhim:
| Status | Ma'nosi | Qachon |
|---|---|---|
412 Precondition Failed |
Yuborilgan shart (If-Match ETag) joriy holatga mos kelmadi |
Optimistik parallellik: ETag eskirgan |
409 Conflict |
So'rov resursning joriy holati bilan ziddiyatda (shartdan mustaqil) | Biznes-qoidaviy ziddiyat: dublikat email, allaqachon band slot, in-progress idempotent operatsiya |
Soddasi: 412 β "sening versiyang eski"; 409 β "bu amal hozirgi holatda mantiqan mumkin emas". Optimistik parallellikning toza signali β 412.
Optimistik va pessimistik nazorat¶
Parallellikni boshqarishning ikki yondashuvi bor:
| Mezon | Optimistik (ETag + If-Match) | Pessimistik (lock) |
|---|---|---|
| G'oya | "Konflikt kam" β yozish paytida tekshir | "Avval bloklab ol, keyin o'zgartir" |
| Holat | Stateless β server hech narsa eslamaydi | Stateful β lock saqlanadi |
| Mos keladi | Web API, yuqori o'qish/kam konflikt | Kam, lekin kritik raqobat (bank ichki tizimi) |
| Kamchilik | Konflikt bo'lsa mijoz qayta urinadi | Lock ushlab turish, deadlock, masshtab qiyin |
| HTTP'da | Tabiiy (ETag/If-Match) |
Tabiiy emas (stateless'ga zid) |
Web API'larda optimistik yondashuv deyarli har doim afzal β chunki HTTP stateless (05 β REST tamoyillari) va lock ushlab turish masshtablashga to'sqinlik qiladi. Konfliktlar kam bo'lgani uchun, ularni paydo bo'lganda hal qilish arzon. Pessimistik lock ko'proq ma'lumotlar bazasi darajasida (qisqa tranzaksiya ichida) ishlatiladi, API shartnomasida emas.
Trade-off: Konflikt tez-tez bo'ladigan resurslarda (masalan, bir kishi yangilaydigan global hisoblagich) optimistik yondashuv ko'p
412keltirib chiqaradi β bu yerda boshqa dizayn (masalan, server tomonida atomik inkrement:POST /counter/increment) yaxshiroq. Optimizmni konflikt darajasiga moslang.
ETag: parallellik va keshlash β bir header, ikki vazifa¶
Diqqat: ETag ni siz 16 β Keshlash bobida ham ko'rasiz, lekin boshqa maqsadda. Bir header, ikki ishlatilishi bor:
- Keshlash (16-bob): mijoz
If-None-Match: "<etag>"yuboradi -> agar resurs o'zgarmagan bo'lsa, server304 Not Modifiedqaytaradi (tana yubormaydi, trafik tejaydi). Bu o'qishni optimallashtiradi. - Parallellik (bu bob): mijoz
If-Match: "<etag>"yuboradi -> agar resurs o'zgargan bo'lsa, server412 Precondition Failedqaytaradi (yozishni rad etadi). Bu yozishni xavfsiz qiladi.
Eslab qoling: If-None-Match = keshlash (o'zgarmagan bo'lsa 304); If-Match = parallellik (o'zgargan bo'lsa 412). Ikkisi ETag ning teskari yo'nalishlari. Bu yerda biz faqat parallellik β If-Match β bilan shug'ullandik.
Eslatma: Bir xil
ETagqiymatini ikkala maqsadda ham ishlatish mumkin va odatiy. Server uni bir marta hisoblaydi (masalan, resurs versiyasi yoki mazmun xeshi), mijoz esa kontekstga qarabIf-MatchyokiIf-None-Matchda yuboradi.
Hammasi birga β to'lov endpointi dizayni¶
Idempotentlik va parallellikni bir misolda jamlaymiz. To'lov yaratish (POST, idempotency-key bilan), keyin to'lov holatini yangilash (PATCH, ETag bilan):
1. To'lov yaratish β Idempotency-Key bilan:
POST /v1/payments HTTP/1.1
Host: api.example.com
Content-Type: application/json
Idempotency-Key: 9f8a2c14-7b3e-4d21-a0f6-1e5c8b2d4a90
Authorization: Bearer <token>
{"amount": 50000, "currency": "UZS", "order_id": 7}
HTTP/1.1 201 Created
Location: /v1/payments/pay_1001
ETag: "p-v1"
Content-Type: application/json
{"id": "pay_1001", "status": "pending", "amount": 50000}
Mijoz timeout'da shu Idempotency-Key bilan qayta yuborsa β bir xil 201 va pay_1001 qaytadi, ikkinchi to'lov yaratilmaydi.
2. To'lov holatini yangilash β ETag bilan (parallellikdan himoya):
PATCH /v1/payments/pay_1001 HTTP/1.1
Host: api.example.com
If-Match: "p-v1"
Content-Type: application/json
Authorization: Bearer <token>
{"status": "cancelled"}
HTTP/1.1 200 OK
ETag: "p-v2"
Content-Type: application/json
{"id": "pay_1001", "status": "cancelled", "amount": 50000}
Agar orada boshqa jarayon to'lovni succeeded ga o'tkazgan bo'lsa, "p-v1" eskirgan -> 412 Precondition Failed. Mijoz qayta GET qilib holatni ko'radi va "allaqachon bajarilgan to'lovni bekor qila olmayman" degan qarorni to'g'ri qabul qiladi. Ikki xil himoya β biri takrordan, biri raqobatdan.
Asosiy g'oyalar (bobni qisqacha)¶
- Tarmoq ishonchsiz β mijoz javobni olmasa qayta yuboradi. Idempotentlik bu takrorni xavfsiz qiladi: N marta yuborish = 1 marta yuborish (server holatida).
- Safe = holatni o'zgartirmaydi (
GET); idempotent = o'zgartirishi mumkin, lekin takror = bir marta (PUT,DELETE).POSTikkalasi ham emas;PATCHidempotent bo'lishi shart emas (RFC 9110). PUTidempotent β to'liq holat o'rnatadi;POSTemas β har takror yangi yaratadi.Idempotency-Key(IETF draft, Stripe naqshi)POST/PATCHni idempotent qiladi: mijoz unikal kalit yuboradi, server kalit+javobni saqlaydi, takrorni qayta bajarmaydi. Lock, fingerprint, TTL β muhim qirralar.- Faqat idempotent yoki kalit bilan himoyalangan operatsiyalarni avtomatik qayta urinish mumkin (
429/5xx/timeout'dan keyin), eksponensial backoff + jitter bilan;Retry-Afterni hurmat qiling. - Lost update β ikki mijoz bir resursni parallel o'zgartirsa, biri ikkinchisini bosib ketadi. Optimistik parallellik (
ETag+If-Match->412) buni hal qiladi (RFC 9110 Β§13). 428 Precondition RequiredbilanIf-Matchni majburlang.412= versiyang eski;409= amal joriy holatda mantiqsiz.- Web API'da optimistik yondashuv pessimistik lock'dan afzal β HTTP stateless.
ETagham keshlash (If-None-Match-> 304), ham parallellik (If-Match-> 412) uchun ishlatiladi.
Mashqlar¶
Oson¶
1-mashq. Quyidagi metodlarni safe va idempotent bo'yicha tasniflang va har biriga sabab yozing: GET, POST, PUT, DELETE, PATCH.
2-mashq. Mijoz POST /v1/orders yuboradi, lekin tarmoq uzilib javobni olmaydi. U qayta yuboradi. Hech qanday himoya bo'lmasa, qanday muammo yuzaga keladi? Bu nima deb ataladi?
3-mashq. DELETE /v1/users/7 ni ikki marta yuborsangiz, birinchisi 204 No Content, ikkinchisi 404 Not Found qaytaradi. Bu metod hali ham idempotentmi? Javobingizni asoslang.
O'rta¶
4-mashq. Idempotency-Key oqimini o'z so'zlaringiz bilan tushuntiring: mijoz nima yuboradi, server nimani saqlaydi, takroriy so'rov kelganda nima sodir bo'ladi.
5-mashq. Optimistik parallellik uchun to'liq GET -> PUT If-Match HTTP almashuvini yozing (header + status), shu jumladan resurs orada o'zgargan holat uchun javobni.
6-mashq. Quyidagi endpoint'lardan qaysilariga Idempotency-Key mantiqan kerak, qaysilariga kerak emas, va nega? GET /v1/users/7, POST /v1/payments, PUT /v1/users/7, POST /v1/orders, DELETE /v1/orders/7.
Qiyin¶
7-mashq. POST /v1/transfers (pul o'tkazish) endpointini idempotent qilib to'liq dizayn qiling: mijoz nima yuboradi, server kalit ombarida nima saqlaydi, va uchta qirra holatni (parallel kelgan ikki so'rov; bir xil kalit + boshqa tana; muddat o'tgan kalit) qanday hal qilasiz. Har biriga mos status kodni ko'rsating.
8-mashq. Hujjat tahrirlash stsenariysi: muharrir A va B bir hujjatni o'qiydi, A saqlaydi, keyin B saqlaydi. Lost update qanday sodir bo'lishini ketma-ket ko'rsating, so'ng ETag + If-Match optimistik yechimini bosqichma-bosqich qo'llang. 428 ning roli nima?
9-mashq. 409 Conflict va 412 Precondition Failed farqini misollar bilan tushuntiring: qaysi biri optimistik parallellik konflikti uchun, qaysi biri biznes-qoidaviy ziddiyat uchun? Har biriga bittadan HTTP misol bering.
Yechimlar
1-mashq yechimi¶
| Metod | Safe | Idempotent | Sabab |
|---|---|---|---|
GET |
HA | HA | Faqat o'qiydi, holatni umuman o'zgartirmaydi |
POST |
YO'Q | YO'Q | Har takror yangi resurs/effekt yaratadi |
PUT |
YO'Q | HA | To'liq holatni o'rnatadi β takror bir xil natija |
DELETE |
YO'Q | HA | O'chgan resurs o'chgancha qoladi; takror holatni o'zgartirmaydi |
PATCH |
YO'Q | shart emas | Amalga bog'liq: {"status":"done"} idempotent, {"balance":"+10"} emas |
Qoida: har safe metod idempotentdir, lekin har idempotent metod safe emas (PUT/DELETE idempotent, ammo holatni o'zgartiradi). Bu ta'riflar RFC 9110 dan.
2-mashq yechimi¶
Hech qanday himoya bo'lmasa, ikkala so'rov ham serverga yetib boradi (faqat birinchisining javobi mijozga yetmagan). Server POST ni idempotent emas deb biladi, shuning uchun ikkita alohida buyurtma yaratadi β foydalanuvchi bitta narsani buyurtma qilib, ikkita to'lov/yetkazib berish oladi. Bu dublikat (takroriy) so'rov muammosi. Yechimi β Idempotency-Key: mijoz ikkala urinishda bir xil kalit yuboradi, server ikkinchisini qayta bajarmasdan birinchisining saqlangan javobini qaytaradi.
3-mashq yechimi¶
Ha, hali ham idempotent. Idempotentlik server holatiga tegishli, javob status kodiga emas (RFC 9110 Β§9.2.2). Birinchi DELETE dan keyin resurs o'chadi; ikkinchi, uchinchi DELETE dan keyin resurs baribir o'chgan β server holati o'zgarmaydi. Status kod farqi (204 keyin 404) muhim emas: 404 shunchaki "bu resurs allaqachon yo'q" deb aytadi. Resurs holati barqaror bo'lgani uchun metod idempotent.
4-mashq yechimi¶
Mijoz har bir mantiqiy operatsiya uchun unikal kalit yaratadi (odatda UUIDv4) va uni Idempotency-Key header'ida yuboradi. Retry'lar orasida o'sha kalitni saqlaydi.
Server kalitni ko'rib:
- Kalit yangi -> operatsiyani bajaradi, so'ng kalit + so'rov fingerprint + natijaviy javob (status + tana) ni TTL bilan saqlaydi, javobni qaytaradi.
- Kalit allaqachon bor (completed) -> operatsiyani qayta bajarmaydi, saqlangan o'sha javobni qaytaradi.
Takroriy so'rov (tarmoq uzilgach mijoz qayta yuboradi) bir xil kalit bilan keladi -> server saqlangan javobni qaytaradi -> ikkinchi marta effekt (masalan to'lov) sodir bo'lmaydi. Shu tariqa "kamida bir marta" yetkazishda ham "aniq bir marta" effekti olinadi.
5-mashq yechimi¶
Qadam 1 β o'qish:
GET /v1/documents/7 HTTP/1.1
Host: api.example.com
HTTP/1.1 200 OK
ETag: "v1-3a8f"
Content-Type: application/json
{"id": 7, "title": "Loyiha"}
Qadam 2a β yozish, resurs o'zgarmagan (muvaffaqiyat):
PUT /v1/documents/7 HTTP/1.1
If-Match: "v1-3a8f"
Content-Type: application/json
{"id": 7, "title": "Loyiha 2026"}
HTTP/1.1 200 OK
ETag: "v2-9c1d"
Content-Type: application/json
{"id": 7, "title": "Loyiha 2026"}
Qadam 2b β yozish, lekin resurs orada o'zgargan (konflikt):
PUT /v1/documents/7 HTTP/1.1
If-Match: "v1-3a8f"
Content-Type: application/json
{"id": 7, "title": "Loyiha 2026"}
HTTP/1.1 412 Precondition Failed
Content-Type: application/problem+json
{
"type": "https://api.example.com/errors/version-conflict",
"title": "Versiya konflikti",
"status": 412,
"detail": "Resurs siz o'qiganingizdan beri o'zgargan; qayta GET qiling"
}
412 ni olgan mijoz qayta GET qiladi, eng yangi ETag va mazmunni oladi, o'zgarishini qo'shadi va qayta urinadi.
6-mashq yechimi¶
| Endpoint | Kalit kerakmi | Sabab |
|---|---|---|
GET /v1/users/7 |
YO'Q | Safe β holatni o'zgartirmaydi, takror zararsiz |
POST /v1/payments |
HA | Idempotent emas, yon ta'sirli (pul) β takror = ikki to'lov |
PUT /v1/users/7 |
YO'Q | Allaqachon idempotent β takror bir xil holat o'rnatadi |
POST /v1/orders |
HA | Idempotent emas β takror = ikki buyurtma |
DELETE /v1/orders/7 |
YO'Q | Allaqachon idempotent β takror o'chgan holatni saqlaydi |
Umumiy qoida: kalit idempotent bo'lmagan, yon ta'sirli amallar (POST yaratish, idempotent bo'lmagan PATCH) uchun. Safe (GET) va tabiatan idempotent (PUT/DELETE) metodlarga kerak emas.
7-mashq yechimi¶
Mijoz yuboradi:
POST /v1/transfers HTTP/1.1
Host: api.example.com
Content-Type: application/json
Idempotency-Key: 7c2e9b40-1a3d-4f8e-9012-ab34cd56ef78
Authorization: Bearer <token>
{"from": "acc_11", "to": "acc_22", "amount": 100000, "currency": "UZS"}
Server kalit ombarida saqlaydi:
{
"idempotency_key": "7c2e9b40-1a3d-4f8e-9012-ab34cd56ef78",
"request_fingerprint": "sha256:9b1c...",
"status": "completed",
"response_status": 201,
"response_body": {"id": "trf_5001", "status": "settled", "amount": 100000},
"expires_at": "2026-06-17T10:00:00Z"
}
Qirra holatlar:
- Parallel kelgan ikki so'rov (bir xil kalit). Birinchisi kalitni
in-progressholatida lock qiladi. Ikkinchisi shu lock'ni ko'radi va409 Conflict("so'rov hali bajarilmoqda") qaytaradi yoki birinchisi tugashini kutib, keyin saqlangan javobni qaytaradi. Ikki o'tkazma sodir bo'lmaydi.
HTTP/1.1 409 Conflict
Content-Type: application/problem+json
{"type": "https://api.example.com/errors/idempotency-in-progress",
"title": "So'rov bajarilmoqda", "status": 409}
- Bir xil kalit + boshqa tana (masalan boshqa
amount). Server saqlanganrequest_fingerprintbilan yangi so'rov xeshini solishtiradi β mos kelmaydi ->422 Unprocessable Content("bu kalit boshqa so'rov uchun ishlatilgan"). Bu kalitni tasodifan qayta ishlatishdan himoya qiladi.
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{"type": "https://api.example.com/errors/idempotency-key-reuse",
"title": "Kalit boshqa so'rov uchun ishlatilgan", "status": 422}
- Muddat o'tgan kalit. Kalitning
expires_ato'tgan (masalan 2 kun keyin) -> ombarda yo'q -> server uni yangi operatsiya deb qabul qiladi va o'tkazmani bajaradi. Shuning uchun TTL'ni mijozning retry oynasidan kattaroq (masalan 24 soat) tanlang, aks holda kech retry takroriy o'tkazma keltirib chiqaradi.
Atomarlik muhim: kalit yozuvi va o'tkazmaning o'zi bir tranzaksiyada bo'lishi yoki kalit aniq holat mashinasiga (in-progress -> completed/failed) ega bo'lishi kerak β yarmida o'chsa, qayta tiklanganda holat aniq bo'lsin.
8-mashq yechimi¶
Lost update qanday sodir bo'ladi (himoyasiz):
- A:
GET /docs/7->{"title": "Loyiha"}. - B:
GET /docs/7->{"title": "Loyiha"}(ikkalasi ham bir xil eski nusxa). - A:
PUT /docs/7 {"title": "Loyiha 2026"}->200. Endi server'da "Loyiha 2026". - B:
PUT /docs/7 {"title": "Yangi loyiha"}->200. B A ni bosib ketdi.
Natija: A ning "Loyiha 2026" o'zgarishi yo'qoldi β B uning yozganini bilmasdan ustiga yozdi.
Optimistik yechim (ETag + If-Match):
- A:
GET /docs/7->ETag: "v1". - B:
GET /docs/7->ETag: "v1". - A:
PUT /docs/7,If-Match: "v1"-> joriy"v1"ga mos -> yoziladi, yangiETag: "v2",200. - B:
PUT /docs/7,If-Match: "v1"-> joriy endi"v2", mos kelmaydi ->412 Precondition Failed. - B
412ni ko'radi,GET /docs/7qaytadan qiladi ("v2"va A ning matnini oladi), o'z o'zgarishini A ning ustiga ongli qo'shadi yoki foydalanuvchidan so'raydi, so'ngIf-Match: "v2"bilan qayta yozadi.
Endi hech kim ko'r-ko'rona bosib ketmaydi.
428 ning roli: agar B If-Match ni umuman yubormasa, server eski uslubda yozib qo'yishi mumkin edi β lost update qaytadi. Server If-Matchsiz PUT ni 428 Precondition Required bilan rad etib, har bir yozishni "avval ETag ol" qoidasiga majbur qiladi. Bu lost update'ni strukturaviy ravishda imkonsiz qiladi.
9-mashq yechimi¶
412 Precondition Failed β yuborilgan shart (If-Match dagi ETag) joriy holatga mos kelmadi. Bu optimistik parallellik konfliktining toza signali: "sening versiyang eski".
PUT /v1/documents/7 HTTP/1.1
If-Match: "v1-3a8f"
Content-Type: application/json
{"title": "..."}
HTTP/1.1 412 Precondition Failed
Content-Type: application/problem+json
{"type": "https://api.example.com/errors/version-conflict",
"title": "Versiya konflikti", "status": 412}
409 Conflict β so'rov resursning joriy holati bilan biznes-qoidaviy ziddiyatda, shartdan mustaqil ravishda. "Bu amal hozirgi holatda mantiqan mumkin emas".
POST /v1/users HTTP/1.1
Content-Type: application/json
{"email": "ali@example.com"}
HTTP/1.1 409 Conflict
Content-Type: application/problem+json
{"type": "https://api.example.com/errors/duplicate-email",
"title": "Email allaqachon band", "status": 409}
Xulosa: optimistik parallellik (ETag eskirgan) uchun 412; biznes-qoidaviy ziddiyat (dublikat, band slot, in-progress idempotent operatsiya) uchun 409. 412 "versiyang eski", 409 "amal joriy holatda mumkin emas" deydi.
β¬ οΈ Oldingi: 14 β Rate limiting, throttling, kvotalar Β· π README Β· Keyingi: 16 β Keshlash (HTTP caching) β‘οΈ