Ciencias de la computación

Qué es la idempotencia y por qué pulsar dos veces el botón de pago es seguro

La idempotencia es la propiedad de obtener el mismo resultado al enviar varias veces la misma solicitud. Aquí resumimos por qué es necesaria ante fallos de red y reintentos, la diferencia entre GET, PUT, DELETE y POST, y cómo implementar Idempotency-Key.

5 min de lectura
Imagen de portada de Qué es la idempotencia y por qué pulsar dos veces el botón de pago es seguro

Pulsas el botón de pago, pero la pantalla se queda bloqueada un buen rato. Te pones nervioso. Te entran ganas de pulsarlo otra vez.

Pero, si lo pulsas dos veces, ¿no se cobrará dos veces?

En un sistema bien diseñado, no. La propiedad que lo garantiza tiene un nombre.

Idempotencia.

Es un concepto habitual en la documentación de backend, las especificaciones HTTP y las guías de API de pago, pero su definición cabe en una línea. Lo difícil es entender «por qué es tan importante» y «cómo implementarla».

En este artículo responderemos a ambas preguntas.

Este es el resumen clave.

  1. Idempotencia: propiedad por la que el resultado es el mismo tanto si una solicitud se envía una vez como varias
  2. Por qué importa: las redes fallan, los fallos requieren reintentos y los reintentos solo son seguros cuando la operación es idempotente
  3. En HTTP, GET, PUT y DELETE son idempotentes; POST no lo es
  4. El mecanismo práctico que hace seguro POST es Idempotency-Key

Definición: hacerlo varias veces equivale a hacerlo una vez

Idempotencia es un término matemático. Si una operación f cumple f(f(x)) = f(x), es idempotente.

La función valor absoluto es un buen ejemplo. |−5| = 5 y |5| = 5.

El resultado es el mismo tanto si se aplica una vez como cien.

Aplicado a una API, queda así.

El estado del servidor es el mismo tanto si la misma solicitud se envía 1 vez como N veces.

Importante: no es necesario que la respuesta sea igual, sino que lo sea el estado del servidor. Aunque la segunda solicitud DELETE devuelva 404, sigue siendo idempotente porque el estado «el recurso no existe» no cambia.

Una analogía cotidiana es el botón de un ascensor. Aunque pulses cinco veces el botón del quinto piso, el ascensor solo va una vez al quinto piso.

En cambio, si fuera un botón de «subir cinco pisos más», el resultado cambiaría cada vez. El primero es idempotente; el segundo, no idempotente.


Por qué importa: las redes fallan inevitablemente

La importancia de la idempotencia se explica con un único escenario.

El cliente envía una solicitud de pago. El servidor procesa el pago.

Pero la red se corta mientras la respuesta vuelve.

El cliente no puede saber si la solicitud falló antes de llegar al servidor (no se realizó el pago) o si, tras procesarse, solo se perdió la respuesta (pago realizado).

En ambos casos, el resultado parece exactamente un tiempo de espera agotado.

Diagrama de secuencia en el que un pago se procesa una sola vez al reintentar con la misma clave de idempotencia
Si se pierde la respuesta, no puedes saber si el pago se realizó. Por eso hace falta idempotencia.

Solo hay dos opciones: renunciar al reintento (el pago podría no haberse realizado) o reintentar (si ya se realizó, sería un cobro duplicado). Ambas son terribles.

La idempotencia elimina este dilema. Si una solicitud es idempotente, «si no estás seguro, vuelve a enviarla» siempre es una estrategia segura.

Los reintentos automáticos, la entrega al menos una vez (at-least-once) de las colas de mensajes y las actualizaciones repetidas del cliente dejan de ser motivo de preocupación.

Por eso los documentos de diseño de sistemas distribuidos repiten: «Configura reintentos solo para operaciones idempotentes».


La idempotencia según los métodos HTTP

La especificación HTTP (RFC 9110) define la idempotencia de cada método.

Método ¿Idempotente? Motivo
GET O Consultar no cambia el estado
PUT O «Sustituir por este valor» produce el mismo valor cada vez
DELETE O «Borrar» deja el estado eliminado cada vez
POST X «Crear uno nuevo» aumenta en uno cada vez que se invoca
PATCH Condicional «Establecer este campo en X» es idempotente; «sumar 1» no lo es

El contraste entre PUT y POST es fundamental. PUT especifica el «estado resultante», por lo que es idempotente; POST indica una «acción», por lo que no lo es.

PATCH depende del contenido.

Cuando el navegador vuelve a una página POST mediante el botón Atrás y pregunta «¿Quieres volver a enviar este formulario?», es porque sabe que reenviar POST no es seguro.


El mecanismo práctico: las claves de idempotencia

Sin embargo, registrarse, crear un pedido y realizar un pago son operaciones POST por naturaleza. No se pueden evitar las operaciones no idempotentes.

Por eso apareció un mecanismo para hacer idempotentes las solicitudes no idempotentes: Idempotency-Key.

Su funcionamiento es sencillo.

  1. El cliente genera una clave única por solicitud (normalmente un UUID) y la envía en una cabecera
  2. Si es la primera vez que ve la clave, el servidor procesa la solicitud normalmente y guarda la respuesta junto con ella
  3. Si vuelve a recibir la misma clave, no procesa la solicitud y devuelve exactamente la respuesta guardada

Sea un reintento o un doble clic, el servidor solo procesa una vez mientras se use la misma clave. API de pago como Stripe y Toss Payments admiten esta cabecera y la tratan como un requisito en sus guías de integración.

Un detalle del cliente: «al reintentar, usa la misma clave».

Si generas una clave nueva en cada reintento, el servidor lo verá como una solicitud distinta. La clave debe crearse por cada «solicitud con la misma intención».

Terminal de pago que procesa solo uno de dos sobres con la misma clave y emite un único recibo
Cuando llega la misma clave de idempotencia, el servidor procesa una sola vez y devuelve la respuesta guardada

Resumen

  • La idempotencia es la propiedad por la que el estado del servidor es el mismo sin importar cuántas veces se envíe la misma solicitud (f(f(x)) = f(x))
  • Cuando un fallo de red deja la situación en «no sé si se completó», el reintento solo es seguro si la operación es idempotente
  • HTTP: GET, PUT y DELETE son idempotentes; POST no lo es; PATCH depende de su contenido
  • PUT es idempotente porque especifica el estado resultante; POST no lo es porque indica una acción
  • Haz idempotente un POST no idempotente con Idempotency-Key: con la misma clave solo se procesa una vez
  • Al reintentar, reutiliza siempre la misma clave. La clave representa una «solicitud con la misma intención»