Архитектура Стартапа - Anton Skogorev Engineering & AI – Telegram
Архитектура Стартапа - Anton Skogorev Engineering & AI
2.1K subscribers
49 photos
1 video
2 files
109 links
Канал про архитектуру быстрорастущего бизнеса.

Привет, меня зовут Антон @skogorev.
Я - Технический Директор AI Center Tinkoff, ex Yandex Go Senior EM.

В переписках остается много полезных материалов, теперь я собираю их на этом канале.
Download Telegram
RESTful API Patterns

Часто приходится изобретать одни и те же вещи при проектировании API.
В статье вместе с подробными примерами собраны концепции:
- пагинация
- фильтрация
- асинхронные операции
- версионирование
- агрегация
- локализация

https://levelup.gitconnected.com/restful-api-patterns-81930c43e494

#article #pattern #microservices #practices #en
Фильтр Блума
https://en.wikipedia.org/wiki/Bloom_filter

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

Можно воспользоваться Блум-фильтром.
Создадим в сервисе хэш-таблицу фиксированного размера и заполним ее ключами пользователей, у которых есть активные заказы. Будем для каждого пользователя перед походом в базу данных проверять - есть ли его идентификатор в этой хэш-таблице.
Но ведь будут коллизии, следовательно, ложные срабатывания? - спросите вы и будете правы. Однако, в данной задаче ложные срабатывания не приведут к проблемам.
Когда мы находим совпадение в хэш-таблице - мы все равно идем в базу данных за заказами. Если их там не окажется (по причине коллизии в хэштаблице), мы возвратим пользователю пустой список.
Однако, мы исключим большое количество пользователей, которых в этой таблице точно нет.
Размеры такой хэш-таблицы можно подобрать исходя из ваших требований и ограничений.

https://en.wikipedia.org/wiki/Bloom_filter

#algo #doc #wiki #practices #ru
Вы проектируете Netflix с нуля. Нужно сделать сервис, который будет отдавать пользователю рекомендуемые фильмы на главной.
Начальный DAU — 1 000 000, через год — 100 000 000.
Какой запас прочности в rps вы обеспечите на старте для этого сервиса?
Anonymous Poll
5%
100 rps
14%
500 rps
25%
1000 rps
12%
2000 rps
10%
5000 rps
21%
10000 rps
1%
15000 rps
4%
20000 rps
3%
30000 rps
5%
50000 rps
Стратегия обработки ошибок Circuit Breaker pattern.
https://medium.com/@kirill.sereda/стратегии-обработки-ошибок-circuit-breaker-pattern-650232944e37

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

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

Одним из способов решения этой проблемы служит имплементация circuit breaker. Паттерн подразумевает "умное" управление перезапросами. Вместо того, чтобы каждый раз делать запрос в недоступный сервис, подсчитывается общая статистика отказов в скользящем окне, из которой можно будет понять - а стоит ли пытаться делать запрос (или перезапрос) в этот сервис, или в этот раз стоит деградировать эту функциональность.

Одна из самых известных имплементаций этого паттерна, которую можно изучить - это реализация компании Netflix Hystrix (btw: которую компания перестала разрабатывать).

https://medium.com/@kirill.sereda/стратегии-обработки-ошибок-circuit-breaker-pattern-650232944e37

#algo #doc #medium #pattern #practices #ru
Materialized View Pattern

Давайте возьмем за пример такую задачу - в мобильном приложении нужно отображать топ-100 пользователей за последние 24 часа по количеству какой-то активности.
Пользователи хранятся в одной базе, активность хранится в другой базе (отсортированные в последовательности их создания).
Решение в лоб - на запрос топ-100 пользователей сделать запрос а базу за всей активностью за 24 часа, построить топ, сходить в базу данных пользователей (чтобы достать ники пользователей), закэшировать этот ответ и отдавать его, в том числе, другим пользователям какое-то время.

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

Materialized View pattern подразумевает создание "виртуального" представления данных, которе будет эффективно работать для получение нужной информации:

Можем каждые несколько минут (по какому-нибудь крону) строить топ-100 пользователей за последние 24 часа, будем также получать всю нужную информацию о пользователях и из всего этого создавать новую View, например, в in-memory базе данных. Это и будет наш materialized view. На запросы пользователей на топ-100 мы просто будем быстро отдавать уже подготовленные данные.

https://docs.microsoft.com/ru-ru/azure/architecture/patterns/materialized-view
http://www.jeisystems.co.uk/tech-blog/programming-blog/materialized-view-pattern/

#pattern #practices #doc #ru #en
Учения

Обычно написание кода подразумевает обработку ошибок. Тут запрос вернул 500, и мы подставили дефолтное значение; там ошибка при попытке записать на диск, и мы показали пользователю сообщение итд.

Код обработки ошибок, как и любой другой код, имеет свойство ломаться и устаревать, если он не работает. Я часто встречаюсь с тем, что код, который должен был спокойно обрабатывать ошибку приводит к каким-нибудь сегфолтам. При каких-то минорных неполадках в системе, которые должны легко парироваться, может прилечь весь бизнес.

Конечно, отличным решением может быть покрытие тестами вроде: если сервис А отвечает Б, то должно происходить В. Но если ресурсов на имплементацию таких вещей нет, то можно обойтись минимумом - учения.

Учения в этом случае - ручные тесткейсы интеграционного тестирования, которые можно провести прям на продакшн сервисе с целью понять, что ваша обработка ошибок работает так, как должна.

Что можно сделать и что проверяем:
— Гасить отдельные сервисы
проверяем: что пользователи этих сервисов умеют работать без них (graceful degradation)
— Гасить часть машин, отдельные ДЦ
проверяем: что сервис продолжает жить при таких вещах, как выход из строя оборудования (например, пожар), что остальные машины справляются с возросшей нагрузкой
— Отключать функциональность
проверяем: что функциональность, которая должна быть отключаемой - остается отключаемой.

#reliability #practices #ru
Проводите учения?
Anonymous Poll
79%
Да
14%
Хотелось бы
7%
Нет
Consistent hashing

Когда наступает пара масштабироваться, встает вопрос "как" делить пользовательские запросы между несколькими инстансами. Если у вас stateless сервисы, то от запроса к запросу одного и того же пользователя можно направлять на разные инстансы - никаких проблем нет.

Проблемы появляются, когда у вас есть какой-то state (например, кэш авторизаций пользователей). Ну ничего, можно приземлить пользователей на какой-то один инстанс, если взять его id по модулю количества этих инстансов (instance_id = user_id % instances_count - 1).
Но что, если вам требуется добавить один новый инстанс? Если мы оставим нашу формулу, то большинство клиентов сменит инстанс, что приведет к потере state (и, например, валу перезапросов на сервис авторизации).

Особый вид хэширования Consistent hashing служит для того, чтобы бОльшая часть пользователей (будут переназначены только user_id_count/instances_count ключей) осталась "жить" на том же инстансе при добавлении нового.

В статье объясняется принцип его работы с картинками на основе кольцевого массива.

https://medium.com/better-programming/load-balancers-and-consistent-hashing-in-6-minutes-b5fc460aea4e

#practices #medium #en
Полагаю, все здесь знают, что такое 1-1 (или one on one, 1:1, один-один, тет-а-тет). Я считаю, что этот формат общения — один из самых важных инструментов в рукаве тимлида/руководителя, но очень недооценен. Про него обе сегодняшние статьи.

Мне 1:1 довольно долго не заходил, пока я где-то не наткнулся на хороший материал про этот формат (к сожалению, найти его уже не могу). Основная идея в том, что он предназначен не для получения статуса по задачам от подчиненного, а для обсуждения успехов, неудач, планов и т.д. Если сделать 1:1 откровенными, честными, открытыми, то можно будет узнавать о людях очень много такого, чего в обычной обстановке они бы не рассказали: свои взгляды на карьеру, чего хочется, что не нравится. Вам, как руководителю, остается только использовать эту информацию для роста человека и предотвращения кризисов. Статья уровня “сейчас я научу вас жизни”, но в ней есть интересные мысли. https://medium.com/@zackbloom/youre-doing-one-on-ones-wrong-dfc27f36f204

Наверное, не сильно ошибусь, если скажу, что большинство тимлидов и руководителей на этом канале так или иначе вышли из разработки/тестирования/администрирования, в общем, люди с техническим бэкграундом. По своему опыту, таким людям не всегда легко дается открытое общение с начальством/подчиненными. Следующая статья как раз про то, что хорошие 1:1 получаются тогда, когда вам “неловко”: вы перешагиваете через себя и рассказываете о своих мыслях, мечтах, планах, неудачах. В статье есть примеры вопросов/тем, которые подталкивают к открытому общению. https://medium.com/@mrabkin/the-art-of-the-awkward-1-1-f4e1dcbd1c5c (есть вторая часть про то, как получать честный фидбек о себе — тоже рекомендую).

От себя хочу добавить, что большинство менеджерских инструментов, включая 1:1, можно применять снизу вверх. Если вы считаете, что 1:1 с вашим руководителем могут проходить эффективнее, сами предложите поменять формат. Если вы видите, что вашему руководителю не о чем с вами поговорить, заведите разговор сами, например, расскажите, каким вы видите свой рост, как вы развиваетесь, спросите совета по застрявшим проектам/задачам и т.д.

А вы считаете 1:1 полезными? (Отвечайте отрицательно, если не проводите/с вами не проводят).
Saga Pattern

Большую головную боль в микросервисной архитектуре приносит с собой вопрос - как добиться согласованных изменений в нескольких микросервисах?

Паттерн Сага пытается решить этот вопрос. Он подразумевает, что каждый сервис выполняет транзакции (по ACID принципам) в той зоне, за которую он ответственен. Также сервис должен поддерживать компенсационную транзакцию, которая будет откатывать изменения, если что-то пошло не так.
Набор таких транзакций и есть "сага".
Есть два способа того, как вести сагу, отличающиеся тем, кто в схеме оркестрирует изменениями:
- Choreography, где каждый сервис сам тригерит следующую транзакцию
- Orchestration, при которой есть один общий оркестратор, который берет на себя ответственность за последовательность саги

https://microservices.io/patterns/data/saga.html

UPD: из чата посоветовали еще несколько полезных материалов по теме:
#en - https://medium.com/swlh/microservices-architecture-what-is-saga-pattern-and-how-important-is-it-55f56cfedd6b
#ru - https://habr.com/en/company/avito/blog/426101/
#en - https://speakerdeck.com/caitiem20/distributed-sagas-a-protocol-for-coordinating-microservices
#ru - https://habr.com/en/company/oleg-bunin/blog/418235/

#pattern #microservices #practices #doc #en #ru
Graceful degradation

Должна ли какая-то функциональность ломать весь сервис? — Заданный таким образом вопрос, конечно же, получит ответ "нет".
А что, если сломается какая-то core функциональность? Например, в вашем сервисе пользователь получает какую-то услугу за деньги и ломается модуль, отвечающий за оплату. Отдавать пользователю при этом его покупку?
И, конечно, ответ на вопрос зависит от бизнес требований, но я бы сказал, что в большинстве случаев — ответ такой же — нет.
В случае, когда есть выбор — сломаться совсем или сделать хоть что-то (если это не приведет, конечно, к неконсистентному состоянию) почти всегда нужно выбирать сделать хоть что-то.
В такой парадигме мышления, любой сервис должен быть построен таким образом, чтобы без проблем отстреливалась любая функциональность.

Я где-то прочитал хороший пример, попробую воспроизвести:
Если вы показываете персонализированный топ фильмов пользователю и ...
... у вас отвалились персональные рекомендации — просто показывайте фильмы, которые пользователь еще не смотрел
... у вас отвалились пользователи — просто показывайте общий топ
... у вас сломалось совсем все — показывайте какой-нибудь статичный файл со списком фильмов

Сегодня доклад моего коллеги про Graceful degradation
https://habr.com/en/company/yandex/blog/438606/

#practices #speech #habr #ru
Monolith to Event-Driven Microservices with Apache Kafka

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

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

https://medium.com/swlh/monolith-to-event-driven-microservices-with-apache-kafka-6e4abe171cbb

#monolith #pattern #microservices #practices #en
Ретроспектива

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

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

https://donskih.ru/2016/09/retrospektiva-proekta/
https://tproger.ru/blogs/retrospective-analysis/

#practices #people #ru
Вы проводите ретроспективы?
Anonymous Poll
77%
Проводим
23%
Не проводим
Microservices: don’t use them yet!
Микросервисы: пока не используйте их!

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

— Количество микросервисов должно быть меньше, чем количество разработчиков
— Методы сервиса теперь обязательно должны иметь REST api и валидации
— Переключения контекста реально дороги
— Монорепозиторий - серебряная пуля

https://medium.com/teamzerolabs/micro-services-dont-use-them-yet-f58a2758d8f9

#monolith #microservices #practices #en
Шардирование по географии - скорее всего, плохое решение для вас.

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

Одна из самых простых вещей, которая приходит в голову - это разделить пользователей по географии, например, по городам: пользователи из города N будут всегда обрабатываться на кластере 1, пользователи из города M будут всегда будут приходить на кластер 2.

Но за простотой реализации такого подхода стоит достаточно сложная оркестрация (и, даже, жонглирование) нагрузки. У вас есть 2 кластера: на одном отправляются запросы из города N, на другой - из города M, нужно держать в голове:
— В какой кластер добавлять запросы из третьего нового города
— Что делать, если границы города N сильно меняются и этот кластер уже не выдерживает
— Как перераспределять ресурсы, если какой-то город выключается

Хотя преимущества тоже есть:
— Проблемы в кластере, который обрабатывает один набор городов не влияют на остальные
— Если вынос какого-то города подразумевает уменьшение требуемых на обработку ресурсов (например, если вы делаете роутинг и вам нужно держать в памяти граф. Географическое разделение поможет сэкономить ресурсы)

По теме шардирования, можно почитать неплохой доклад с конференции HighLoad:
https://habr.com/en/company/oleg-bunin/blog/433370/
Event Sourcing
https://martinfowler.com/eaaDev/EventSourcing.html

Долго думал как подступиться к этой обширной теме. Сама идея кажется просто замечательной — вместо того, чтобы в stateful сервисе безвозвратно менять этот самый state (например, апдейтом в базу данных), можно вести event-log изменений. Этот лог изменений можно будет накатывать на ту же базу данных и иметь ее последнюю версию, можно полностью восстановить текущее состояние с нуля, можно получить снапшот на абсолютно любой момент времени в прошлом, можно даже сделать rollout (из предположения, что у эвента будет тривиальная обратная операция). Из практических целей стоит отдельно отметить возможность ретрансляции эвентов в другие системы, например, аналитические без каких-то специальных телодвижений.

Помимо очевидных плюсов, тако подход встречается в паре с CQRS, где подобный event-log будет единым источником правды.

Но самое интересное, это, конечно, минусы. В "каноническом описании" по ссылке ниже все очень подробно расписано, я лишь остановлюсь на некоторых:
— Дороговизна. Если эвентов очень много, нужно искать трейдофы на хранение, восстановление, сохранение снапшотов.
— Усложнение кода. Получить результат из системы с event-sourcing может быть сложной задачей.
— Интеграция с сервисами, не живущие в такой же идеологии (тут рекомендуют писать обертки для таких сервисов, что тоже своего рода усложнение).

Делитесь своими комментариями, ссылками и успешными примерами использования в чате.

https://martinfowler.com/eaaDev/EventSourcing.html
https://microservices.io/patterns/data/event-sourcing.html

#pattern #microservices #practices #doc #en
Plug-in Architecture

Имплементируя фичу за фичей вы начинаете замечать, что компоненты начинают переплетаться между собой, создавай достаточно сложные для понимания и удержания в голове зависимости. Рано или поздно это приводит к проблемам — знакома ли вам ситуация, когда только написанный код в одном месте поломал вообще, с первого взгляда, несвязанную функциональность?
Еще пример: команда начинает "раздувать" текущий код, гонясь, в первую очередь, за быстрым продуктовым результатом, мы получаем лонгрид в функциях, в файлах с кодом, который тяжело читать, невозможно поддерживать и легко сломать.

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

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

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

https://medium.com/omarelgabrys-blog/plug-in-architecture-dec207291800

#practices #medium #article #en
Все, что можно сделать на бэкенде — нужно делать на бэкенде

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

Однако, огромный минус, который перевешивает плюсы, основан на времени деплоя клиентских приложений — это негибкость конфигурирования клиента.

Давайте возьмем противоположную позицию — бэкенд все знает про клиент и отвечает за всю визуализацию. Он присылает на клиент детальную информацию об экранах, текстах, кнопках — да вообще по-максимуму. Прямо из коробки это дает возможность быстрого изменения клиентов, нивелируя весь лаг деплоя приложения в сторы. Можно проводить А/Б тестирования, даже на лету менять часть какой-то функциональности, если это позволяет ваше АПИ.

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

В сегодняшней заметке ссылка на видео Highload++ 2017 по теме
https://www.youtube.com/watch?v=G-AiDkZLOIs

#practices #backend #client #video #highload #youtube #ru