Exactly-once vs At-least-once
1) Nə üçün semantika müzakirə
Çatdırılma semantikası alıcının nasazlıqlar və retrajlar zamanı mesajı nə qədər tez-tez görəcəyini müəyyən edir:- At-most-once - təkrarsız, lakin itki mümkündür (nadir hallarda məqbul).
- At-least-once - itirmirik, lakin dublikatlar mümkündür (əksər brokerlərin/növbələrin defoltu).
- Exactly-once - Hər bir mesaj müşahidə olunan təsir baxımından düz bir dəfə emal edilir.
Əsas həqiqət: paylanmış dünyada qlobal əməliyyatlar və sinxron uyğunluq olmadan «təmiz» end-to-end exactly-once əlçatmazdır. Biz effektiv şəkildə exactly-once qururuq: nəqliyyatda təkrarlara icazə veririk, lakin emosional emal edirik ki, müşahidə olunan təsir 'bir dəfə kimi "olsun.
2) Uğursuzluq modeli və dublikatlar harada yaranır
Təkrar görə görünür:- ack/commit itkiləri (prodüser/broker/konsumer təsdiqini «eşitmədi»).
- Liderlərin yenidən seçilməsi/replikaları, şəbəkə fasilələrindən sonra bərpa.
- Hər hansı bir sahədə vaxt/retraut (müştəri → broker → konsumer → sink).
Nəticə: nəqliyyatın «unikallığına» etibar etmək olmaz. Effektləri idarə edirik: DB-yə yazmaq, pul çıxarmaq, məktub göndərmək və s.
3) Təchizatçılarda Exactly-once və əslində nə
3. 1 Kafka
Kərpiclər verir:- Idempotent Producer (`enable. idempotence = true ') - retralarda prodüserin tərəfində dublların qarşısını alır.
- Əməliyyatlar - atomik olaraq bir neçə partiyaya və kommitat istehlak ofsetlərinə («keçidsiz» nümunə read-process-write) göndərilir.
- Compaction - son açar dəyərini saxlayır.
Lakin «zəncirin sonu» (sink: BD/ödəniş/poçt) hələ də idempotentlik tələb edir. Əks halda, prosessor ikiqat təsir səbəb olacaq.
3. 2 NATS / Rabbit / SQS
Default - ack/redelivery ilə at-least-once. Exactly-once proqram səviyyəsində əldə edilir: açarlar, dedup-store, upsert.
Nəticə: Exactly-once nəqliyyat ≠ exactly-once effekt. Sonuncu prosessorda edilir.
4) at-least-once üzərində effektiv exactly-once qurmaq üçün necə
4. 1 Idempotent açarı (idempotency key)
Hər bir komanda/hadisə təbii açar daşıyır: 'payment _ id', 'order _ id #step', 'saga _ id #n'. Prosessor:- «Artıq gördünmü?» - TTL/retenshn ilə dedup stor (Redis/BD).
- Gördüm - əvvəllər hesablanmış nəticəni təkrarlayır və ya no-op edir.
lua
-- SET key if not exists; expires in 24h local ok = redis.call("SET", KEYS[1], ARGV[1], "NX", "EX", 86400)
if ok then return "PROCESS" else return "SKIP" end
4. 2 Upsert bazasında (idempotent sink)
Qeydlər UPSERT/ON CONFLICT vasitəsilə versiyanın/məbləğin yoxlanılması ilə aparılır.
PostgreSQL:sql
INSERT INTO payments(id, status, amount, updated_at)
VALUES ($1, $2, $3, now())
ON CONFLICT (id) DO UPDATE
SET status = EXCLUDED.status,
updated_at = now()
WHERE payments.status <> EXCLUDED.status;
4. 3 Əməliyyat Outbox/Inbox
Outbox: Biznes əməliyyatı və «nəşr hadisələri» qeydləri bir DB əməliyyatında baş verir. Fon publisher outbox oxuyur və broker göndərir → vəziyyət və hadisə arasında heç bir uyğunsuzluq yoxdur.
Inbox: Daxil olan komandalar üçün 'message _ id' və nəticəni yerinə yetirilməzdən əvvəl saxlayın; təkrar emal qeyd görür və yan təsirləri təkrarlamır.
4. 4 Konsistent zəncir emalı (read → process → write)
Kafka: əməliyyat bir atom blokunda «ofset → qeyd → kommit» oxudu.
Əməliyyatlar olmadan: «əvvəlcə nəticə/Inbox, sonra ack yazın»; crash ikisini Inbox görəcək və no-op başa çatacaq.
4. 5 SAGA/kompensasiya
İdempotentlik mümkün olmadıqda (xarici provayder pulu silib), kompensasiya əməliyyatlarından (refund/void) və idempotent xarici API-lərdən (eyni 'Idempotency-Key' ilə təkrar 'POST' eyni nəticəni verir) istifadə edirik.
5) Kifayət zaman at-least-once
Açar kompaksiyası ilə cache/materiallaşdırılmış performans yeniləmələri.
Sayğaclar/metriklər, burada təkrar inkrementasiya məqbuldur (və ya delta versiyasını saxlayın).
İkinci məktubun kritik olmadığı qeydlər (yenə də açar qoymaq daha yaxşıdır).
Qayda: ikiqat iş mənasını dəyişdirmir və ya asanlıqla → at-least-once + qismən qorunma aşkar.
6) Performans və dəyəri
Exactly-once (hətta «effektiv») daha bahalıdır: əlavə qeyd (Inbox/Outbox), açarların saxlanması, əməliyyatlar, diaqnostika daha mürəkkəbdir.
At-least-once daha ucuz/daha asan, throughput/p99 ilə daha yaxşı.
Qiymətləndirin: ikiqat qiymət × ikiqat ehtimalı vs müdafiə dəyəri.
7) Konfiqurasiya və kod nümunələri
7. 1 Kafka prodüseri (idempotentlik + əməliyyatlar)
properties enable.idempotence=true acks=all retries=INT_MAX max.in.flight.requests.per.connection=5 transactional.id=orders-writer-1
java producer.initTransactions();
producer.beginTransaction();
producer.send(recordA);
producer.send(recordB);
// также можно atomically commit consumer offsets producer.commitTransaction();
7. 2 Inbox ilə konsumer (psevdokod)
pseudo if (inbox.exists(msg.id)) return inbox.result(msg.id)
begin tx if!inbox.insert(msg.id) then return inbox.result(msg.id)
result = handle(msg)
sink.upsert(result) # идемпотентный синк inbox.set_result(msg.id, result)
commit ack(msg)
7. 3 HTTP Idempotency-Key (xarici API)
POST /payments
Idempotency-Key: 7f1c-42-...
Body: { "payment_id": "p-123", "amount": 10.00 }
Eyni açar ilə POST təkrar → eyni nəticə/status.
8) Müşahidə və metrika
'duplicate _ attempts _ total' - neçə dəfə dubl (Inbox/Redis ilə).
'idempotency _ hit _ rate' - idempotentliyi ilə «xilas edilmiş» təkrarların payı.
'txn _ abort _ rate' (Kafka/BD) - geri çəkilmələrin payı.
'outbox _ backlog' - nəşr gecikməsi.
'exactly _ once _ path _ latency {p95, p99}' vs 'at _ least _ once _ path _ latency' - əlavə xərclər.
Log auditi: 'message _ id', 'idempotency _ key', 'saga _ id', 'attempt'.
9) Test Playbook (Game Days)
Göndərmənin təkrarı: prodüserin süni vaxtlarda retrayları.
«Sink & ack» arasında Crash: Inbox/Upsert ikiqat qarşısını almaq üçün əmin olun.
Yenidən çatdırılma: broker redelivery artırmaq; dedupu yoxlamaq.
Xarici API idempotentliyi: eyni açarla təkrar POST eyni cavabdır.
Lider dəyişikliyi/şəbəkə qırılması: Kafka əməliyyatlarını/konsumerlərin davranışlarını yoxlayın.
10) Anti-nümunələr
Nəqliyyata güvənmək: «Bizdə Kafka exactly-once var, yəni açarsız mümkündür» - yox.
No-op ack qeyd əvvəl: ackled, lakin sink düşdü → itki.
DLQ/Jitter retrains yoxdur: sonsuz təkrarlamalar və fırtına.
Təbii açarlar əvəzinə təsadüfi UUID: deduplication heç bir şey.
Inbox/Outbox-u indeks olmadan prod cədvəlləri ilə qarışdırın: isti kilidləmə və p99-quyruqlar.
Xarici provayderlərdə idempotent API olmadan biznes əməliyyatları.
11) Check-list seçimi
1. ikiqat qiymət (pul/hüquq/UX) vs müdafiə qiymət (gizli/mürəkkəblik/dəyəri).
2. Hadisənin/əməliyyatın təbii açarı varmı? Əgər yoxdursa, sabit fikirləşin.
3. Sink Upsert/version dəstəkləyir? Əks halda - Inbox + kompensasiya.
4. Qlobal əməliyyatlara ehtiyacınız varmı? Yoxsa, SAGA-da seqmentləşdirin.
5. Replica/uzun gecikmə tələb olunur? Kafka + Outbox. Sürətli RPC/aşağı gecikmə lazımdır? NATS + Idempotency-Key.
6. Çox tenantlıq və kvotalar: açarların/məkanların izolyasiyası.
7. Müşahidə: idempotency və backlog metriklər daxildir.
12) FAQ
Q: «riyazi» exactly-once end-to-end əldə etmək mümkündürmü?
A: Yalnız bütün yolda bir konsistent saxlama və əməliyyatlar ilə dar ssenarilərdə. Ümumiyyətlə - yox; səmərəli idempotentlik vasitəsilə exactly-once istifadə edin.
Q: Hansı daha sürətli?
A: At-least-once. Exactly-once əməliyyat/anahtar saxlama → p99 yuxarıda və dəyəri əlavə edir.
Q: İdempotent açarları harada saxlanılır?
A: TTL ilə sürətli stor (Redis) və ya Inbox cədvəli (PK = message _ id). Ödənişlər üçün - daha uzun (gün/həftə).
Q: TTL dedup açarları necə seçilir?
A: Minimum = maksimum təkrar çatdırılma vaxtı + əməliyyat ehtiyatı (adətən 24-72 saat). Maliyyə üçün - daha çox.
Q: Mən Kafka açar compaction varsa açar lazımdır?
A: Bəli. Compaction saxlama azaldacaq, lakin sink idempotent etməyəcək.
13) Nəticələr
At-least-once - əsas, etibarlı nəqliyyat semantikası.
İş effekti kimi Exactly-once prosessor səviyyəsində əldə edilir: Idempotency-Key, Inbox/Outbox, Upsert/versiyası, SAGA/kompensasiya.
Seçim - güzəşt dəyəri, iki dəfə riskdir. Təbii açarları layihələndirin, çəngəlləri idempotent edin, müşahidə əlavə edin və mütəmadi olaraq oyun günləri keçirin - sonra retraj və uğursuzluqlar fırtınasında belə paylayıcılarınız proqnozlaşdırıla bilən və təhlükəsiz olacaq.