Стенограмма доклада Алексея Лесовского за 2020 год «Примеры масштабирования PostgreSQL».
Сегодня никого не удивляет, что инфраструктура живет в «облаке», однако не все компоненты переходят в «облако» легко и просто.
Одним из таких компонентов является «база данных», которая всегда требовательна к ресурсам и производительности.
Особенно остро стоит вопрос масштабируемости и отказоустойчивости, поэтому в последние годы можно наблюдать бурное развитие альтернативных СУБД.
Однако благодаря накопленным возможностям классические СУБД зачастую остаются выбором №1, несмотря на то, что они также не стоят на месте и предоставляют богатый набор инструментов с точки зрения масштабирования.
В этом докладе я рассмотрю в основном PostgreSQL, варианты его масштабирования и когда это нужно делать и как это делать правильно и как делать неправильно.
В докладе будут затронуты следующие темы:
- Потоковая репликация и совместное использование рабочей нагрузки чтения/записи.
- Логическая репликация и сегментирование данных
- Обеспечение высокой доступности и устойчивости к сбоям
Добрый вечер всем! Я не ожидал, что будет так много людей.
Очень приятно вас всех видеть! Мой доклад будет о масштабировании.
Расскажу немного о себе.
Я работаю администратором базы данных PostgreSQL (администратором базы данных) в Data Egret. А наша компания занимается консалтингом и обслуживанием PostgreSQL. Раньше я был системным администратором.
А дух системного администрирования до сих пор живет во мне.
Мне нравится это делать.
Пару лет назад на одной из конференций я говорил о Топ ошибок разработчиков при работе с PostgreSQL .
И был этот слайд. По сути, оно показало, какие возможности предоставляет PostgreSQL в плане масштабирования, когда ресурсов и мощности одного узла недостаточно.
В этом отчете я сказал, что эта фотография достойна целого отчета, и пропустил содержание этого слайда.
И этой конференцией я решил восполнить этот пробел.
Я решил сделать этот снимок и подробнее рассказать о том, какие возможности есть у PostgreSQL, чтобы использовать больше мощности, больше ресурсов и как можно масштабировать PostgreSQL.
Соответственно, родился этот отчет Scaling Usecaces. И в этом отчете я постараюсь обобщить весь тот опыт, который у нас есть в плане решений по масштабированию, какие варианты масштабирования мы используем в своей практике с нашими клиентами.
И в какой-то степени этот доклад, вероятно, не будет содержать каких-либо революционных открытий.
Я думаю, что по большей части все, что я скажу, уже где-то слышали или читали.
И этот отчет похож на попытку рассортировать информацию по разделам.
И я старался не использовать в этом отчете сложные диаграммы и формулы.
И поэтому этот отчет носит более общий характер, не раскрывая никаких новых технологий, новых технологических решений, после которых вам захочется что-то запустить и реализовать.
Если говорить конкретно о том, о чем мы будем говорить, то по большей части я буду говорить о таких технологиях, как потоковая репликация.
Расскажу про декларативное разбиение, которое появилось недавно, в 10-м релизе, потому что это такой переходный кирпичик для построения каких-то сложных схем.
И я затрону вопросы так называемого встроенного шардинга, потому что этот вопрос нам задают достаточно часто: и мне, и моим коллегам.
Люди спрашивают, как можно горизонтально масштабировать PostgreSQL. И на этот достаточно сложный вопрос, на мой взгляд, я постараюсь ответить в этом докладе.
Я, пожалуй, не буду касаться вопросов, как планировать схему и как ее рефакторить для будущего шардинга.
Потому что, на мой взгляд, универсальных решений здесь не существует. И все подходы к планированию и преломлению схемы зависят от того, как схема реализована, какие типы данных там, какие таблицы, вообще, как данные в этой схеме размещены.
Я не буду рассказывать вам различные истории успеха о том, как мы конвертировали десятки терабайт в шарды.
И я не буду рассказывать вам истории из собственного опыта.
Я не буду вам рассказывать, как пользоваться Greenplum, Timescale, Citus и т. д. Потому что это можно найти в Интернете.
Это можно взять из документации и получить представление о том, как этим пользоваться.
Как правило, мысли о масштабировании приходят тогда, когда у нас острая нехватка ресурсов.
Это могут быть ядра процессора, память или нехватка места на диске.
В общем, все те системные ресурсы, которые нам предоставляют компьютеры или облака.
Или мы можем подумать о масштабировании, когда руководство или бизнес ставит перед нами задачу масштабирования на несколько дата-центров.
Нам нужно настроить базу данных таким образом, чтобы она жила в нескольких дата-центрах и могла быстро переключаться в случае сбоя дата-центра.
Но бывают случаи, когда о масштабировании думать не нужно.
И я добавил этот слайд совсем недавно.
Молодой человек написал одному из моих коллег, сидящему на заднем ряду.
А письмо имело примерно следующее содержание.
Он говорит, что у нас есть стартап, мы режем MVP, и я решил заранее подумать, как мне сделать шардинг в нашей базе, чтобы потом у меня потенциально было меньше проблем, когда этот шардинг действительно понадобится.
Это было довольно интересное письмо.
И мы ему сказали, что на этапе проектирования MVP шардинг не нужен.
Когда проблемы действительно возникают, их следует решать.
Следовательно, вам не нужно масштабирование, когда все ваши данные помещаются в оперативную память.
Когда данных мало и их легко эксплуатировать, управлять, передавать, а когда какой-то проект находится на стадии взлета, на стадии проектирования, о масштабировании думать не стоит. Потому что все это приводит к дополнительному времени, которое нужно тратить на разработку и сопровождение, плюс увеличивается сложность этого проекта.
Если вы там пишете ansible роли или автоматизацию, то вам нужно включить в эти рецепты информацию о том, что ваша база данных уже настолько масштабирована.
А еще это время, потраченное на написание рецептов, их поддержание и тестирование.
А плюс — преждевременная оптимизация, которую многие критикуют. Это спорный вопрос.
Некоторые люди стараются заранее все спланировать на будущее, т. е.
оптимизировать под какие-то сценарии использования, которые возникнут в будущем, а другие считают, что это преждевременная оптимизация и в ней нет необходимости.
Поэтому это спорный вопрос.
Но, я считаю, что преждевременная оптимизация не очень нужна, потому что в итоге она может оказаться бесполезной, да и время на это уже потрачено.
Когда необходимо масштабирование?
- Когда мы исчерпали ресурс оптимизации запросов.
Вы получаете отчеты о работе вашей базы данных и уже оптимизировали все запросы.
Вы построили необходимое количество индексов.
Переписали неоптимальные запросы.
И действительно, оптимизировать больше нечего, все запросы оптимальны.
Но ресурсов для их обработки недостаточно.
- Когда ресурс схемы данных исчерпан.
У вас есть столы, вы выбросили из них все ненужное.
Все файлы JSON, находящиеся в полях jsonb, оптимизированы.
Вытащили в отдельные столбцы, или ненужные дополнительные поля в этих JSONs вытащили и выкинули, вы не храните мусор в базе данных.
То есть схема уже строго оптимизирована и хранится только действительно необходимая информация.
- Ресурсы инфраструктуры исчерпаны.
Например, вам не хватает ядер, загрузка вашего процессора ниже 100. Или вы исчерпали возможности, которые предоставляет программное обеспечение.
Например, у вас на каждой базе установлен PgBouncer, который оптимизирует количество подключений, но этого все равно недостаточно.
Если говорить о масштабировании, то самый простой и распространенный способ — использовать потоковую репликацию.
Потоковая репликация — это когда у нас есть мастер.
Мастер передает журналы транзакций на реплики.
Это просто.
А сама потоковая репликация удобна тем, что узлы, эти реплики, они могут выступать в качестве узлов горячего резерва.
Мы можем переключиться на них, если мастер станет недоступен.
И мы можем отправлять на них трафик только для чтения, т. е.
это уже возможность для масштабирования.
Если наш мастер не справляется с нагрузкой, мы можем часть read-only трафика отправить на реплики.
И этот вариант часто недооценивается и зачастую не используется.
У многих клиентов, когда мы администрируем базы данных, мы видим, что мастер достаточно сильно загружен, и в то же время, если посмотреть на реплики, их утилизация отсутствует. Они не используются для запросов.
И, на мой взгляд, это очень большое упущение, потому что эти ресурсы простаивают на тот момент, когда умирает мастер и можно переключиться на реплику.
И это очень неоптимальный подход. Поэтому, когда мы говорим о масштабировании, начинать нужно с реплик.
Но есть проблемы.
Когда мы используем потоковую репликацию, всегда могут возникнуть проблемы, связанные с задержкой репликации.
Запросы, которые мы отправляем мастеру, реплике, могут возвращать разные результаты.
Соответственно, для решения этой проблемы мы должны перейти на уровень приложения.
И на стороне приложения мы можем точно представлять, какие данные мы хотим получить от мастера, а какие от реплики.
Здесь у нас уже задействованы разработчики приложений.
И с ними нужно работать.
Другая проблема более сложна и сложна.
Это балансировка трафика, потому что здесь нужно задействовать не только разработчиков, но и отдел инфраструктуры: это админы, это люди, которые поддерживают нашу облачную инфраструктуру.
Нам нужен какой-то уровень балансировки трафика от приложения к базе данных, чтобы сбалансировать этот трафик, потому что в итоге мы получаем два потока.
Один поток на запись, который необходимо отправить мастеру.
И большой поток чтения, который нужно отправить на реплику или на реплики, если их несколько.
Существует два подхода к балансировке.
Я условно назвал их инфраструктурным подходом и прикладным подходом.
Инфраструктурный подход — это когда мы говорим нашим администраторам, что нам нужна балансировка.
И с помощью каких-то инструментов нам уже реализуют балансирующий слой между приложением и базой данных.
Есть разные варианты.
Если в инфраструктуре используются какие-то системы DCS: Consul или Etcd, мы можем построить балансировку с помощью DNS и этих систем.
Можно использовать более распространенный метод — это использование отдельных балансировщиков типа Haproxy и дополнительных вспомогательных вещей вроде Confd, Consul-Template, определяющих конфигурацию Haproxy в зависимости от изменения топологии кластера postgres. Допустим, наша главная роль перешла к другому хосту.
Балансировщику необходимо как-то исправить в себе эти изменения, чтобы корректно перенаправлять трафик на нужные узлы.
Для этого используются дополнительные инструменты, о которых я упоминал ранее.
И нам всегда нужна отказоустойчивость на уровне сети, потому что у нас есть какой-то IP-адрес, какое-то DNS-имя, которое прописано, например, в конфигах приложения.
И приложение уже идет на это имя, идет в базу данных через уровень балансировки.
Соответственно, если наш IP-адрес принадлежит какому-то узлу-балансировщику, и этот узел пропал, то нам нужно сохранить этот адрес доступным.
Вам необходимо передать этот адрес на другой узел.
И делаем это с помощью Keepalived или VRRP, которые реализуют это на аппаратном или программном уровне.
Прикладная или прикладная балансировка.
Здесь все козыри отданы разработчикам приложений.
Потому что здесь приложение решает, как идти, на какие узлы и с каких узлов брать данные.
Например, могут быть некоторые важные данные, которые всегда нужны свежие.
И мы берем их у мастера.
Самый простой пример — баланс пользователя.
мы всегда берем их у мастера, так как не хотим, чтобы задержка репликации мешала получению свежих данных.
А некоторые мелочи мы можем взять из реплик.
И эта балансировка осуществляется уже на уровне приложений.
Но и здесь есть несколько вызовов, несколько острых моментов.
В нашем кластере, особенно если мы используем какой-то автоотказ, роль мастера переменчива.
Допустим, произошел какой-то сбой.
И роль мастера перешла на другой сервер.
Наше приложение должно быть к этому готово и не должно паниковать.
Соединение должно быть восстановлено.
Если это была какая-то транзакция, то она должна повторить транзакцию.
Если это был запрос, то он должен повторить этот запрос.
И в то же время должен быть какой-то предохранитель, чтобы сразу после аварии в нашу базу не хлынул большой поток накопившихся запросов, т. е.
базу нельзя было заспамить.
С одной стороны, на словах все выглядит просто, но в приложении реализовать довольно сложно.
Хотя современные тенденции и тенденции развития приложений диктуют такие условия.
Есть образец двенадцатифакторное применение .
И среди этих факторов как раз указаны такие подходы, что приложение должно реализовать эти режимы работы.
( Примечание: вы также можете упомянуть Приложение 19 Факторов )
Я, наверное, даже слишком глубоко копнул в балансировке.
Вернемся к масштабированию.
Где и как мы можем использовать потоковую репликацию в качестве варианта масштабирования? У многих клиентов всегда есть горячий резерв, но он практически не используется.
Когда речь идет о распределении ресурсов, мы говорим: давайте часть трафика перенесем на реплики и будет лучше.
У нас есть некоторый трафик чтения, плюс мы можем обрабатывать аналитические запросы.
И теперь мы можем разгрузить мастер от нагрузки и масштабировать существующую нагрузку, исходящую от приложения.
Плюс мы можем дополнительно использовать эти реплики для резервного копирования.
С них берутся резервные копии и где-то хранятся.
Это снимает с мастера еще одну дополнительную нагрузку.
Это такой маленький бонус от наличия реплик.
Но есть недостатки.
Самым основным из них является задержка репликации.
Когда мы используем потоковую репликацию, нам необходимо отслеживать задержку репликации и следить за тем, чтобы она была как можно меньшей.
И у нас есть несколько причин, которые вызывают эту задержку репликации.
Это могут быть жесткие запросы.
Это могли быть какие-то пылесосы, особенно если их запускалось сразу несколько.
Это могут быть какие-то массовые операции по изменению данных: массовое удаление, обновление.
И существуют разные варианты того, как смягчить воздействие и эффект этих операций.
И применять эти операции нужно правильно.
Если мы делаем какие-либо массовые обновления данных, то нам нужно делать их небольшими партиями.
Если мы работаем с вакуумом, то мы оптимизировать конфигурацию пылесосов , когда они могут много работать, не оказывая негативного влияния на производительность диска.
Если мы используем некоторые запросы к реплике, мы можем столкнуться с отменой длинных запросов.
И нам нужно быть к этому готовыми.
Это так называемые конфликты восстановления, когда выполнение запроса конфликтует с восстановлением журнала транзакций.
Это также довольно распространенная проблема.
А еще лечим это изменением файла конфигурации PostgreSQL. Плюс есть такой эффект как раздуваться Когда у нас есть таблицы, индексы раздуваются из-за длительных запросов.
Это особенно актуально, когда включена опция hot_standby_feedback. Он позволяет задержать наступление вакуума и очистку мертвых линий на мастере, тем самым увеличивая раздутость.
То есть использование потоковой репликации для масштабирования имеет как плюсы, так и минусы.
Именно так мы масштабируем PostgreSQL, но с другой стороны, есть некоторые подводные камни, о которых нужно помнить.
То есть у нас нет какого-то хорошего со всех сторон решения.
Так каков результат? В результате у нас есть мастер записи и масштабирование записи по-прежнему ограничено мощностью одного узла.
Чуть дальше расскажу, какие есть варианты решения.
И мы можем масштабировать чтение, увеличивая количество реплик.
Давайте подойдем с другой стороны, допустим, у нас достаточно ресурсов процессора, памяти и т. д. Но не хватает места на диске, простыми словами не хватает места для хранения данных.
Но нам нужны все данные вместе; мы не можем передать часть данных в какие-то отдельные сервисы и хранить их там.
С 10-й версией PostgreSQL, которая вышла, если мне не изменяет память, в 2018 году (Уточнение: в 2017 году), появилось декларативное секционирование.
Раньше разбиение делалось по триггерам и это было не очень удобно.
Нужно было прикрепить триггеры, написать функцию для маршрутизации, а это было неудобно.
Теперь все проще.
Теперь есть декларативное секционирование.
Он устанавливается довольно быстро.
А существующее секционирование, основанное на триггерах, можно легко перевести в декларативное.
То есть это очень удобный и хороший инструмент в PostgreSQL. Но это не имеет прямого отношения к масштабированию.
Поэтому я не буду на этом долго останавливаться.
Есть хороший вариант использования декларативного секционирования — это для каких-то архивных данных, которые записываются, а потом периодически читаются.
То есть мы их никак не меняем.
У нас есть оперативные данные, которые мы условно назовем «горячей головой», и есть холодный архив — «холодный хвост», который мы читаем очень редко.
А используя такое декларативное разделение, управлять местом становится проще.
Наши данные разделены на разделы, и мы уже управляем этими разделами.
Если мы можем разбить данные на разделы, то мы можем как-то отдельно эти данные заархивировать, отдельно скачать и положить в другое хранилище.
Кроме того, эти отдельные сохраненные разделы, которые у нас уже есть в качестве копий, мы можем использовать Обертки сторонних данных подключить PostgreSQL и брать оттуда данные при необходимости, если надо.
Например, существует файл-оболочка внешних данных, который позволяет подключать файлы CSV в виде таблиц к базе данных.
Есть неофициальный S3 Обертки внешних данных , который позволяет читать файлы CSV из S3, а также соединять их в виде таблиц.
То есть всегда можно придумать такую схему, когда нашим разработчикам вдруг понадобятся данные и нам нужно будет их легко подключить.
Мы должны рассмотреть эту возможность, потому что она возникает.
И как мы его используем? У одного из наших клиентов мы эксплуатируем систему, в которой имеется около 7,5 ТБ операционных данных, включая таблицы и индексы.
То есть данных довольно много.
Они размещаются в нескольких табличных пространствах.
И управлять ими не всегда легко, особенно когда в одном из табличных пространств не хватает места.
Но со временем мы решили эту проблему.
Мы объединили все табличные пространства в одно, проблема исчезла.
Весь холодный архив хранится в S3. Мы периодически, раз в месяц, скачиваем старые ненужные разделы, сжимаем их и помещаем в S3. А архив занимает всего 1 ТБ.
Это без индексов, просто сжатый простой текст. И данные за 2015 год. В 2015 году мы начали потихоньку добавлять некоторые таблицы.
Со временем их число увеличивалось, становясь все более многочисленными.
Я даже не скажу сразу, сколько их.
Реализуется это все достаточно просто.
Мы ничего не придумали.
Мы взяли простые скрипты оболочки, поместили их в cron и запускаем эти скрипты по расписанию, которые упаковывают данные, архивируют их, отправляют на S3, и они там лежат. То есть это достаточно простое фронтальное решение, но оно работает. Плюс эти же скрипты еще и удаляют старые данные.
То есть есть простой механизм, который захватывает старый раздел и удаляет его.
Основная проблема — когда к нам приходят разработчики и говорят: «Дайте нам данные двухлетней или годичной давности».
Учитывая, что вся эта история продолжается с 2015 года, она реализована в виде SQL-бэкапов, обычного текста, т. н.
Сейчас мы уже думаем, стоит ли использовать собственный формат дампа.
И напрямую с S3 подключить эти разделы нет возможности.
Мы не можем использовать какие-либо механизмы обёрток иностранных данных, поэтому приходится всё это скачивать вручную, распаковывать, создавать ddl, загружать туда и потом подключать к основной таблице.
Это довольно рутинный процесс.
И это довольно неудобно.
Этот вопрос на самом деле ближе к шардингу.
Мы можем взять декларативное разбиение и перенести эти разделы на другие хосты, на другие сервера с помощью обёрток Foreign data, подключить их по протоколу FDW. И они будут видны на главном узле.
Об этом методе я расскажу дальше, но, с моей точки зрения, этот метод не очень хорош.
И у него много недостатков.
Поэтому, если мы застряли в производительности, самый простой и легкий вариант — масштабирование.
Мы просто увеличиваем железо, максимально увеличиваем количество ядер, объем памяти.
Если это какой-то облачный провайдер, берем более мощный (и более дорогой) экземпляр.
Если это какое-то арендованное оборудование, мы арендуем еще более мощное оборудование.
Как правило, этот вариант реализовать гораздо проще, хотя и дороже, вместо того, чтобы строить какие-то распределенные системы на PostgreSQL.
Это путь для тех, кто хочет приключений и некоторых испытаний.
Можно сделать так называемый шардинг.
Вы можете разместить несколько несвязанных между собой PostgreSQL на отдельных серверах.
Представьте один из этих узлов как мастер узла.
На этом главном узле создайте декларативную секционированную таблицу и разместите все разделы на отдельных узлах.
И через механизм оберток внешних данных включить эти таблицы в главный узел.
Таким образом, приложение, работающее с мастер-узлом, будет работать и с нашими листовыми узлами и брать данные оттуда.
Но на самом деле приложение будет работать на одном узле.
На мой взгляд, по-другому сделать нельзя.
В самом PostgreSQL нет лучших, более дешевых или простых функций.
И это единственный вариант. И есть проблемы, т. е.
это не самая идеальная схема, но других вариантов толком нет. Потому что нет инструментов, позволяющих реализовать какие-либо другие схемы.
Мы можем использовать только определенные продукты, созданные на базе PostgreSQL. Например, Greenplum или PostgreS-XL. Они, в свою очередь, также несут свои проблемы и издержки.
Но при этом они дают пользователю дополнительный функционал в плане горизонтального масштабирования.
Но у всех этих решений одни и те же болезни.
Например, затраты на обеспечение высокой доступности.
Все конечные узлы, на которых хранятся данные, должны иметь резервные копии.
Потому что при выходе из строя узла мы теряем данные, то нам нужна реплика, чтобы мы вообще не потеряли эти данные.
Не самое лучшее выступление.
Возьмем Postgres-XL/XC, CitusDB, они используют двухфазную фиксацию.
И это не быстро.
И если наша сеть еще несколько тормозит, то при обмене данными нам нужно договориться между всеми участниками кворума.
Это медленно.
Если мы возьмем вариант с декларативным секционированием и обертками Foreign data, то двухфазного коммита не будет. И мы можем напрямую записывать данные в конечные узлы, но тогда нам необходимо убедиться, что данные согласованы на уровне приложения.
Это также проблема, над которой вам нужно очень тщательно подумать, прежде чем реализовывать схему.
И плюс производительность.
Я когда-то делал бенчмарк, брал достаточно простые ноды с 2 ядрами и 2 ГБ памяти.
Я сравнил, как работает эта схема с декларативным секционированием и обертками внешних данных.
Я взял небольшой набор данных, который помещается в память, чтобы исключить чтение с диска, чтобы чтение с диска не учитывалось в производительности запроса.
И я попробовал посмотреть, как это работает. Таблица простая.
Он состоит из трех полей: временная метка и 2 целых числа.
И все это разбито по меткам времени.
Запросы также довольно просты.
Первый запрос — это выбор первичного ключа.
Базовый вариант — это так, как если бы все данные находились на одном хосте.
Второй случай «шардинга» (точно в кавычках, язык не решается назвать его шардингом) — это как если бы данные располагались на листовых узлах, а мы работаем только с мастер-узлом.
Разница во времени выполнения запроса составляет примерно 6 раз.
Но цифры, конечно, миллисекунды.
То есть здесь это 0,5 миллисекунды, а второй результат — 2,9 миллисекунды.
С точки зрения малонагруженного приложения это может быть приемлемо.
Но иногда это может сыграть очень большую роль.
Возьмем более сложный запрос.
И давайте возьмем несколько строк.
Этот запрос принимает термин 1000. Время уже увеличилось с 2 миллисекунд до 26, т. е.
разница примерно в 9 раз.
Теперь давайте посчитаем агрегат, используя выбранные данные.
Время примерно то же самое, мало что изменилось.
Было 2,24 и 2,26, т.е.
примерно одинаково.
И последний вариант — мы просто делаем обновление на основе первичного ключа.
Разница оказалась примерно в 5 раз больше, чем в первом запросе.
То есть понятно, что это решение с декларативным секционированием и обертками Foreign data не самое быстрое.
Позже мне пришло в голову, что было бы более идеально проводить тест, когда конечные узлы кажутся расположенными на одном узле.
То есть здесь мы упираемся в производительность сети, но было бы интересно убрать сетевую составляющую и посмотреть, как она работает на одном узле, чтобы увидеть накладные расходы самого PostgreSQL при таком решении.
Но эта мысль пришла ко мне слишком поздно.
И, к сожалению, я этого не сделал.
Есть варианты с Postgres-XC/XL, CitusDB. Но, как я уже говорил, они используют двухфазные коммиты и это не быстро.
Плюс там Теги: #Системное администрирование #Администрирование баз данных #postgresql #sql #масштабирование
-
Будьте Осторожны С Adobe Flash Player 10!
19 Oct, 24