Architektura

Idempotence: proč stejná zpráva nesmí dvakrát otevřít účet

Co je idempotence, proč ji distribuovaný systém potřebuje a jak ji zajistit v praxi

Martin Tribula26. října 20267 min čteníRead in English

Sdílet článek
Titulní obrázek článku: Idempotence: proč stejná zpráva nesmí dvakrát otevřít účet. Martin Tribula – Architektura.

Timeout neznamená, že se nic nestalo. Bez idempotence vede druhý pokus ke dvojí platbě, dvěma účtům nebo dvěma stejným e-mailům zákazníkovi.

Idempotence je vlastnost, díky které může systém bezpečně zkusit totéž znovu.

V distribuovaném systému se ke stejnému požadavku dřív nebo později dostaneme dvakrát. Otázka nezní, jestli se to stane, ale co se v tu chvíli stane s daty.

Obsah

Co je idempotence

Operace je idempotentní, když má její opakované provedení stejný výsledek jako provedení jediné.

Nejlépe to ukazuje rozdíl mezi dvěma příkazy:

První příkaz je idempotentní, druhý ne. Podobně funguje tlačítko u výtahu: když ho zmáčknu podruhé, výtah nepřijede dvakrát.

V softwaru nás nezajímá matematická definice, ale účinek na stav systému. Idempotentní operace může vrátit jinou odpověď nebo zapsat záznam do logu. Podstatné je, že druhé provedení nezmění data jinak než to první.

Proč na ní v distribuovaných systémech záleží

Dokud vše běží v jednom procesu a jedné transakci, vidíme, jestli operace proběhla. Jakmile mezi dvěma částmi systému stojí síť, ztrácíme jistotu.

Duplicity mají tři typické zdroje:

  1. Ztracené potvrzení. Služba zprávu zpracuje, ale potvrzení se cestou ztratí. Broker ji doručí znovu.
  2. Retry klienta. Volání skončí timeoutem a klient ho zopakuje. Netuší, jestli první pokus prošel.
  3. Redelivery brokeru. Po výpadku spojení nebo uzlu broker doručí zprávy, které už konzument mohl vidět.

Dokumentace RabbitMQ to říká přímo: zprávy mohou být doručeny znovu a konzumenti musí být připraveni zpracovat doručení, které už viděli. Doporučuje navrhovat konzumenty jako idempotentní [1].

Broker garantuje „aspoň jednou“, ne „právě jednou“.

Na našem školení Event-Driven Architecture to ukazujeme na výplatě hypotéky. Služba Disbursement zavolá platební systém a volání skončí timeoutem. Peníze mohly odejít, nebo ne. Poslat výplatu znovu je riziko, neposlat ji je taky riziko.

Schéma: služba Disbursement pošle platebnímu systému výplatu 2 000 000 Kč, odpověď se ztratí a služba ohlásí timeout. Druhý pokus pošle stejnou výplatu znovu. Výsledek: dvakrát odesláno. Text: Timeout není odpověď „ne“. Je to odpověď „nevím“. Retry bez idempotence zaplatí dvakrát.
Timeout není odpověď „ne“. Je to odpověď „nevím“.

Co se stane bez ní

Vezměme službu, která po schválení hypotéky otevírá úvěrový účet. Dostane zprávu „otevři účet pro žádost 42“, účet založí a potvrdí zpracování.

Potvrzení se ztratí. Broker zprávu doručí znovu. Služba ji zpracuje jako novou a založí druhý účet.

Intuitivní reakce bývají dvě a obě jsou špatně:

Spolehlivé doručení a správná data spolu nejdou dohromady bez idempotence. V praxi se proto „právě jednou“ skládá ze dvou částí: doručení aspoň jednou plus idempotentní zpracování.

Jak idempotenci zajistit

Idempotentní klíč

Klient ke každému požadavku přidá jedinečný identifikátor. Server si u klíče uloží výsledek prvního zpracování. Když přijde stejný klíč znovu, operaci neprovádí a vrátí uložený výsledek.

Takto fungují například platby ve Stripe. Server uloží stavový kód a tělo odpovědi prvního požadavku s daným klíčem a opakované požadavky se stejným klíčem dostanou uloženou odpověď. Klíče lze po uplynutí nejméně 24 hodin odstranit [2].

Důležité je, kdo klíč vytvoří: klient, ještě před prvním pokusem. Klíč vygenerovaný až na serveru by byl u každého retry jiný.

Inbox: evidence zpracovaných zpráv

U zpráv z brokeru je klíčem ID zprávy. Konzument si ve vlastní databázi eviduje, které zprávy už zpracoval, a duplicity zahodí. Vzor se jmenuje Idempotent Consumer [3].

Podmínkou je, aby se záznam o zpracování a změna dat zapsaly v jedné transakci:

public async Task Handle(OpenMortgageAccount message)
{
    await using var tx = await db.BeginTransactionAsync();

    // Duplicita: zpráva už byla zpracována, nic dalšího nedělat.
    if (await db.ProcessedMessages.AnyAsync(m => m.Id == message.MessageId))
        return;

    db.Accounts.Add(Account.Open(message.ApplicationId, message.Amount));
    db.ProcessedMessages.Add(new ProcessedMessage(message.MessageId));

    await db.SaveChangesAsync();   // obojí, nebo nic
    await tx.CommitAsync();
}

Kontrola a zápis musí být v jedné transakci a tabulka zpracovaných zpráv potřebuje unikátní index na ID. Jinak dvě souběžná doručení projdou kontrolou současně.

Schéma Inboxu: zpráva ID 42 dorazí dvakrát. Služba se zeptá, jestli ID 42 už zpracovala. Pokud ne, otevře účet a zapíše ID 42 do databáze. Pokud ano, zprávu zahodí a nic nedělá. Text: Inbox: stejná zpráva, jedno zpracování. Záznam o zpracování a změna dat v jedné transakci.
Druhé doručení se zastaví na kontrole zpracovaných zpráv.

Přirozeně idempotentní operace

Někdy jde idempotenci zajistit bez evidence:

Kde idempotence končí

Idempotentní příjemce chrání jen vlastní data. Pokud služba v rámci zpracování volá další systém, například posílá e-mail nebo platbu, musí idempotentní klíč poslat i dál. Jinak se duplicita jen přesune o patro níž.

Na straně odesílatele pomáhá vzor Transactional Outbox: zpráva se uloží do stejné databáze jako změna dat a odešle se až potom [4]. Outbox zaručí, že se zpráva neztratí. Za duplicity, které při odesílání vzniknou, odpovídá idempotentní příjemce.

Idempotence v HTTP API

Specifikace HTTP definuje idempotentní metody: opakovaný identický požadavek má na serveru stejný zamýšlený účinek jako jediný. Idempotentní jsou PUT, DELETE a bezpečné metody jako GET. POST idempotentní není [5].

Proto se u plateb, objednávek a dalších POST požadavků používá hlavička Idempotency-Key. IETF ji standardizuje právě proto, aby šly neidempotentní metody bezpečně opakovat [6].

Prakticky:

Typické chyby

Pět otázek pro váš systém

  1. Co se stane, když tahle zpráva dorazí dvakrát?
  2. Kdo vytváří idempotentní klíč a zůstává stejný při retry?
  3. Zapisuje se evidence zpracování ve stejné transakci jako data?
  4. Jak dlouho klíče uchováváme a stačí to na nejdelší výpadek?
  5. Posíláme klíč dál do systémů, které voláme?

Idempotence není optimalizace. Je to podmínka, aby retry byl bezpečný.

Duplicitu si můžete vyzkoušet na našem školení Event-Driven Architecture v části o idempotenci a ve cvičení 12, kde simulátor ukáže stejný scénář bez Inboxu a s ním. Na stejné téma navazuje článek Event Sourcing jako paměť systému.

Ozvat se


Literatura a zdroje

Zdroje ověřeny 26. října 2026.

[1] RabbitMQ. Reliability Guide. Dokumentace RabbitMQ. Část Data Safety on the Consumer Side: opakované doručení a doporučení idempotentních konzumentů.

[2] Stripe. Idempotent requests. Stripe API Reference. Ukládání odpovědi prvního požadavku s klíčem, opakované požadavky a uchování klíčů.

[3] Richardson, Chris. Pattern: Idempotent Consumer. Microservices.io. Evidence ID zpracovaných zpráv v databázi a zahazování duplicit.

[4] Richardson, Chris. Pattern: Transactional Outbox. Microservices.io.

[5] Fielding, R.; Nottingham, M.; Reschke, J. RFC 9110: HTTP Semantics, oddíl 9.2.2 Idempotent Methods. IETF, 2022.

[6] IETF HTTPAPI Working Group. The Idempotency-Key HTTP Header Field. Internet-Draft, IETF. Návrh standardu, ne finální RFC.

Sdílet článek

Další krok

Chcete se na tuto cestu vydat společně?

Můžeme začít přímo situací, kterou právě řešíte — od konkrétního problému k vlastnímu návrhu a jeho ověření v praxi.

Ozvat se Markovi
Probrat produkt