Паттерны декомпозиции данных

Содержание

Слайд 2

Меня хорошо слышно && видно?

Меня хорошо слышно && видно?

Слайд 3

Карта вебинара Паттерны миграции данных Event Sourcing CQRS

Карта вебинара

Паттерны миграции данных
Event Sourcing
CQRS

Слайд 4

Кейс. Кэширование данных в разных сервисах. Есть приложение «Интернет-магазин». У нас

Кейс. Кэширование данных в разных сервисах.

Есть приложение «Интернет-магазин». У нас есть

несколько сервисов:
Сервис «Поиск товаров»
Сервис «Склад»
Сервис «Промокоды и акции»
В поиске товаров надо уметь искать и показывать товары по атрибутам, которые лежат в других сервисах:
Скидочные акции
Наличие на складе
И т.д.
Как поддерживать консистентность данных в этих сервисах? «Поиск товара» – это по сути кэш для данных из других сервисов
Слайд 5

Кейс Чтобы избежать излишней связности сервисов и сложности реализации распределенных транзакций,

Кейс

Чтобы избежать излишней связности сервисов и сложности реализации распределенных транзакций, решили

использовать очереди.
Каждый сервис, при изменениях у себя отсылает событие в очередь, которую слушает consumer и применяет эти изменения в поиске.
Слайд 6

Кейс

Кейс

Слайд 7

Кейс Например Склад отправляет события: Товар id = 42 зарезервирован в

Кейс

Например
Склад отправляет события:
Товар id = 42 зарезервирован в количестве 2 штух

на складе
Привезли 50 единиц товара id = 42 на склад
Промокоды отправляет события:
На товар id = 42 теперь действует скидка 50% по акции "купи сегодня»
Пусть очередь событий выглядит так:
[
{”event_type”: “productReserved”, “product_id”: 42, “quantity”: 2},
{“event_type”: “productShipped”, “product_id”: 42, ”quantity”: 50},
{“event_type”: “providedDiscount”, “product_id”: 42, “discount”: “SEGODNYA50” }
]
Слайд 8

Кейс Последовательно применяем события: [ {”event_type”: “productReserved”, “product_id”: 42, “quantity”: 2},

Кейс

Последовательно применяем события:
[
{”event_type”: “productReserved”, “product_id”: 42, “quantity”: 2},
{“event_type”: “productShipped”,

“product_id”: 42, ”quantity”: 50},
{“event_type”: “providedDiscount”, “product_id”: 42, “discount”: “SEGODNYA50” }
]
Как меняется состояние в сервисе «Поиск» после обработки каждого события
Слайд 9

Кейс Однажды сервис ”Cклад” упал, и не успел отправить сообщение в

Кейс

Однажды сервис ”Cклад” упал, и не успел отправить сообщение в очередь,

но успел изменить данные у себя в БД. Соответственно, событие «перекладчик» не прочитал, и данные разошлись.
Слайд 10

Возможное решение (outbox table) Завести табличку с событиями (outbox table). Коммиты

Возможное решение (outbox table)

Завести табличку с событиями (outbox table).
Коммиты в табличку

с данными и outbox table происходят транзакционно (1)
Специальный сервис читает outbox table (2)
Публикует сообщения в брокер сообщений (3)
Слайд 11

Кейс Однажды сервис "перекладчик" упал, и не успел ack-нуть сообщение, но

Кейс

Однажды сервис "перекладчик" упал, и не успел ack-нуть сообщение, но успел

уже изменить данные. И после того, как поднялся перепрочитал сообщение еще раз.
ИЛИ
Сервис, читающий outbox в сервисе Склад, успел отправить сообщения, но не успел пометить, что уже отправлял сообщение, и после рестарта отправил еще раз
Слайд 12

Возможное решение 1 (idempotent receiver) Завести табличку с отработанными сообщениями (processed_message).

Возможное решение 1 (idempotent receiver)

Завести табличку с отработанными сообщениями (processed_message).
Коммиты в

табличку с данными и processed_table происходят транзакционно
Если сообщение уже было, то ничего не делать
Слайд 13

Возможное решение 2 Для исправления решили передавать данные прямо в событии (идемпотентные события)

Возможное решение 2

Для исправления решили передавать данные прямо в событии (идемпотентные

события)
Слайд 14

Кейс Как-то менеджер по скидкам, когда редактировал товары для акций ошибся:

Кейс

Как-то менеджер по скидкам, когда редактировал товары для акций ошибся: и

ввел неправильную акцию для товара, но быстро исправился. Но все-равно в результате было отправлено два события:
На товар id = 42 теперь действует скидка 60% по акции "купи завтра"
На товар id = 42 теперь действует скидка 50% по акции "купи сегодня” – поменяли на правильную акцию
И так вышло, что один из перекладчиков взял второе событие отработал первым, а другой перекладчик первое событие уже после этого (например, первому не повезло с сетью к базе)
Слайд 15

Возможное решение? 1 При отправке в outbox: В транзакции обновляем version_id

Возможное решение? 1

При отправке в outbox:
В транзакции обновляем version_id в объекте
Вставляем

предыдущий и текущий version_id в событие (в outbox table)
При чтении события:
Транзакционно в processed_messages проверяем, что version_id в данной сущности = version_id в событии.
Проставляем version_id = new_version_id
Слайд 16

Возможное решение 2 Читаем событие Забираем актуальные данные из point of

Возможное решение 2

Читаем событие
Забираем актуальные данные из point of truth баз

или сервисов
Фактически получается, что события рассматриваются как сигнал к частичной инвалидации кэша из первоисточника
В этом случае табличка processed_messages не нужна, мы сразу получаем и идемпотентность
Слайд 17

Возможное решение 2 Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Возможное решение 2

Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Слайд 18

Возможное решение 2 Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Возможное решение 2

Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Слайд 19

Возможное решение 2 Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Возможное решение 2

Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Слайд 20

Возможное решение 2 Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Возможное решение 2

Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Слайд 21

Возможное решение 2 Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Возможное решение 2

Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Слайд 22

Возможное решение 2 Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Возможное решение 2

Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Слайд 23

Возможное решение 2 Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Возможное решение 2

Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Слайд 24

Возможное решение 2 Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Возможное решение 2

Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Слайд 25

Возможное решение 2 Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Возможное решение 2

Такая схема используется в Badoo - http://www.highload.ru/2017/abstracts/2930.html

Слайд 26

Возможное решение 3 (transactional log) Читаем WAL-лог базы данных или прикидываемся

Возможное решение 3 (transactional log)

Читаем WAL-лог базы данных или прикидываемся репликой
Можем

читать outbox table, можем читать сразу данные
Слайд 27

Возможное решение 3 (transactional log) Debezium (https://debezium.io/) – платформа для Change Data Capture (CDC)

Возможное решение 3 (transactional log)

Debezium (https://debezium.io/) – платформа для Change Data

Capture (CDC)
Слайд 28

Возможное решение 3 (transactional log) Такая схема используется в Mail.Ru (mytarget) - https://habr.com/ru/company/mailru/blog/219015/

Возможное решение 3 (transactional log)

Такая схема используется в Mail.Ru (mytarget) -

https://habr.com/ru/company/mailru/blog/219015/
Слайд 29

Возможное решение 3 (transactional log) Такая схема используется в Rambler - https://www.youtube.com/watch?v=oByOmhOmOh4

Возможное решение 3 (transactional log)

Такая схема используется в Rambler - https://www.youtube.com/watch?v=oByOmhOmOh4

Слайд 30

Event Sourcing А почему бы в базе хранить не состояние объекта,

Event Sourcing

А почему бы в базе хранить не состояние объекта, а

просто набор событий?
Банковский счет – это производная от набора транзакций
balance = sum(transaction.value), а не наоборот
Любое состояние – это производная от набора фактов
Факты или события – не меняются, а то, как мы представляем данные – меняется. На самом деле мы храним факты, а не представление.
Слайд 31

Event Sourcing Пусть будет набор событий во времени:

Event Sourcing

Пусть будет набор событий во времени:

Слайд 32

Event Sourcing Если мы проиграем во времени все события, получится конечное

Event Sourcing

Если мы проиграем во времени все события, получится конечное состояние.


state –> func (init(), events)
Слайд 33

Что может дать Event Sourcing? Аудит лог за бесплатно Крутые средства

Что может дать Event Sourcing?

Аудит лог за бесплатно
Крутые средства для дебага


Не теряешь данных и можешь делать какие угодно проекции
Слайд 34

Event Sourcing Например, можно хранить вот в такой табличке:

Event Sourcing

Например, можно хранить вот в такой табличке:

Слайд 35

Event Sourcing Как получить текущее состояние объекта (агрегата)? Последовательно применяем события

Event Sourcing

Как получить текущее состояние объекта (агрегата)?
Последовательно применяем события к объекту

и меняем его состояние
Поскольку события – единственный источник состояния в Event Sourcing, то Event должен все(!) необходимые данные для того, чтобы можно было вычислить новое состояние (объекта).
Слайд 36

Команды, события и применение В мире Event Sourcing есть: Cобытия –

Команды, события и применение

В мире Event Sourcing есть:
Cобытия – что-то, что

случилось в прошлом
Команды - не изменяют состояния, возвращают события
func command(state, events) -> event(s)’
Применения – изменяют состояние
func apply(state, events) –> state’
Слайд 37

Event Sourcing – меняет способ написания кода Крайне важно, чтобы изменение

Event Sourcing – меняет способ написания кода

Крайне важно, чтобы изменение состояния

не происходило вне событийной модели.
Функции или методы классов, которые меняют состояния
func f(state) -> state’
Должны быть в явном виде разделены на команды и применения
func f_command(state, events) –> events’
func f_apply(state, events’) -> state’
При этом функции, которые не трогают состояние можно не трогать (инфраструктурный слой или утилиты, например)
Слайд 38

Event Sourcing – меняет способ написания кода Любое изменение состояния происходит

Event Sourcing – меняет способ написания кода

Любое изменение состояния происходит только,

как результат применения события
Раньше:
class Account():
def change_name(self, new_name):
self.name = new_name
Event Sourcing:
class Account():
def change_name(self, new_name):
self.raise(AccountNameChanged(new_name), apply=True)
@on(AccountNameChanged)
def apply(self, e):
self.name = e.new_name
Слайд 39

Event Sourcing – меняет способ написания кода

Event Sourcing – меняет способ написания кода

Слайд 40

Command Sourcing vs Event Sourcing Если записывать не события, а команды

Command Sourcing vs Event Sourcing

Если записывать не события, а команды в

стор, то получим command sourcing.

Event Sourcing
Храним только события, которые про изменение состояния
Replay без побочных эффектов

Command Sourcing
Храним команды
При replay-ее могут быть побочные эффекты (дважды созданные заказы, несколько отправок на email) и т.д.

Слайд 41

Event Sourcing – пример pet project https://github.com/gregoryyoung/m-r – пример реализации Event

Event Sourcing – пример pet project

https://github.com/gregoryyoung/m-r – пример реализации Event Sourcing

от его создателя Грега Янга
Слайд 42

Снапшоты Время от времени для производительности, приходится делать снапшоты

Снапшоты

Время от времени для производительности, приходится делать снапшоты

Слайд 43

Event Sourcing - паттерн уровня хранения данных Event Sourcing – это

Event Sourcing - паттерн уровня хранения данных

Event Sourcing – это не

паттерн уровня архитектуры всей системы, а паттерн уровня хранения.
У нас архитектура основана на Event Sourcing» == «у нас архитектура основана на RDBMS»
Один event store на всю компанию == Один postgres на всю компанию
Совершенно нормально иметь в одном микросервисе ES/CQRS, а в другом RDBMS.
Слайд 44

Event Sourcing – плюсы и минусы Плюсы Бесплатный аудит-лог Производительность event-store-ов

Event Sourcing – плюсы и минусы

Плюсы
Бесплатный аудит-лог
Производительность event-store-ов обычно хорошая
Можно делать

темпоральные запросы
Можно строить какую угодно аналитику, т.к. храним все данные
Минусы
Миграции данных делать тяжело
Рефакторинг и изменение событийной модели может быть сложно
Крутая кривая обучения из-за того, что меняется стиль программирования
Возможное дублирование кода
Делать выборки (quering) сложно (без CQRS паттерна использовать крайне тяжело)
Слайд 45

Event Sourcing библиотеки и хранилища EventStore – хранилище от Янга Axon

Event Sourcing библиотеки и хранилища
EventStore – хранилище от Янга
Axon - https://axoniq.io/


Eventuate http://eventuate.io/
Грег Янг считает, что клиентские библиотеки и фреймворки не нужны
Слайд 46

А как делать выборки? Сложные выборки из EventStore делать сложно. Поэтому на помочь приходит паттерн CQRS

А как делать выборки?

Сложные выборки из EventStore делать сложно. Поэтому на

помочь приходит паттерн CQRS
Слайд 47

CQRS CQRS – Command/Query Responsibility Segregation Давайте отделим чтение (Read Model/Query) от записи (Write Model/Command)

CQRS

CQRS – Command/Query Responsibility Segregation
Давайте отделим чтение (Read Model/Query) от записи

(Write Model/Command)
Слайд 48

Зачем отделять чтения от записи? Операции чтения (queries) и записи (commands)

Зачем отделять чтения от записи?

Операции чтения (queries) и записи (commands) имеют

несколько принципиальных различий в требованиях
Консистентность
Query: Большинство систем «ок» с eventual consistency для запросов
Command: Неконсистентные данные для операций записи часто очень плохо
Хранение данных
Command: требуется транзакционность хранилища и зачастую удобнее иметь как минимум 3ью нормальную форму
Query: для чтения лучше подходит денормализованная форма
Масштабируемость
Command: в большинстве систем команды производят не очень большую нагрузку (в целом).
Query: в большинстве систем чтение производит намного больше нагрузки
Крайне тяжело сделать так, чтобы единая модель и единое хранилище удовлетворяло противоречащим требованиям Query и Command
Слайд 49

CQRS CQRS является органичным способом дополнением Event Sourcing. CQRS может быть

CQRS

CQRS является органичным способом дополнением Event Sourcing.
CQRS может быть без

ES, ES без CQRS тоже может, но в редких случаях.
CQRS не является архитектурным паттерном для всей системы
CQRS не обязывает иметь 2 разных БД или NoSQL хранилище.
CQRS не обязательно приводит к eventual consistency – Read Model может обновляться синхронно.
Иногда Write модель может читать из Read Model-и
Write модель может возвращать значения
Слайд 50

Опрос https://otus.ru/polls/7756/

Опрос

https://otus.ru/polls/7756/