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

推荐订阅源

Help Net Security
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
U
Unit 42
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Vulnerabilities – Threatpost
T
The Exploit Database - CXSecurity.com
Know Your Adversary
Know Your Adversary
Simon Willison's Weblog
Simon Willison's Weblog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
F
Full Disclosure
Cyberwarzone
Cyberwarzone
C
Cisco Blogs
L
Lohrmann on Cybersecurity
Security Latest
Security Latest
宝玉的分享
宝玉的分享
博客园 - 三生石上(FineUI控件)
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
WordPress大学
WordPress大学
Last Week in AI
Last Week in AI
NISL@THU
NISL@THU
Cisco Talos Blog
Cisco Talos Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Check Point Blog
S
Schneier on Security
V
V2EX
月光博客
月光博客
G
GRAHAM CLULEY
D
DataBreaches.Net
P
Proofpoint News Feed
C
Cyber Attacks, Cyber Crime and Cyber Security
D
Docker
T
Tor Project blog
Project Zero
Project Zero
The Hacker News
The Hacker News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Visual Studio Blog
MongoDB | Blog
MongoDB | Blog
人人都是产品经理
人人都是产品经理
G
Google Developers Blog
博客园 - 【当耐特】
H
Hackread – Cybersecurity News, Data Breaches, AI and More
C
CERT Recently Published Vulnerability Notes
Cloudbric
Cloudbric
Microsoft Azure Blog
Microsoft Azure 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 миллионов точек без потерь
Две базы: одна пишет, другая читает. CQRS без культа и с последствиями
Павел · 2026-06-17 · via Все публикации подряд на Хабре

Привет, Хабр! Меня зовут Павел, я ведущий разработчик. После статьи про Kafka хочется продолжить тему продовых интеграций, но без попытки написать архитектурную энциклопедию на 40 минут чтения.

Сегодня про схему, которая на диаграмме выглядит очень спокойно:

Write DB -> Outbox -> Kafka -> Consumer -> Read DB

Одна база принимает изменения. Другая отвечает на чтение. Между ними события. На словах — красота. В проде — lag, backfill, дубли, версии событий и вопрос от бизнеса: “Почему я нажал сохранить, а в отчете еще старое?”

Две базы: одна пишет, другая читает

Две базы: одна пишет, другая читает

Зачем вообще разделять чтение и запись

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

  • быстро принимать изменения;

  • держать транзакции и бизнес-инварианты;

  • строить тяжелые отчеты;

  • отдавать списки с фильтрами;

  • делать поиск по полям, которые вчера “точно не понадобятся”;

  • не грустить под нагрузкой.

Если проблема только в том, что чтение давит на primary, иногда достаточно read replica. Это более простой путь.

Но replica повторяет структуру write-базы. Если для чтения нужна другая форма данных — денормализованная, предрассчитанная, заточенная под экран, поиск или отчет — появляется смысл в отдельной read model.

Коротко:

Подход

Когда подходит

Read replica

Нужно разгрузить чтение, схема данных та же

Read model

Нужно другое представление данных под запросы

Отдельная read DB

Нужен другой движок: ClickHouse, Elastic, Mongo, отдельный Postgres

Важно: CQRS не обязан означать две физические базы. Но в этой статье говорим именно про вариант, где write model и read model живут в разных хранилищах.

Как это обычно выглядит

Write DB, Outbox, Kafka и Read DB

Write DB, Outbox, Kafka и Read DB

Поток записи:

  1. Command API принимает команду.

  2. В write DB меняется бизнес-сущность.

  3. В той же транзакции пишется запись в outbox.

  4. Outbox relay публикует событие в Kafka.

  5. Consumer читает событие.

  6. Consumer обновляет read DB.

  7. Query API читает из read DB.

Почему outbox пишется в той же транзакции, что и бизнес-изменение?

Потому что иначе есть неприятный разрыв:

Сценарий

Что случилось

БД обновили, Kafka отправить не успели

Изменение есть, события нет

Kafka отправили, БД откатилась

Событие есть, изменения нет

Бизнес-строка и outbox в одной транзакции

Если изменение зафиксировано, событие не забыли

Outbox не делает всю систему magically exactly-once. Он решает конкретную задачу: событие не теряется относительно изменения в базе.

Главная цена: eventual consistency

После разделения write DB и read DB появляется окно несогласованности.

Пользователь нажал “сохранить”. Write DB уже обновлена. API ответил 200 OK. Но read DB еще не обновилась: событию нужно пройти outbox, Kafka, consumer и projection logic.

Окно eventual consistency

Окно eventual consistency

Это нормально, если окно измерено и согласовано. Это плохо, если команда делает вид, что окна нет.

Не надо объяснять бизнесу так:

У нас CQRS, поэтому read side eventually consistent.

Лучше так:

Изменение сохраняется сразу. В отчетах и поиске оно появляется с задержкой. Нормальное окно — до N секунд. Если больше, это инцидент, у нас есть метрика и алерт.

Eventual consistency — это не баг сам по себе. Баг — не знать, насколько eventual ваша consistency.

Что мониторить

Один Kafka lag не отвечает на все вопросы. Consumer может отставать по сообщениям, а пользователь страдает от задержки в секундах. Или наоборот: сообщений мало, но одно старое событие застряло и портит read model.

Минимальный набор метрик:

Метрика

Зачем

Outbox size

Relay жив или копит долг

Oldest outbox age

Сколько самое старое событие ждет публикации

Kafka consumer lag

Сколько сообщений осталось обработать

Event age

Насколько старое событие сейчас применяем

Projection errors

Не сломался ли consumer read model

DLQ size

Сколько событий ушло в ручной разбор

Read model freshness

Насколько read DB отстает от write DB

Самая честная бизнес-метрика:

сколько объектов в read model отстает от write model больше N секунд.

Она неприятная. Поэтому полезная.

Дубли: consumer должен быть идемпотентным

Kafka может отдать событие повторно. Типичный сценарий:

  1. Consumer прочитал событие.

  2. Обновил read DB.

  3. Упал до commit offset.

  4. После рестарта получил то же событие еще раз.

Это нормальная цена at-least-once обработки. Если handler не идемпотентный, read model может получить дубль, неверный счетчик или старое состояние поверх нового.

Idempotent consumer и повторная доставка

Idempotent consumer и повторная доставка

Базовая защита:

CREATE TABLE inbox_messages (
    event_id UUID PRIMARY KEY,
    processed_at TIMESTAMP NOT NULL
);

Логика простая:

  1. Начать транзакцию в read DB.

  2. Проверить event_id в inbox.

  3. Если уже обработан — пропустить.

  4. Если новый — применить изменение к read model.

  5. Записать event_id в inbox.

  6. Зафиксировать транзакцию.

  7. Commit offset.

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

Порядок событий

Kafka сохраняет порядок внутри partition, а не глобально по всему topic. Поэтому, если события одного агрегата должны применяться последовательно, ключ сообщения обычно выбирают по агрегату:

key = orderId

И все равно лучше иметь версию:

{
  "eventId": "bda67a8d-9f11-4e49-98d9-4f5f0a6d10a1",
  "aggregateId": "order-123",
  "version": 42,
  "occurredAt": "2026-06-16T10:00:00Z",
  "type": "OrderStatusChanged"
}

Версия помогает consumer’у понять, что делать:

Что пришло

Реакция

Следующая версия

Применить

Уже примененная версия

Пропустить

Старая версия

Пропустить или отправить в диагностику

Разрыв версий

Остановить обработку/отправить в retry

Без версии read model может молча принять старое событие поверх нового. А молчаливые ошибки в read model особенно прекрасны: все работает, просто неправильно.

Когда так делать не надо

Разделять write/read DB не стоит, если:

  • одна база спокойно справляется;

  • проблему решает индекс, query tuning или replica;

  • бизнес требует строгий read-after-write на всех экранах;

  • нет плана backfill/replay;

  • команда не готова владеть outbox, consumer, DLQ и мониторингом;

  • никто не может ответить, какое окно freshness считается нормальным.

CQRS не должен появляться потому, что схема стала красивее. Красивые схемы не отвечают на алерты.

Короткий чек-лист

Перед отдельной read DB я бы спросил:

  • Что именно болит: CPU, IO, locks, latency, сложность запросов?

  • Почему read replica не подходит?

  • Какое допустимое окно eventual consistency?

  • Где хранится outbox?

  • Как consumer переживает дубли?

  • Есть ли eventId, aggregateId, version, occurredAt?

  • Как пересобрать read model с нуля?

  • Что мониторим: lag, freshness, DLQ, outbox age?

Главная мысль:

Две базы — это не “одна для записи, другая для красоты”. Это контракт: write side отвечает за истину и изменения, read side отвечает за быстрое чтение и измеряемую свежесть.

В Telegram-канале “Продовый оффсет” отдельно выложу чек-лист по read model, шаблон метрик freshness и пример outbox/inbox-таблиц для .NET + Kafka.

На что опирался