IT TL;DR – Telegram
IT TL;DR
343 subscribers
3 photos
6 links
Обзоры на статьи, книги, доклады по IT-тематике, а иногда мои собственные мысли
Download Telegram
Channel created
Channel name was changed to «IT TL;DR»
Channel photo updated
#ddia #book #summary

По этим хэштегам можно будет найти заметки, мысли, комментарии по материалу книги Designing Data-Intensive Applications. The Big Ideas Behind Reliable, Scalable, and Maintainable Systems от Мартина Клеппмана (Martin Kleppmann).

Читаю книгу на английском, но заметки будут на русском, иногда обращаю внимание на нюансы, возникающие при переводе.
#ddia #book #summary

Введение

Во введении автор обозначает фокус книги: data-intensive applications, противопоставляя их compute-intensive applications. К первой категории относят приложения, работающие с большим количеством данных, а ко второй - требующие большого объема вычислений. Часто можно встретить похожее противопоставление IO-bound vs CPU-bound системы/операции.

Лингвистика. Интересно, что в русскоязычном издании книги в заглавии data-intensive applications переведены как высоконагруженные приложения, при этом не подчеркивается, что речь пойдет об IO-bound системах, в которых основным вызовом является масштаб данных. А высоконагруженными могут быть и CPU-bound приложения, то есть часть смысла при переводе утеряна уже в заглавии книги.

В последние годы большое развитие получили различные базы данных и распределенные системы. Это произошло в том числе благодаря набору факторов:

- Бигтех компании (Google, Amazon, etc.) растят бизнес, объемы данных и сетевого трафика; нужны технологии, которые могут справиться с подобным ростом.

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

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

В связи с популяризацией распределенных систем постоянно возникают новые технологии и баззворды, уследить за которыми сложно. Хорошая новость в том, что базовые принципы, лежащие в основе всех этих инструментов, не меняются, и если в этих принципах разобраться, то вы без проблем сможете выбирать правильный инструмент под конкретную задачу. В книге в том числе будет разбираться внутреннее устройство подобных инструментов, в том числе алгоритмы их работы и компромиссы, которые заложены в их устройстве.
👍1
#ddia #book #summary

Часть 1. Foundations of Data Systems

Лингвистика. Во введении покритиковал вариант перевода высоконагруженные приложения для data-intensive applications, но не предложил альтернативу. Если обратиться к словарю, то увидим, что постфикс -intensive часто переводится как -ёмкий, например, labour-intensive – трудоёмкий, resource-intensive – ресурсоёмкий (затратный по ресурсам). Поэтому далее буду использовать вариант датаемкие приложения для передачи английского data-intensive applications.

Глава 1. Reliable, Scalable, and Maintainable Applications

Датаемкие приложения, как правило, собирают из типовых "кирпичиков", способных решать типовые задачи: баз данных, кэшей, поисковых индексов, систем потоковой и пакетной (батч) обработки. При этом важно выбирать подходящие кирпичики под конкретную задачу, а также следить за тем, чтобы эти кирпичики хорошо стыковались между собой. Кроме того, создатели приложений стремятся к тому, чтобы построенная система была надежной (reliable), масштабируемой (scalable) и удобно поддерживаемой (maintainable).

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

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

Удобство поддержки - команды разработки и эксплуатации способны эффективно заниматься обслуживанием системы: поддержанием ее текущего поведения, а также ее развитием для новых сценариев использования.
👍21👏1
#ddia #book #summary

Reliability

Надежность - интуитивно понятное и ожидаемое свойство системы. Простыми словами это способность системы продолжать корректно работать, даже если что-то идет не так. Более строго, моменты, когда что-то идет не так, называют сбоями, а систему, которая способна предвидеть и/или нивелировать эффект от сбоев, называют устойчивой к сбоям (англ. fault-tolerant или resilient).

Лингвистика. Автор призывает разделять сбои (англ. faults) и отказы (англ. failures). Первое - отклонение поведения какого-то компонента системы от рабочих характеристик, например, деградация времени чтения с диска, второе - состояние системы, в котором она перестает выполнять свои функции. В словаре можно увидеть вариант перевода resilient как "отказоустойчивый", хотя на самом деле система должна быть устойчивой к сбоям, а именно, чтобы сбои не приводили к отказам.

На первый взгляд может показаться парадоксальным, но нередко в систему искусственно добавляют различные сбои, чтобы проверить, устойчива ли к ним система. На практике именно логика обработки ошибок содержит больше ошибок, чем happy path сценарии, т.к. ошибки возникают довольно редко (иногда их называют "исключительные ситуации"), поэтому и логика их обработки "тестируется" в продакшене тоже редко. Чтобы компенсировать такой дисбаланс, некоторые компании берут на вооружение подход chaos-engineering (можно встретить перевод хаос-тестирование). Подход популяризирован компанией Netflix, которая выложила в open-source свой инструмент для хаос-тестирования - Chaos Monkey.

Аппаратные сбои

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

Важно не ввести себя в заблуждение, что железо ломается редко, а, значит, можно не тратить время на попытки устранить этот риск. Дело в том, что риск отказа железа проявляет себя на большом масштабе: если принять среднее время службы диска за 30 лет, то в датацентре с 10 000 дисками в среднем каждый день выходит из строя один диск (уже не звучит, как что-то невероятное?).

Очевидный способ сделать систему устойчивой к аппаратным сбоям - резервирование. Диски можно объединять в RAID-массивы, использовать источники бесперебойного питания для отдельных серверов, а на уровне датацентров держать наготове дизельные генераторы на случай отключения питания. Этот подход неплохо работал, пока системы были небольшими и могли работать на одном сервере, который за счет резервирования мог работать бесперебойно годами.

Однако с появлением датаемких систем, которые работают на большом числе серверов, как и в примере с дисками, начинает играть эффект масштаба: с ростом числа серверов/узлов пропорционально растет частота возникновения аппаратных сбоев. Таким образом, возникает необходимость создавать системы, которые устойчивы к частым отказам целых узлов. Плюсом таких систем является возможность накатывать обновления, требующие полной перезагрузки (например, обновления безопасности): с помощью плавного обновления (англ. rolling upgrade) можно избежать даунтайма всей системы.
👏1
#ddia #book #summary

Reliability (продолжение)

Программные ошибки

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

Универсальной защиты от подобных сбоев не существует, есть множество подходов по предотвращению и устранению последствий, но это отдельная сложная тема.

Человеческий фактор

Люди проектируют, строят и, наконец, эксплуатируют системы. При этом люди могут ошибаться (даже если увести за скобки злой умысел). В контексте надежности люди очень ненадежны: при внесении изменений в конфигурацию системы можно допустить ошибку, которая приведет к поломке системы. К счастью, есть ряд подходов, позволяющих нивелировать человеческий фактор:
- проектировать интерфейсы, админки, API таким образом, чтобы минимизировать возможность ошибиться: проверять различные инварианты, которые не позволят сделать конфигурацию ошибочной
- создавать изолированные окружения, в которых можно получить навык управления системой без риска поломки продакшена (тестовое окружение, песочница)
- предусматривать возможность быстрого отката изменений на случай, если в конфигурации будет допущена ошибка
- давать возможность раскатывать изменения плавно, чтобы не влиять сразу на 100% пользователей системы

Нужно ли заниматься надежностью?

Надежность это не только про атомные электростанции и самолеты. В любом сервисе пользователи ожидают, что система будет доступна 24/7, данные не потеряются и т.д. Иногда надежность задвигается на второй план ради скорости разработки новых фич или достижения бизнес-результата, но долгосрочно терять надежность из фокуса точно не стоит.
👏1
#ddia #book #summary

Scalability

Масштабируемость - это заложенная в системе возможность продолжать [надежно] работать при росте нагрузки. Нагрузка может измеряться в числе пользователей, в частоте запросов, в объеме данных, которые система обрабатывает. Как правило, мы ожидаем, что при росте нагрузки достаточно добавить определенного вида серверных ресурсов, при этом не делая изменений в архитектуре системы.

Прежде всего, важно уметь описывать нагрузку на систему, как качественно (например, какие типы запросов могут приходить в систему), так и количественно (сколько всего запросов в секунду, какое соотношение читающей и пишущей нагрузки, сколько пользователей единовременно находится в системе). Здесь приводится небезызвестный пример Твиттера. В сервисе есть два типа основных действий: написать пост (пишущая нагрузка, в среднем 5К RPS, данные за 2012) и загрузить ленту (посты пользователей, на которых подписан, читающая нагрузка, 300K RPS).

Один из подходов к хранению данных в такой системе - складывать данные в нормализованном виде в классическую реляционную БД. Тогда запрос ленты это JOIN нескольких таблиц (посты, пользователи, подписки). Этот подход использовался в Твиттере изначально, но с ростом нагрузки (с ростом числа пользователей растет и объем данных, и нагрузка в RPS) такая схема плохо масштабировалась, было тяжело поддерживать работу тяжелого JOIN-запроса по вычислению ленты конкретного пользователя.

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

Подход с предрасчетом ленты работает хорошо для среднего пользователя, у которого десятки подписчиков. Однако есть аккаунты знаменитостей, на которых подписаны десятки миллионов пользователей. Для постов знаменитостей подход с предрасчетом ленты уже работает не так хорошо, потому что каждый твит вызывает огромное число записей, нагружая систему. В итоге Твиттер перешел на гибридную схему: для большинства пользователей их посты записываются в предрассчитанную ленту, а посты знаменитостей добавляются в ленту на лету, как в изначальном подходе с нормализованными данными в реляционной БД.
👍1
#ddia #book #summary

Scalability (продолжение)

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

Лингвистика. Автор обращает внимание, что хотя и термины response time (время ответа) и latency (задержка) часто используют как синонимы, они обозначают разные вещи. Время ответа замеряется на стороне клиента: помимо времени обработки запроса на сервере (англ. service time) оно включает в себя время передачи данных по сети, а также время нахождения запроса в различных очередях. Задержка это время, в течение которого запрос ожидает обработки, находится в latent-состоянии.

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

Арифметическое среднее плохо подходит для описания распределений, т.к. чувствительно к выбросам (очень большое единичное значение может сильно повлиять на среднее, хотя это единичный случай). Поэтому чаще всего используют персентили - p95, p99, p999. Например, если для некоторой системы p99 времени ответа 200мс, это значит, что на 99% запросов система отвечает не дольше, чем 200мс.

Может показаться нецелесообразным следить за высокими персентилями времени ответа, также известными как tail latencies, от англ. tail - хвост [распределения]. Казалось бы, оптимизировать персентиль p999 избыточно, т.к. это влияет только на 1 запрос (или пользователя) из 1000. Однако часто бывает что эти самые 1 из 1000 запросов приходят от пользователей с наибольшим количеством данных в сервисе (потому они и самые медленные), а, значит, это могут быть важные 0.1% пользователей с точки зрения бизнеса. При этом оптимизация p9999 времени ответа уже выглядит как невероятно дорогая оптимизация, не приносящая достаточно выгоды (данные от компании Amazon).

На высокие персентили времени ответа часто влияет долгое время нахождения запросов в очереди, связанное с эффектом блокировки начала очереди (англ. head-of-line blocking): один или несколько тяжелых запросов могут занять все ресурсы сервера (потоки, соединения к БД) на некоторое время, при этом остальные запросы, которые сами по себе можно обработать довольно быстро, вынуждены ждать в очереди, пока у сервера появятся свободные ресурсы. В микросервисной архитектуре высокие персентили времени ответа могут расти из-за одного медленного сервиса-зависимости: даже если делать запросы в N сервисов параллельно, время ответа будет не быстрее, чем время ответа самого медленного из N сервисов.

В контексте масштабирования часто всплывает дихотомия: вертикальное (увеличение мощности конкретного сервера) или горизонтальное (распределение нагрузки на бОльшее количество серверов) масштабирование. В реальности часто используется комбинация подходов, при этом для stateless систем характерно масштабироваться горизонтально, а для stateful - вертикально. При этом, конечно, у вертикального масштабирования есть вполне осязаемый предел, и в итоге нагруженные stateful системы сталкиваются с необходимостью становиться распределенными.
👍21
#ddia #book #summary

Maintainability

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

Чтобы упростить жизнь всем, кто занимается поддержкой, при проектировании системы важно придерживаться трех принципов: удобство эксплуатации (англ. operability), простота (англ. simplicity) и удобство развития (англ. evolvability). Последний принцип также известен как расширяемость (англ. extensibility), модифицируемость (англ. modifiability) или гибкость (англ. plasticity).

Удобство эксплуатации

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

Для того, чтобы команда эксплуатации могла эффективно справляться с перечисленным задачами, система должна обладать следующими свойствами:
- быть хорошо наблюдаемой: иметь удобные метрики работы системы и инструмент для их просмотра в реальном времени
- автоматизировать типовые операции вроде релизов, откатов, рестартов, профилировки, расширения серверных ресурсов
- не иметь критичных компонентов без резервирования, то есть не должно быть конкретных серверов/узлов, обслуживание которых приводит к даунтайму системы
- поддерживать актуальными ранбуки (англ. runbook) - подробные и понятные инструкции, что нужно делать в ситуации инцидента, в которых не нужно тратить время на обдумывание действий, а просто следовать инструкциям в стиле "если произошло X, сделай Y"
- иметь хорошее поведение по умолчанию (например, настройки таймаутов/ретраев сетевых запросов), но при этом давать гибкость в настройке для специфичных сценариев
- обладать возможностью автоматического восстановления при возникновении поломок, при этом представляя возможность ручкой починки команде эксплуатации
- иметь максимально предсказуемое поведение
#ddia #book #summary

Maintainability (продолжение)

Простота (управление сложностью)

Кодовая база многих небольших проектов простая и выразительная, но по мере того, как проект растет, код часто становится сложным для понимания. Это, в свою очередь, усложняет и замедляет поддержку подобных проектов, которые иногда называют "большой ком грязи" (англ. big ball of mud). В таких проектах внесение изменений нередко может занимать непредсказуемо долгое время (что рушит планы и бюджеты), а также существует высокая вероятность внести баг в существующую функциональность при добавлении новой фичи. Типичные симптомы подобных проектов: взрыв пространства состояний (вследствие экспоненциального роста), сильная связность модулей, запутанные зависимости, неконсистентные именования и терминология и многое другое.

Для упрощения поддержки хочется двигаться в обратном направлении и, наоборот, снижать сложность имеющейся системы, но возможно ли это делать без выкидывания части функционала? Оказывается, что во многих случаях возникающая сложность является непреднамеренной (англ. accidental complexity). Это значит, что сложность происходит не из предметной области задачи, которую решает построенная система, а является результатом конкретной реализации.

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

Удобство развития

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

Существует популярная методология Agile (англ. гибкий), которая дает ответы на вопросы, как адаптироваться к изменениям при разработке систем. В рамках этой методологии существуют разные подходы и фреймворки, например, рефакторинг и TDD (англ. Test-Driven Development). Однако Agile по большей части сосредоточен на изменениях в масштабах нескольких файлов с исходным кодом внутри одного приложения, при этом не давая ответа на вопрос, как похожие изменения проводить в большой распределенной системе, состоящей из разных сервисов/приложений с разными характеристиками (например, бэкенд и база данных). Автор обещает дать ответы на подобные вопросы в рамках данной книги.
👍2