Короткий ответ
Диагностика: нужно проверить логи/трейсы на наличие двух запросов с близкими временными метками и одинаковыми (или очень похожими) данными от одного клиента; предотвращение — требовать от клиента ключ идемпотентности для таких операций, либо выводить детерминированный ключ дедупликации из бизнес-данных запроса (если клиент не может передать явный ключ).
Как это работает подробнее
таймаут на стороне клиента не означает, что сервер не обработал запрос — ответ может быть потерян в сети уже после того, как операция выполнилась. Поэтому клиент, не получивший ответ, не может различить "запрос не дошёл" и "ответ не дошёл", и при retry без идемпотентности рискует продублировать операцию.
Пример кода
example.py
from fastapi import FastAPI, Header, HTTPException
app = FastAPI()
@app.post("/orders")
def create_order(payload: dict, idempotency_key: str | None = Header(default=None)):
if idempotency_key is None:
raise HTTPException(status_code=400, detail="Idempotency-Key header is required")
existing = idempotency_store.get_if_exists(idempotency_key)
if existing is not None:
return existing
order = orders_service.create(payload)
idempotency_store.save(idempotency_key, order)
return orderЧто использовать на практике
- Рекомендация. если изменить протокол клиента (добавить ключ идемпотентности) быстро невозможно, можно на короткое время дедуплицировать по комбинации полей запроса (например,
user_id + cart_hashв пределах нескольких минут) — это менее надёжно, чем явный ключ, но может быть временным решением. Долгосрочно правильнее явно согласовать с клиентами использованиеIdempotency-Keyдля всех операций создания с потенциальными повторами.
Подводные камни
- Риски. считать, что увеличение таймаута "решает" проблему — оно лишь снижает частоту повторов, не устраняя саму возможность дублирования при реальном сетевом сбое; реализовывать дедупликацию только в памяти процесса без персистентного хранилища, из-за чего повторный запрос, обработанный другим инстансом сервиса (за балансировщиком), не распознаётся как дубликат.