Теңсіздік және кілттер
Демпотенттік дегеніміз не?
Теңсіздік - сол сәйкестендіргішпен қайталау қорытынды әсерді өзгертпейтін операция сипаты. Таратылған жүйелерде бұл нәтижені ретраға, хабарламалардың телнұсқаларына және таймауттарға қарамастан «дәл бір өңдеуге» баламалы етудің негізгі тәсілі.
Түйінді идея: әрбір әлеуетті қайталанатын операция жүйе «мұны жасады» деп танитын және нәтижені бір реттен артық қолданбайтын кілтпен белгіленуі тиіс.
Бұл қайда маңызды
Төлемдер және баланстар: 'operation _ id' бойынша есептен шығару/есепке алу.
Брондау/квоталар/лимиттер: бір слот/ресурс.
Вебхактар/ескертулер: қайталап жеткізу әсерді қайталамауы керек.
Импорттау/көшіру: файлдарды/пакеттерді қайталап жіберу.
Стрим-процессинг: брокерден/CDC.
Кілттердің түрлері және олардың қолданылу аясы
1. Operation key - бизнес-операцияның нақты әрекетінің сәйкестендіргіші
Мысалдар: 'idempotency _ key' (HTTP), 'operation _ id' (RPC).
Саласы: сервис/агрегат; дедупликация кестесінде сақталады.
2. Event key - оқиғаның/хабардың бірегей сәйкестендіргіші
Мысалдар: 'event _ id' (UUID), '(producer_id, sequence)'.
Облыс: тұтынушы/тұтынушылар тобы; проекцияларды қорғайды.
3. Business key - пәндік саланың табиғи кілті
Мысалдар: 'payment _ id', 'invoice _ number', '(user_id, day)'.
Аумағы: агрегат; бірегейлік/нұсқа тексерулерінде қолданылады.
TTL және сақтау саясаты
TTL кілттері ≥ мүмкін болатын қайталау терезесі: логтың ретенциясы + желілік/процестік кідірістер.
Сыни домендер (төлемдер) үшін TTL - күндер/апталар; телеметрия үшін - сағат.
Дедуп-кестелерді бэкграунд-джобтармен тазалаңыз; аудит үшін - мұрағаттаңыз.
Кілттер қоймасы (дедупликация)
Транзакциялық ДБ (ұсынылады): сенімді upsert/unique-индекстер, әсері бар бірлескен транзакция.
KV/Redis: жылдам, қысқа TTL үшін ыңғайлы, бірақ OLTP-мен бірлескен транзакциясыз - абайлаңыз.
State store стрим-процессоры: жергілікті + брокердегі чейнджлог; жақсы Flink/KStreams.
- idempotency_keys
`consumer_id` (или `service`), `op_id` (PK на пару), `applied_at`, `ttl_expires_at`, `result_hash`/`response_status` (опц.) .
Индекстер: '(consumer_id, op_id)' - бірегей.
Іске асырудың базалық тәсілдері
1) «Нәтиже + прогресс» транзакциясы
Нәтижені жазу және оқу/позиция прогресін тіркеу - бір транзакцияда.
pseudo begin tx if not exists(select 1 from idempotency_keys where consumer=:c and op_id=:id) then
-- apply effect atomically (upsert/merge/increment)
apply_effect(...)
insert into idempotency_keys(consumer, op_id, applied_at)
values(:c,:id, now)
end if
-- record reading progress (offset/position)
upsert offsets set pos=:pos where consumer=:c commit
2) Optimistic Concurrency (агрегаттың нұсқасы)
Жарыс кезінде қосарлы әсерден қорғайды:sql update account set balance = balance +:delta,
version = version + 1 where id=:account_id and version=:expected_version;
-- if 0 rows are updated → retry/conflict
3) Іспеттес sinks (upsert/merge)
Бір рет есептеу "әрекеті:sql insert into bonuses(user_id, op_id, amount)
values(:u,:op,:amt)
on conflict (user_id, op_id) do nothing;
Хаттамалардағы сәйкестік
HTTP/REST
'Idempotency-Key' атауы: <uid 'hash>'.
Сервер кілттің жазбасын сақтайды және сол жауапты қайтарады (немесе '409 '/' 422' коды инварианттар қайшылығында).
«Қауіпсіз емес» POST үшін - міндетті 'Idempotency-Key' + тұрақты таймаут/ретрай-саясат.
gRPC/RPC
'idempotency _ key', 'request _ id' + deadline метадеректері.
Серверлік іске асыру - REST-дегі сияқты: транзакциядағы дедуп кестесі.
Брокерлер/стриминг (Kafka/NATS/Pulsar)
Продюсер: тұрақты 'event _ id '/демпотенттік продюсер (қолдау көрсетілетін).
Консьюмер: '(consumer_id, event_id)' бойынша және/немесе агрегаттың бизнес-нұсқасы бойынша дедуп.
Демпотенттік емес/зақымдалған хабарлар үшін жеке DLQ.
Вебхактар және сыртқы әріптестер
Келісімшартта 'Idempotency-Key '/' event _ id' дегенді талап етіңіз; қайта жеткізу қауіпсіз болуы тиіс.
'notification _ id' және жіберу мәртебелерін сақтаңыз; ретра кезінде - қайталамаңыз.
Кілттерді жобалау
Детерминирленуі: ретраялар сол кілтті жіберуі тиіс (клиентке/оркестрге алдын ала генерациялаңыз).
Көріну аумағы: 'op _ id' дегенді 'service: aggregate: id: purpose' деп пішіңіз.
Коллизиялар: бизнес параметрлерінен UUIDv7/ULID немесе хэшті қолданыңыз (қажет болған жағдайда тұзбен).
Иерархия: фронтқа жалпы 'operation _ id' → барлық кіші операцияларға (идемпотенттік тізбек) трансляцияланады.
UX және азық-түлік аспектілері
Кілт бойынша қайта сұрау сол нәтижені (дене/мәртебені қоса алғанда) немесе анық «орындалды» қайтаруы тиіс.
Сәтті қайтадан әрекет жасаудың орнына «операция өңделеді/аяқталды» күйін көрсетіңіз.
Ұзақ операциялар үшін - кілт бойынша поллинг ('GET/operations/{ op _ id}').
Бақылау
'op _ id', 'event _ id', 'trace _ id' деп логин жасаңыз, нәтижесі: 'APPLIED '/' ALREADY _ APPLIED'.
Өлшемдер: қайталау үлесі, дедуп-кесте мөлшері, транзакция уақыты, нұсқа қайшылықтары, DLQ-ставка.
Трейс: кілт пәрмен арқылы өтуі керек → оқиға → проекция → сыртқы қоңырау.
Қауіпсіздік және комплаенс
PII кілттерде сақтамаңыз; кілт - идентификатор, payload емес.
Ұзақ TTL кезінде дедуп жазбаларындағы сезімтал өрістерді шифрлаңыз.
Сақтау саясаты: TTL және мұрағаттар; ұмыту құқығы - жауаптарды/метадеректерді (егер олар PII болса) крипто-өшіру арқылы.
Тестілеу
1. Телнұсқалар: бір хабарды/сұрауды 2-5 рет басып өту - дәл бір нәтиже.
2. Қадамдар арасындағы құлау: әсерді жазуға дейін/кейін, офсетті бекітуге дейін/кейін.
3. Тұтынушылардың қайта стандарты/ребалансы: екі рет қолданылмайды.
4. Бәсекелестік: бір 'op _ id' → бір әсері бар параллель сұраулар, екіншісі - 'ALREADY _ APPLIED/409'.
5. Ұзақ өмір сүретін кілттер: қалпына келтірілгеннен кейін TTL мен қайталаудың аяқталуын тексеру.
Антипаттерндер
Әрбір ретрайдың кездейсоқ жаңа кілті: жүйе қайталауды танымайды.
Екі бөлек коммита: алдымен әсер, содан кейін офсет - олардың арасындағы құлау әсерді қайталайды.
Тек брокерге ғана сенім: синкада/агрегатта дедуптың болмауы.
Агрегат нұсқасының болмауы: қайталанған оқиға күйді екінші рет өзгертеді.
Fat keys: кілт бизнес-өрістерді/PII → ағып кетуді және күрделі индекстерді қамтиды.
Қайталанатын жауаптардың болмауы: клиент қауіпсіз ретрациялай алмайды.
Мысалдар
Төлем POST
Клиент: 'POST/payments' + 'Idempotency-Key: k-789'.
Сервер: транзакция - 'payment' және 'idempotency _ keys' жазбасын жасайды.
Қайталау: сол '201 '/денені қайтарады; инвариант қақтығысы кезінде - '409'.
Бонусты есептеу (sink)
sql insert into credits(user_id, op_id, amount, created_at)
values(:u,:op,:amt, now)
on conflict (user_id, op_id) do nothing;
Оқиғалар проекциясы
Консьюмер 'seen (event_id)' және 'version' агрегатын сақтайды; қайталау - ignor/idempotent upsert.
Оқу прогресі проекцияны жаңарту сияқты транзакцияда тіркеледі.
Шығарылған чек-парақ
- Барлық қауіпсіз емес операциялар үшін идемпотенттік кілт және оның көріну аумағы анықталған.
- TTL және бірегей индекстері бар дедуп кестелері бар.
- Оқудың әсері мен прогресі атомдық түрде қосылады.
- Write-модельде оптимистік бәсекелестік бар (нұсқа/sequence).
- API келісімшарттары 'Idempotency-Key '/' operation _ id' және қайталау мінез-құлқын белгілейді.
- Метриктер мен логдарда 'op _ id '/' event _ id '/' trace _ id' бар.
- Телнұсқа, құлау және жарыс тестілері - CI.
- TTL/мұрағат саясаты және PII қауіпсіздігі сақталған.
FAQ
'Idempotency-Key' дегеннің 'Request-Id' дегеннен айырмашылығы неде?
'Request-Id' - трассировка; ол ретраяларда өзгеруі мүмкін. 'Idempotency-Key' - қайталау кезінде бірдей семантикалық идентификатор.
ДБ-сыз теңсіздік жасауға бола ма?
Қысқа терезе үшін - иә (Redis/процессішілік кэш), бірақ бірлескен транзакциясыз дубль қаупі артады. Сындарлы домендерде - бір БД-транзакциясында жақсы.
Сыртқы серіктестермен не істеу керек?
Кілттер мен қайталанатын жауаптар туралы келісіңіз. Егер серіктес қолдамаса, қоңырауды теңсіздік қабатыңызға айналдырып, «қолданылған» күйінде сақтаңыз.
TTL қалай таңдауға болады?
Максималды кідірістерді қосыңыз: логтың ретенциясы + worst-case желісі/ребаланс + буфер. Қор қосыңыз (× 2).
Жиынтығы
Теңсіздік - бұл кілттердің, транзакциялардың және нұсқалардың тәртібі. Операциялардың тұрақты сәйкестендіргіштері + оқу әсері мен прогресін атомарлық бекіту + демпотенттік sinks/проекциялар көлік деңгейінің сиқырынсыз «тура бір әсер» береді. Кілттерді детерминирленген, TTL - шынайы, ал тестілерді - жаман ниетті етіңіз. Сонда ретрациялар мен дубликаттар қақтығыс емес, дағдыға айналады.