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

Рингтон телефона звучал как умирающее сердцебиение моей карьеры на больничном кардиомониторе. В такие моменты сияющий маяк вдохновляющего лидерства действительно имеет значение. Как первые слова моего босса; «Как, черт возьми, это случилось?!».

Что ж, позвольте мне рассказать вам, как:

Немного контекста

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

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

Платформа состояла из микросервисной архитектуры, каталог продуктов представлял собой модель чтения с денормализованной информацией, построенной из потоков событий из нескольких разных доменов, управляемых другими микросервисами. Каталог продуктов поддерживался базой данных ElasticSearch, которая содержала 17 миллионов продуктов, с информацией, начиная от метаданных продуктов, запасов, производственной информации, доступности, цен и т. д., представленных в REST API. Мы использовали ElasticSearch в основном из-за большого количества и разнообразия фильтров (более 50 различных фильтров, некоторые с текстовым поиском).

Очень краткий контекст ElasticSearch

Ни у кого не было прямого доступа для записи к какой-либо базе данных ни на одной технологии (мы использовали несколько технологий в зависимости от варианта использования, от SQL Server до MongoDB и Cassandra). Однако ElasticSearch был исключением, поскольку традиционно им управляли инженерные группы, а не Infra или администраторы баз данных.

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

{конечная_точка_кластера}/{имя_индекса}/{тип}/{идентификатор_документа}

(пример: elastic.com/productIndex/product/152474145)

тип был удален в более новых версиях.

Любая операция выполнялась с помощью HTTP-вызова, который в противном случае выполнялся бы с помощью сценария SQL, в ElasticSearch вы бы выполняли HTTP-запрос. Например, придерживаясь рекомендаций REST, если у вас есть индекс каталога продуктов (индекс в ElasticSearch более или менее эквивалентен таблице SQL) и вы хотите получить конкретный продукт, вы должны сделать GET elastic.com/productIndex/ продукт/152474145. Одна и та же конечная точка будет использоваться для обновления этого продукта с помощью PUT или PATCH, его удаления с помощью DELETE или создания с помощью POST или PUT. То же самое можно применить к различным частям URL-адреса, GET на elastic.com/productIndex/product получит информацию о типе, то же самое для создания, удаления или обновления типа. То же самое происходит с elastic.com/productIndex для получения информации об индексе, обновления, удаления или создания индекса.

Дело

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

За пятнадцать минут, которые у меня были перед следующей встречей, я быстро присоединился к одному из своих старших членов, чтобы быстро получить доступ к живой среде и выполнить запрос. Поскольку прямой доступ к ElasticSearch по сути является REST API, мы обычно использовали Postman для выполнения запросов.

Мой коллега помог через удаленное совместное использование экрана. Практика, которую я обычно делал, заключалась в проверке кода любой операции в реальном времени. Сначала я хотел проверить подключение, чтобы убедиться, что у меня правильный URL-адрес, поэтому я скопировал активную конечную точку и имя индекса (что-то вроде того, что мы обсуждали выше cluster_endpoint/index_name) и отправил запрос GET. Если вы знакомы с интерфейсом Postman, возможно, вы помните, что выбирали действие HTTP из раскрывающегося списка:

К сожалению, к своему ужасу, после отправки запроса я заметил, что было выбрано действие DELETE вместо GET. Вместо того, чтобы получать информацию об индексе, я просто удалил ее.

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

Быстро унесенный сильным ветром рациональности, который понял, что запрос все еще будет продолжаться на стороне сервера (в ElasticSearch), несмотря на то, что он был отменен на клиенте (Postman). Я выполнил обычный поиск без фильтров по индексу, чтобы подтвердить общее количество, запрос, который обычно возвращает 17 миллионов обращений, вернул несколько сотен (сервис потреблял около 70 событий в секунду, эти несколько сотен были продуктами, которые были созданы / отредактированы). тем временем).

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

Где нам повезло

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

У нас было несколько вариантов:

ElasticSearch не имеет возможности развивать схему при критических изменениях. Стратегия в основном переиндексирует всю информацию в новый индекс. Чтобы учесть такие ситуации, у нас был компонент, который создавал каждый продукт с нуля, извлекая данные из всех остальных микросервисов через синхронные REST API. Также было полезно решить любую проблему согласованности из-за ошибки или любого инцидента в вышестоящих службах. Однако для получения всех данных по всем 17 миллионам продуктов потребовалось 6 дней. В любом случае, мы сразу же побежали.

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

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

Что мы узнали

Резервные копии против скорости восстановления

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

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

Горизонтальная масштабируемость была ложью

Одним из наиболее рекламируемых преимуществ микросервисов является возможность горизонтального масштабирования. Часто незаметная деталь заключается в том, что если полагаться исключительно на синхронные API (как показано на рис. 4), горизонтальная масштабируемость быстро становится заблуждением. Компонент, отвечающий за перестроение модели чтения, занял 6 дней, но теоретически мы могли бы сократить время на несколько градусов, масштабируя его по горизонтали. Дело в том, что он полагался на синхронные REST API для получения информации. Он запрашивал данные у всех остальных микросервисов через запросы REST, строил денормализованное представление и сохранял состояние. Его масштабирование вызовет большое количество запросов к другим службам, которые не готовы справиться со значительной дополнительной нагрузкой и сами нуждаются в масштабировании. Это вызовет цепную реакцию, которая в конечном итоге поставит под угрозу всю платформу. Добавьте к этому тот факт, что большинство из них очень зависят от базы данных, и их базы данных также нуждаются в масштабировании.

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

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

Доступ на основе ролей

Одним из, возможно, наиболее очевидных действий, которые мы предприняли, было внедрение управления доступом на основе ролей. Мы использовали более старую версию ElasticSearch, которая допускала только очень простую аутентификацию пользователей, вариант был с использованием XPack, который для этой версии был платным. В более поздних версиях XPack уже включен в бесплатную лицензию.

Мы перешли на более позднюю версию ElasticSearch (версия 7) и реализовали разные роли для чтения и записи. В конце концов, только приложения должны иметь возможность регулярно записывать непосредственно в базу данных, пользователи могут (максимум) читать.

Виноваты процессы, а не люди

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

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

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

Заключительные слова

Я всегда читал о таких проблемах, но всегда был уверен, что они никогда не случатся со мной. «У меня есть процесс», — наивно подумал я, «я не отношусь к таким действиям легкомысленно». Иногда все, что нужно, это мимолетный момент, доля секунды отвлечения внимания, чтобы оставить след непоправимого ущерба. Я был навсегда унижен этим опытом, поучительной историей, которую я иногда рассказываю своим командам, чтобы показать, что иногда их босс тоже совершает худшие ошибки и что в конечном итоге существуют процессы, чтобы защитить нас от самих себя и нашей неумирающей глупости.

Не стесняйтесь проверять мои другие статьи по адресу: https://medium.com/@hugo.oliveira.rocha/