惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CERT Recently Published Vulnerability Notes
U
Unit 42
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
Cisco Talos Blog
Cisco Talos Blog
P
Proofpoint News Feed
H
Heimdal Security Blog
Help Net Security
Help Net Security
H
Help Net Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Palo Alto Networks Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Secure Thoughts
The GitHub Blog
The GitHub Blog
博客园_首页
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Azure Blog
Microsoft Azure Blog
Hacker News: Ask HN
Hacker News: Ask HN
博客园 - 【当耐特】
J
Java Code Geeks
S
SegmentFault 最新的问题
Application and Cybersecurity Blog
Application and Cybersecurity Blog
P
Proofpoint News Feed
The Last Watchdog
The Last Watchdog
O
OpenAI News
博客园 - 三生石上(FineUI控件)
Recent Announcements
Recent Announcements
B
Blog RSS Feed
V2EX - 技术
V2EX - 技术
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Tenable Blog
PCI Perspectives
PCI Perspectives
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Hacker News
The Hacker News
Schneier on Security
Schneier on Security
Google Online Security Blog
Google Online Security Blog
美团技术团队
G
GRAHAM CLULEY
D
DataBreaches.Net
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 聂微东
W
WeLiveSecurity
Vercel News
Vercel News
S
Security Affairs
T
Tailwind CSS Blog
V
Vulnerabilities – Threatpost
博客园 - 司徒正美
G
Google Developers Blog
D
Docker
Webroot Blog
Webroot Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Идемпотентность в System Design: полный пример
ph_piter (Из · 2026-05-13 · via Все публикации подряд на Хабре

Идемпотентность в System Design: полный пример

Идемпотентность часто упоминается при проектировании систем (system design). Ниже будет простыми словами объяснено, что это такое, далее мы разберём основные детали идемпотентности, часто понимаемые неверно и, наконец, проиллюстрируем её на полном примере. 

Что такое идемпотентность?

Операция является идемпотентной, если при однократном или многократном выполнении она всякий раз даёт один и тот же результат.

Иными словами, если вы выполняете идемпотентную операцию один раз, то можете быть уверены, что и на 2-й, и на 3-й, и на 10-й раз она завершится точно так же, как и на 1-й. Рассмотрим стандартный пример: у нас есть кнопки «вкл». и «выкл». Нажимая её, мы выполняем идемпотентную операцию. Нажмёте её один раз — машина включится. Если после этого нажимать её снова и снова, то ничего не изменится. Машина останется во включённом состоянии. То же самое касается кнопки «выкл».   

Рассмотрим пример из программирования:

def hide_my_button(self):
  self.show_my_button = False

Определённо, это идемпотентная операция.

def toggle_my_button_visibility(self):
  self.show_my_button = not self.show_my_button

А эта операция, конечно же, не идемпотентная.

Дело здесь совсем не в возвращаемом значении!

Как раз этого зачастую не понимают. Приведённую выше функцию скрытия можно реализовать и следующим образом:

def hide_my_button(self):
  has_something_changed = self.show_my_button
  self.show_my_button = False
  return has_something_changed

Таким образом, функция возвращается независимо от того, изменилось что-нибудь или нет. Если вызывать эту функцию несколько раз, то возвращаемое ею значение может меняться! Но она всё равно является идемпотентной, так как идемпотентность характеризует воздействие на «состояние» или «эффект» и напрямую не связана с тем кодом состояния, который получит клиент.

Идемпотентность vs Чистота

Хотя, чистота функции не является темой этой статьи, я всё равно хочу коротко её затронуть, поскольку в этом заключается ещё одна распространённая путаница.

Функция или операция является чистой, если, получая одинаковый ввод, она всегда результирует одинаковым выводом, и при этом не возникает никаких побочных эффектов.  

def square(my_number):
  return my_number ** 2

Это чистая функция. square(3) всегда будет одним и тем же числом.

def square_with_randomness(my_number):
  return (my_number ** 2) * random.uniform(0, 1)

Это не чистая функция. Функцияsquare_with_randomness(3)почти всегда будет давать разные результаты, поскольку мы умножаем её на случайное число в диапазоне от 0 до 1. Аналогично, если мы будем умножать её на какую-то глобальную переменную или переменную класса, она больше не будет чистой. Глобальная переменная может измениться, в таком случае изменится и результат.

Идемпотентная функция не обязана быть чистой. Рассмотрим следующий простой пример.

def save_name(name):
  my_database.save(name) # побочный эффект
  return name

Идемпотентность в проектировании систем

Почему же эта концепция так важна при проектировании систем? На это есть много причин, и ниже мы обсудим основные из них.

Обработка сообщений

Допустим, мы проектируем нашу систему как событийно-ориентированную. В состав нашей системы входят очередь сообщений и сервис, потребляющий сообщения из этой очереди.

Проблема заключается в следующем. Когда  Service B потребляет сообщение, скажем, содержащее Event 3, он его обрабатывает, а затем записывает результат обработки в нашу базу данных. Оставим пример максимально простым и предположим, что Service B вычисляет для каждого события некую сложную формулу, и именно результат этого вычисления попадает в базу данных. Теперь важно обеспечить, чтобы ничего из этого ни в коем случае не потерялось.  Мы работаем с очень важными данными!

Но, если в процессе вычислений Service B откажет, либо если обнаружится фрагментация сети между Service B и базой данных, или если произойдёт что-то ещё, то и сообщение, и событие будут утрачены навсегда. Ужасно.

Проблема решается просто: мы не будем сразу же удалять сообщение из очереди, а дождёмся, пока Service B закончит работу, то есть, запишет результат в базу данных. Только после этого мы удалим сообщение.

Но здесь возникает и другая проблема. Может случиться, что одно и то же сообщение будет прочитано дважды. Например, Service B выполнил вычисление и записал результат в базу данных, а затем что-то произошло. Из-за этого сервис аварийно завершил работу. Отказ сервиса произошёл в момент, когда сообщение ещё оставалось в очереди. Что произойдёт дальше? Сервис перезапустится и, возобновив работу, он продолжит потреблять сообщения. Причём, возобновит работу он именно с того сообщения, на котором остановился. Таким образом, это сообщение окажется обработано дважды!   

Как решается эта проблема? Напрямую — никак. Как известно, проектирование систем — это поле для компромиссов. Можно пойти либо на потерю сообщений, либо на двукратную обработку одних и тех же сообщений.

Но эта проблема не так серьёзна. Если мы спроектируем работу Service B как идемпотентную операцию, то ничего не случится. Сервис повторно потребит сообщение, но это не важно, поскольку результат будет точно таким, как и раньше.

Единственный недостаток заключается в некоторой дополнительной сложности (нужно найти способ, как сделать эту операцию идемпотентной) и в дополнительном расходе вычислительных ресурсов (потенциально нам потребуется делать что-то по несколько раз, а эти операции избыточны). Но, как правило, а в нашем случае — точно — это лучше, чем потерять сообщения!

Минимум однократная доставка + идемпотентность = строго однократная доставка

Что здесь наиболее важно: в таких очередях сообщений как AWS SQS предлагается семантика минимум однократной доставки. Это означает, что ваше сообщение может прибыть один раз, два раза или более. Но, если одновременно сделать такую операцию идемпотентной, то, фактически, получится семантика строго однократной доставки. Может быть, сообщение будет обработано многократно. Но, поскольку операция является идемпотентной, с точки зрения бизнес-логики результат получится ровно таким же, как и после строго однократной обработки. Это практичное и элегантное решение сложной проблемы, связанной с распределёнными системами.

Подводные камни

Здесь есть несколько аспектов, с которыми нужно проявлять осторожность. Во-первых, может возникнуть бесконечный цикл (катастрофическое восстановление после отказов). Если у нас есть  “плохое” сообщение, например, записанное в нечитаемом формате, и именно из-за него отказывает сервис, потребляющий сообщения (Service B), то оно так и останется в очереди. Таким образом, Service B будет перезапускаться лишь для того, чтобы потребить то же самое плохое сообщение — и снова отказывать. И так снова и снова. Даже если в вашей системе пока нет таких проблем, рано или поздно они возникнут. Для устранения подобной проблемы используется так называемая «очередь недоставленных сообщений» (dead-letter queue), также называемая «госпиталь сообщений».

Как ещё можно использовать идемпотентность

Мы обсудили доставку сообщений, но идемпотентность встречается в проектировании систем повсюду. Давайте рассмотрим другие распространённые варианты её применения. 

API

Если вы пишете REST API, то уже имеете дело с идемпотентностью, даже если не осознаёте этого. В протоколе HTTP прямо определено, какие методы должны быть идемпотентными.  

Запросы GET ничего не меняют на сервере, поэтому, естественно, они идемпотентны. Их можно вызывать хоть 100 раз — результат всегда будет одинаков. Можно сколько угодно обновлять веб-страницу, не беспокоясь о том, что она может измениться.

Запросы PUT должны полностью заменять один ресурс на другой. Но, если PUT одни и те же данные дважды, то результат будет одинаков. Представьте, что вы перезаписываете файл — если эту операцию повторить, то ничего не изменится.

Запросы DELETE предназначены для удаления ресурса. А если удалить что-то, что уже удалено? Его просто больше нет. Никаких проблем.

Запросы POST обычно по определению не идемпотентны. При каждом запросе POST, как правило, что-то создаётся. Но их можно сделать идемпотентными при помощи  специальных ключей. Вот как это работает: вместе с запросом вы посылаете уникальный ID (часто он записывается в заголовке), и сервер вспоминает: «этот ID я уже обрабатывал, так что просто верну уже имеющийся результат, а не буду выполнять работу повторно».

def create_user(request):
    idempotency_key = request.headers.get('Idempotency-Key')
    
    # Обрабатывали ли мы уже ранее именно этот запрос?
    if idempotency_key and already_processed(idempotency_key):
        return get_cached_response(idempotency_key)
    
    # Нет, создаём пользователя
    user = User.create(request.data)
    
    # Кэшируем отклик на будущее 
    if idempotency_key:
        cache_response(idempotency_key, user)
    
    return user

Базы данных

Операции над базами данных также зачастую бывают идемпотентными. Вот наиболее распространённые паттерны:

Операции UPSERT (INSERT или UPDATE при наличии) естественно идемпотентны. Можно 10 раз выполнить  upsert над одними и теми же данными – и всякий раз результат будет одинаков. Запись в базе данных либо создаётся один раз, либо сколько угодно раз обновляется одними и теми же значениями.

Распределённые системы

В распределённых системах что-то постоянно отказывает. Сети фрагментируются, сервисы аварийно завершаются, жёсткие диски умирают и, да, кот может прогуляться по клавиатуре — бывает и так. Поэтому многие операции то и дело выполняются с повторной попытки. Но повторные попытки безопасны лишь при условии, что сами операции идемпотентны.

Полный пример: система обработки заказов

Хорошо, теперь давайте соберём всё разобранное здесь в конкретный пример, который соответствует системе, которая здесь схематически изображена. Имеется простой конвейер обработки заказов: заказы поступают из веб-приложения, проверяются сервисом заказа, поступают в очередь, а затем обслуживаются сервисом обработки заказов, который записывает информацию в базу данных.

Что мы создаём

Система устроена просто:

  1. Веб-приложение отправляет запросы HTTP POST, в которых содержатся данные о заказе

  2. Шлюз API обрабатывает маршрутизацпю, аутентификацию и ограничивает частоту передачи данных

  3. Сервис заказа проверяет заказы и публикует их в очереди

  4. Сообщения о заказах содержатся в Amazon SQS

  5. Сервис обработки заказов потребляет сообщения и записывает информацию в базу данных

  6. В базе данных с заказами содержится вся информация о наших заказах

  7. В очередь недоставленных сообщений отправляются все те сообщения, которые можно считать ядовитыми  

  8. Сервис уведомлений отправляет нашим клиентам подтверждения

Ключевой момент здесь заключается в том, что сервис обработки заказов должен быть идемпотентным.

Как сделать сервис обработки заказов идемпотентным

Сервис обработки заказов потребляет сообщения, поступающие от SQS, и выполняет конкретную бизнес-логику.

При обработке сообщений, то есть, когда происходит заказ, нам требуется:

  • Проверить, был ли уже обработан этот заказ

  • Если нет — вставить его в нашу OrdersDB

  • Если нет – приказать NotificationService, чтобы он отправил уведомление

Эта операция идемпотентна, поскольку мы проверяем, был ли уже обработан заказ. Для этого можно, например, выполнить запрос SELECT в OrderDB и вставлять данные лишь в случае, если их там пока нет. Схожую операцию можно выполнять для NotificationService или внутри NotificationService, работая с его собственной базой данных.

Правда, здесь будут возникать проблемы, связанные с конкурентностью, и с ними придётся справляться. Что, если у нас есть два разных экземпляра OrderProcessService, обрабатывающих одно и то же сообщение? И они оба одновременно выполняют SELECT. Тогда мы обработали бы сообщение дважды, а это нехорошо. Поэтому стоит обернуть данную логику в транзакцию.

В итоге у нас должно получиться нечто подобное:

Ещё одно замечание: следует попытаться обеспечить в системе полноценную отказоустойчивость, и для этого мы также поставим очередь между OrderProcessService и NotificationService, а затем сделаем так же, как и выше.

У идемпотентности есть область действия

Этот момент также часто остаётся недопонятым: иногда идемпотентность понимают так, будто при многократном выполнении одной и той же идемпотентной операции совсем ничего не меняется. То есть, что последующие вызовы никак не подействуют на систему.

Это не совсем верно. Идемпотентность касается только операций бизнес-логики, а не состояния всей системы в целом.

Допустим, у нас имеется идемпотентная операция обработки платежей. Если вызвать её трижды с одними и теми же реквизитами платежа, то в базе данных должна быть создана только одна транзакция. Это операция бизнес-логики, и она идемпотентна. Но это не означает, что при втором и третьем вызове ничего не происходит. Происходит следующее:

  • Мы продолжаем логировать информацию о вызовах в журнале операций

  • Мы как и раньше записываем время отклика и собираем мониторинговые метрики  

  • Мы как и раньше валидируем запрос

  • Мы проверяем, обрабатывали ли мы этот заказ ранее

Всё это может (и должно) происходить. Ключевой момент здесь таков: идемпотентной является базовая бизнес-операция, а не всё состояние системы. Абсолютно необходимо логировать тот факт, что вызов был получен. Необходимо отслеживать связанные с ним метрики. Это побочные эффекты, которые не противоречат идемпотентности.