Минутка просвещения

Читать в телеге. Когда-то там были посты не только от меня.

Пять стадий принятия саги

  1. Отрицание — дергаем что надо по HTTP с ретраями.
  2. Гнев — переходим на асинхронные сообщения.
  3. Торг — Transactional Outbox решит же большую часть проблем, да?
  4. Депрессия — 100% надежность не достижима, все равно что-то может сломаться. Но сделаем хотя бы идемпотентно, чтобы ретраить было проще.
  5. Принятие — ладно, сделаем еще сагу, чтобы хотя бы следить, какие шаги сделаны в рамках процесса.

по мотивам

Ссылка • Комментировать

Идемпотентность

Одно из базовых свойств, которое стоит поддерживать при асинхронной обработке команд/событий — это идемпотентность: при повторной обработке события результат не должен меняться (разве что какие-нибудь служебные поля вроде updated_at).

Достичь exactly-once доставки (т.е. когда гарантируется, что событие будет доставлено ровно 1 раз) в распределенных системах практически невозможно, но с помощью идемпотентной обработки и at-least-once системы (т.е. могут быть дубликаты) можно к этому очень близко подойти. В хардкорной реализации каждое сообщение имеет уникальный идентификатор, каждый клиент помнит идентификаторы обработанных сообщений и выкидывает дубликаты. Однако во многих случаях можно поступить проще и немного дополнить бизнес-логику, чтобы она учитывала возможность повторной обработки, благо многие системы поддерживают upsert или его аналог.

Чтобы не было недоразумений, это поведение стоит покрывать тестами. Наверняка в тестах есть какой-то вспомогательный метод, который проверяет поведение системы после получения какого-то события:

fun onReceiving(event: Event, assertions: () -> Unit)

Вот прямо внутрь него можно и встроить проверку идемпотентности: продублировать внутренности два раза, чтобы два раза прислать сообщение и два раза проверить результат.

Ссылка • Комментировать

Порядок ключей в словаре в питоне

В Python 3.6 была оптимизирована память, используемая dict. Раньше элементы хранились в массиве, каждый элемент — структура из хэша и ссылок на ключ и на значение. Поскольку используется открытая адресация, то многие ячейки были пусты и прорва памяти тратилась зря. В новой реализации в основном массиве хранится только индекс, а хэши и указатели хранятся в отдельном списке. За счет этого размер заполненной и пустой ячейки будут одинаковыми, а список с полезной нагрузкой почти всегда плотно заполненным (при удалении там остается заглушка, чтобы список за линейное время не двигать).

Один из побочных эффектов — ключи становятся упорядочены в порядке добавления (в 3.7 это закрепили на уровне требования). С одной стороны — прикольно, но с другой, когда читаешь код, который на это полагается, то так можно и с ума начать сходить (“почему в хэш-таблице ключи отсортированы?!”). Для читаемости лучше все-таки явно OrderedDict использовать.

Ссылка • Комментировать

Неймспейсы в k8s

В документации читаем:

In Kubernetes, namespaces provides a mechanism for isolating groups of resources within a single cluster. 
[...]
Namespaces are intended for use in environments with many users spread across multiple teams, or projects.

Однако эта “изоляция” очень условна, и я бы поостерегся ее использовать в мультитенантном смысле. Хотя бы потому, что, например, все сервисы имеют полное имя вида <service-name>.<namespace-name>.svc.cluster.local и с настройками по умолчанию ничто не мешает из неймспейса user1ns обратиться к сервису someService в неймспейсе user2ns просто используя someService.user2ns в качестве хоста. Это может иметь смысл в тестовых окружениях, но на проде — страшно.

Еще можно распихать по разным неймспейсам микросервисы, но когда команды разрастутся до “доменов” (групп команд), то это разделение потеряет смысл, т.к. скорее всего у каждого домена будет свой изолированный кластер.

А вот вполне классный вариант применения — отделить в служебные неймспейсы всякие логгеры, операторы, метрики, телеметрию, CI и т.п., чтобы в основном неймспейсе остались только сами сервисы с “полезной” логикой.

Ссылка • Комментировать

Проблемы монад и алгебраические эффекты

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

Первая и основная на мой взгляд проблема, с которой сталкиваешься при композиции нескольких Either/Result’ов — общий тип для ошибок. Кстати, это одна из проблем, на которую я мужественно забил во время своих забав с elm. Кроме этого, функции приобретают цвета, а с типами вроде Either[Either[ClientError, ServerError], Response] легко запутаться.

Одно из решений — Monad Transformers в Haskell, которые позволяют запихать одну монаду внутрь другой. Однако это довольно трудное для восприятия решение (судите сами) и типы все равно выглядят страшно.

Альтернативное решение — алгебраические эффекты. “Алгебраические” — потому что образуют алгебру, т.е. если функция f имеет эффект A, а функция g имеет эффект B, то любая их композиция будет иметь эффект C = A + B = B + A, причем А + 0 = A, где 0 — отсутствие эффекта. Алгебраические эффекты аналогичны по свойствам трансформерам, но должны быть проще для восприятия. И вариант их реализации, который описывает докладчик, практически идентичен тому, что предлагал Одерски больше года назад с проверяемыми исключениями (aka Capabilities).

В конце еще интересная мысль про то, что можно обрабатывать исключения и потом иметь возможность возвращаться к проблемному месту, но реализация этого явно дорога, а что она полезного даст — непонятно.

Ссылка • Комментировать

Устройство HTTP

Занятная статья, в первой части которой автор довольно просто рассказывает основные принципы работы HTTP/1.1 (я, например, никогда не задумывался о том, как работает отправка запроса/ответа частями (chunked)), а потом плавненько переходит на куски кода на Rust и подсвечивает кучу занятных вопросов про проксирование траффика HTTP/2 (в т.ч. для защиты от плохих парней). Поразительно, сколько есть серых зон в протоколе, и как хорошо, что в повседневной разработке о них не надо думать :)

Ссылка • Комментировать

Удаление дубликатов в таблице-списке

Способов удалить дубликаты в таблице довольно много: с использованием CTE, JOIN, подзапроса, извращений вроде пересоздания таблица через DISTINCT и т.д. Возьмем для примера группировку с USING:

DELETE FROM elements t1 USING elements t2 WHERE t1.some_key = t2.some_key AND t1.created_at > t2.created_at;

Тут одна колонка (some_key) используется для собственно определения дубликатов, а вторая (created_at) — для сортировки их между собой.

Но что делать если в таблице всего одна колонка, как различать два элемента, чтобы удалить не все? Тут пригодятся системные колонки. В данном случае — ctid, “физическое расположение данной версии строки в таблице”:

DELETE FROM list t1 USING list t2 WHERE t1.some_key = t2.some_key AND t1.ctid > t2.ctid;
Ссылка • Комментировать

Хуки СУБД и Transactional outbox

В событийно ориентированной архитектуре хуки довольно полезны: можно перед коммитом добавлять в транзакцию все события (transactional outbox, все дела), а после коммита — отправлять их в очередь сообщений. Однако стоит помнить, что количество соединений к базе ограничено и делать в них что-то длительное не стоит. И на эти грабли я наступил в процессе разработки, сделав отправку сообщений в RabbitMQ после коммита: соединения долго висели и в итоге кончались.

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

Уже потом нашел репозиторий-пост, в котором описан практически идентичный случай.

Ссылка • Комментировать