Расшифровка доклада 2020 года Алексея Лесовского "PostgreSQL Scaling Usecases".
На сегодня уже никого не удивить тем что инфраструктура живет в "облаке", однако не все компоненты заезжают в "облако" легко и просто. Одним из таких компонентов является "база данных", которая всегда требовательна в плане ресурсов и производительности. Особенно остро стоит вопрос масштабируемости и устойчивости к сбоям, именно поэтому в последние годы можно наблюдать бурное развитие альтернативных СУБД.
Однако классические РСУБД за счет накопленных фич нередко остаются выбором №1 при том что они также не стоят на месте и предоставляют богатый набор инструментов в плане масштабирования.
В этом докладе я буду рассматривать преимущественно PostgreSQL, варианты его масштабирования и то когда это стоит делать и как это делать правильно и как делать неправильно. В докладе будут рассмотрены следующие темы:
- Потоковая репликация и разделение read/write рабочей нагрузки
- Логическая репликация и шардирование данных
- Обеспечение высокой доступности и устойчивости к сбоям
Видео:
Всем добрый вечер! Я не ожидал, что будет так много людей. Очень приятно вас всех видеть! Мой доклад будет про масштабирование.
Я немножко расскажу про себя. Я работаю PostgreSQL DBA (администратором баз данных) в компании Data Egret. И наша компания занимается консалтингом и обслуживанием PostgreSQL.
Я раньше был системным администратором. И дух системного администрирования до сих пор живет во мне. Я люблю этим заниматься.
Пару лет назад на одной из конференции я рассказывал про топ ошибок, которые допускают разработчики при работе с PostgreSQL. И там был вот этот слайд. Он, по сути показывал то, какие возможности PostgreSQL предоставляет в плане масштабирования, когда не хватает ресурсов и мощностей одного узла. В том докладе я сказал, что эта картинка тянет на целый доклад и пропустил содержание этого слайда.
И к этой конференции я решил восполнить этот пробел. Решил взять эту картинку и подробней рассказать о том, какие возможности есть в PostgreSQL для того, чтобы утилизировать больше мощностей, больше ресурсов и как PostgreSQL можно масштабировать.
Соответственно, родился этот доклад Scaling Usecaces. И в этом докладе я попытаюсь суммировать весь тот опыт, который у нас есть в плане решения масштабирования, какие варианты масштабирования используем мы в своей практике у своих клиентов. И в какой-то мере в этом докладе, наверное, не будет каких-то революционных открытий. Я думаю, что по большей части все те вещи, которые я скажу, уже где-то слышали или читали. И этот доклад, он как попытка какого-то разложения информации по полочкам.
И я постарался не использовать в этом докладе сложных схем, формул. И поэтому этот доклад более обзорный, без раскрытия каких-то новых технологий, новых технологических решений, после которых захочется побежать и что-то реализовывать.
Если говорить конкретно, о чем пойдет речь, то по большей части я буду рассказывать о таких технологиях, как потоковая репликация.
Расскажу про декларативное партиционирование, которое появилось недавно, в 10-ом релизе, потому что это такой переходный кирпич для того, чтобы строить какие-то сложные схемы.
И затрону вопросы, так называемого, встроенного шардинга, потому что этот вопрос довольно часто нам задают: и мне, и моим коллегам. Спрашивают, как можно масштабировать горизонтально PostgreSQL. И на этот довольно сложный вопрос, на мой взгляд, я попытаюсь ответить в этом докладе.
Я, наверное, не буду затрагивать вопросы, как планировать схему и как осуществлять рефакторинг схемы под будущий шардинг. Потому что-то, на мой взгляд, каких-то универсальных решений там нет. И все подходы по планированию и рефракторингу схемы зависят от того, как реализована схема, какие там типы данных, какие таблицы, в общем как данные размещены в этой схеме.
Не буду рассказывать различных историй успеха про то, как мы перепилили десятки терабайт на шарды.
И не буду рассказывать ни о каких историях из собственного опыта. Не буду рассказывать, как использовать Greenplum, Timescale, Citus и т. д. Потому что это можно найти в интернете. Это можно взять из документации и можно составить представление о том, как это использовать.
Как правило, мысли о масштабировании приходят, когда у нас возникает острая нехватка ресурсов. Это могут быть процессорные ядра, память, нехватка места на диске. В общем, все те системные ресурсы, которые нам предоставляют компьютеры или облака.
Либо мы можем подумать о масштабировании, когда нам руководство или бизнес ставит задачу размасштабироваться на несколько дата-центров. Нам нужно базу данных сконфигурировать таким образом, чтобы она у нас жила в нескольких дата-центрах и можно было быстро переключаться в случае выхода из строя дата-центра.
Но есть несколько моментов, когда не нужно задумываться о масштабировании. И этот слайд я добавил буквально недавно. Одному из моих коллег, который сидит на заднем ряду, написал молодой человек. И письмо было примерно следующего содержания. Он говорит, что у нас стартап, мы пилим MVP и я заранее решил подумать о том, как мне сделать шардинг в нашей базе данных, чтобы потом потенциально иметь меньше проблем, когда этот шардинг реально понадобится. Довольно интересное было письмо. И мы ему ответили, что шардинг на стадии проектирования MVP не нужен. Когда проблемы реально возникнут, тогда их и следует решать.
Поэтому масштабирование вам не нужно, когда у вас все данные умещаются в оперативную память. Когда данных немного и ими легко оперировать, управлять, переносить и когда какой-то проект на стадии взлета, на стадии проектирования, думать о масштабировании не стоит. Потому что это все выливается в дополнительное время, которое нужно потратить на разработку, на сопровождение, плюс увеличивается сложность этого проекта.
Если вы там пишите ansible’вые роли или автоматизацию, то вам нужно закладывать в эти рецепты информацию о том, что у вас база данных уже такая смаштабированная. И это тоже тратится время на написание рецептов и на их сопровождение, и на их тестирование.
И плюс – это преждевременная оптимизация, которую многие ругают. Это дискуссионный вопрос. Кто-то старается заранее все спланировать на будущее, т. е. подоптимизировать под какие-то сценарии использования, которые возникнут в будущем, а кто-то считает, что это преждевременная оптимизация и она не нужна. Поэтому это спорный вопрос. Но, я считаю, что преждевременная оптимизация не очень нужна, потому что она может и не пригодиться в итоге, а время на нее уже потрачено.
Когда масштабирование нужно?
Когда мы исчерпали ресурс оптимизации запросов. Вы получаете какие-то отчеты о работе своей базы данных и все запросы уже оптимизировали. Вы построили нужное количество индексов. Переписали неоптимальные запросы. И, действительно, больше уже оптимизировать нечего, все запросы оптимальные. Но ресурсов на их обработку не хватает.
Когда исчерпали ресурс схемы данных. У вас есть таблицы, вы из них выкинули все ненужное. Все JSONs, которые лежат в jsonb полях, оптимизированы. Вытащены в отдельные колонки, либо ненужные дополнительные поля в этих JSONs вытащены и выброшены, вы не храните мусор в базе. Т. е. схема уже жестко оптимизирована и хранится только реально необходимая информация.
И исчерпаны инфраструктурные ресурсы. Например, у вас не хватает количества ядер, у вас утилизация процессора под 100. Либо вы исчерпали возможности, которые предоставляет ПО. Например, у вас на каждой базе стоит PgBouncer, который оптимизирует количество подключений, и этого все равно не хватает.
Если говорить про масштабирование, то самый простой и самый наиболее распространенный способ – это использование потоковой репликации.
Потоковая репликация – это когда у нас есть мастер. Мастер "стримит" журналы транзакций на реплики. Все просто.
И сама потоковая репликация удобна тем, что узлы, вот эти реплики, они могут выступать в качестве hot standby узлов. Мы можем на них переключаться, если мастер стал недоступен. И можем отправлять на них read-only трафик, т. е. это уже возможность для масштабирования. Если у нас мастер не справляется с нагрузкой, мы можем часть read-only трафика отправить на реплики.
И этот вариант часто недооценен, и часто не используется. Мы у многих клиентов, когда администрируем базы видим, что мастер довольно-таки сильно нагружен, а при этом, если посмотреть на реплики, то на них нет никакой утилизации. Они не используются для запросов. И, на мой взгляд, это очень большое упущение, потому что эти ресурсы ради того момента, когда умрет мастер и можно будет переключиться на реплику, они простаивают. И это очень неоптимальный подход. Поэтому, когда мы говорим о масштабировании, нужно начинать именно с реплик.
Но есть проблемы. Когда мы эксплуатируем потоковую репликацию, всегда могут быть проблемы, связанные с лагом репликации. Запросы, которые мы отправляем на мастер, на реплику, могут возвращать разные результаты.
Соответственно, чтобы решать эту проблему, мы должны переключиться на уровень приложения. И уже на стороне приложения точно представлять, какие данные мы хотим получить с мастера, а какие с реплики. Здесь у нас уже вовлекаются разработчики приложения. И нужно работать с ними.
Другая проблема более комплексная и сложная. Это балансировка трафика, потому что здесь нужно привлечь не только разработчиков, но еще и инфраструктурный отдел: это админы, это люди, которые поддерживают нашу облачную инфраструктуру. Нам нужен какой-то слой балансировки трафика от приложения к базе, чтобы балансировать этот трафик, потому что у нас получается два потока. Один поток на запись, который нужно отправить на мастер. И большой поток чтения, который нужно отправить на реплику или на реплики, если их несколько.
В балансировке бывает два подхода. Я их условно назвал инфраструктурный подход и прикладной подход.
Инфраструктурный подход – это когда мы говорим нашим администраторам, что нам нужна балансировка. И они уже с помощью каких-то инструментов реализуют нам слой балансировки между приложением и базой.
Варианты есть разные. Если в инфраструктуре используется какие-то DCS системы: Consul или Etcd, мы можем с помощью DNS и этих систем построить балансировку.
Можем использовать более распространенный метод – это использование отдельных балансировщиков типа Haproxy и дополнительных вспомогательных вещей типа Confd, Consul-Template, которые определяют конфигурацию Haproxy в зависимости от изменений в топологии postgres’ового кластера.
Допустим, у нас роль мастера перешла на другой хост. Балансировщику нужно как-то эти изменения в себе зафиксировать, чтобы перенаправлять правильно трафик на нужные узлы. Для этого и используются дополнительные инструменты которые я упомянул ранее.
И всегда нужна отказоустойчивость на уровне сети, потому что у нас есть какой-то IP-адрес, какое-то DNS-имя, которое прописано, например, в конфигах приложения. И приложение уже ходит к этому имени, ходит в базу данных через слой балансировки.
Соответственно, если у нас IP-адрес принадлежит какому-то узлу балансировщика, и этот узел исчез, то нам нужно сохранить этот адрес доступным. Нужно перекинуть этот адрес на другой узел. И мы это делаем с помощью Keepalived или VRRP, которые реализуют это на аппаратном уровне, либо на программном уровне.
Application-based или прикладная балансировка. Здесь уже все козыри отдаются разработчикам приложения. Потому что здесь уже приложение решает, как ходить, на какие узлы и с каких узлов брать данные.
Например, могут быть какие-то актуальные данные, которые нужны всегда свежие. И мы берем их с мастера. Самый простой пример – это баланс пользователя. мы берем его всегда с мастера, потому что нам нельзя, чтобы лаг репликации мешал получить свежие данные. А какие-то второстепенные вещи мы можем брать уже с реплик. И вот эта балансировка у нас уже осуществляется на уровне приложения.
Но тут тоже есть несколько вызовов, несколько острых моментов. В нашем кластере, особенно, если мы используем какой-то auto-failover, роль мастера переменчива. Допустим, произошел какой-то сбой. И роль мастера переключилась на другой сервер. Наше приложение должно быть готово к этому и оно не должно паниковать. Оно должно переустановить соединение. Если это была какая-то транзакция, то оно должно повторить транзакцию. Если это был запрос, то оно должно повторить этот запрос.
И в то же время должен быть некий предохранитель (circuit breaker), чтобы непосредственно после аварии на нашу базу данных не хлынул большой поток накопленных запросов, т. е. нельзя заспамить базу.
С одной стороны, это на словах все выглядит просто, а в приложении это реализовать довольно сложно. Хотя современные тренды и тенденции в разработке приложений диктуют эти условия. Есть паттерн twelve factor application. И среди этих факторов как раз указываются эти подходы, что приложение должно реализовывать эти режимы работы. (Примечание: еще можно упомянуть про 19 Factor App)
Про балансировку я, наверное, даже слишком глубоко закопался. Возвращаемся к масштабированию. Где и как мы используем потоковую репликацию как вариант масштабирования?
У многих клиентов hot standby есть всегда, но он практически не используется. Когда заходит речь о выделении ресурсов, мы говорим, что давайте часть трафика мы перенесем на реплики и это будет лучше.
У нас есть какой-то read трафик, плюс мы можем вынести аналитические запросы. И уже мы можем разгрузить мастер от нагрузки и смасштабировать существующую нагрузку, идущую от приложения.
Плюс у нас дополнительно эти реплики могут использоваться и для резервного копирования. С них снимаются резервные копии и куда-то складываются. Таким образом снимается еще одна дополнительная нагрузка с мастера. Это такой небольшой бонус от наличия реплик.
Но есть недостатки. Самый основной – это лаг репликации. Когда мы используем потоковую репликацию, нам нужно следить за лагом репликации и смотреть, чтобы он был как можно меньше.
И у нас есть несколько вещей, которые вызывают этот лаг репликации. Это могут быть тяжелые запросы. Это могут быть какие-то вакуумы, особенно, если их запустилось несколько сразу. Это могут быть какие-то массовые операции по изменению данных: массовые deletes, updates.
И есть разные варианты, как смягчать влияние и эффект от этих операций. И нужно применять эти операции правильно. Если мы делаем какие-то массовые обновления данных, то нужно делать их маленькими пачками. Если мы работаем с вакуумом, то мы оптимизируем конфигурацию вакуумов, когда они могут работать много и при этом не влиять негативно на дисковую производительность.
Если мы используем какие-то запросы на реплике, то мы можем столкнуться с тем, что долгие запросы отменяются. И нам нужно быть к этому готовым. Это, так называемые, конфликты восстановления (recovery conflicts), когда выполнение запроса входит в конфликт с восстановления журнала транзакций. Это тоже довольно частая проблема. И мы ее тоже лечим изменением файла конфигурации PostgreSQL.
Плюс есть такой эффект, как bloat, когда у нас таблицы, индексы распухают из-за того, что у нас есть долгие запросы. Особенно это проявляется, когда включена опция hot_standby_feedback. Она позволяет откладывать наступление вакуума и чистку мертвых строк на мастере, тем самым увеличивая bloat. Т. е. используя потоковую репликацию для масштабирования, у нее есть как и плюсы, так и минусы. Мы тем самым масштабируем PostgreSQL, но с другой стороны, есть некоторые подводные камни, про которые нужно помнить. Т. е. у нас нет какого-то решения, хорошего со всех сторон.
И что в итоге? В итоге у нас есть мастер на запись и масштабирование записи у нас по-прежнему ограничено мощностью одного узла. Я чуть дальше расскажу, какие варианты есть для решения. А чтение мы можем масштабировать путем увеличения количества реплик.
Давайте зайдем с другой стороны, допустим у нас достаточно ресурсов процессора, памяти и т. д. Но есть дефицит дискового пространстова, простыми словами недостаточно места для хранения данных. Но все данные нам при этом нужны вместе, мы не можем вынести часть данных в какие-то отдельные сервисы и держать их там.
С 10-ой версии PostgreSQL, которая вышла, если не изменяет мне память, в 2018-ом году (Уточнение: в 2017), появилось декларативное партиционирование. Раньше партиционирование делалось на триггерах и оно было не очень удобное. Нужно было вешать триггеры, писать функцию для маршрутизации и это было неудобно. Сейчас все проще. Сейчас есть декларативное партиционирование. Оно настраивается довольно быстро.
И существующее партиционирование, которое на триггерах, его легко смигрировать на декларативное. Т. е. это очень удобный и хороший инструмент в PostgreSQL. Но он не относится напрямую к масштабированию. Поэтому я не буду на нем долго задерживаться.
Есть хороший вариант применения декларативного партиционирования – это для каких-то архивных данных, которые у нас записываются и потом периодически читаются. Т. е. мы их не изменяем никак. У нас есть какие-то оперативные данные, которые условно назовем «горячей головой», и есть есть холодный архив — "холодный хвост", который мы читаем очень редко.
И при использовании этого декларативного партиционирования становится легко управлять местом. У нас данные поделены на секции, и мы уже управляем секциями. Если мы можем разбить данные на секции, то мы можем эти данные как-то отдельно заархивировать, отдельно сгрузить, положить в другое хранилище.
Кроме того, эти отдельные сохраненные партиции, которые у нас лежат уже в качестве копий, мы можем с помощью Foreign data wrappers подключить PostgreSQL и уже взять оттуда данные при необходимости, если это надо.
Например, есть файл Foreign data wrappers, который позволяет подключать CSV-файлы в виде таблиц в базу данных. Есть неофициальный S3 Foreign data wrappers, который позволяет читать из S3 CSV-файлы и тоже подключать их в качестве таблиц. Т. е. всегда можно придумать такую схему, когда у нас разработчикам вдруг понадобились данные и мы должны их легко подключить. Мы должны рассматривать такую возможность, потому что она возникает.
И как у нас используется? У одного из клиентов мы эксплуатируем систему, в которой оперативных данных примерно 7,5 TB, это включая таблицы, индексы. Т. е. данных довольно много. Они размещены на нескольких tablespaces. И управлять ими не всегда просто, особенно когда начинается нехватка мест на каком-то из tablespaces.
Но мы со временем порешали эту проблему. Мы смержили все tablespaces в один, проблема исчезла.
Весь холодный архив хранится в S3. Мы периодически, раз в месяц сгружаем старые ненужные партиции, сжимаем их и кладем в S3. И архив занимает всего 1 TB. Это уже без индексов, просто сжатый plain text.
И данные лежат с 2015 года. Мы в 2015-ом году начали потихоньку какие-то таблицы добавлять. Со временем их количество возросло, стало все больше и больше. Сейчас я даже с ходу не скажу, сколько их там.
Реализовано это все довольно просто. Мы не стали ничего придумывать. Мы взяли простые shell-скрипты, положили их в cron и у нас по расписанию запускаются эти скрипты, которые пакуют данные, архивируют, отправляют в S3, и они там лежат. Т. е. это довольно простое лобовое решение, но оно работает.
Плюс те же самые скрипты удаляют еще и старые данные. Т. е. там простой механизм, который цепляет старую секцию и удаляет ее.
Основная проблема – это когда к нам приходят разработчики и говорят: «Подключите нам данные двухгодичной или одногодичной давности». Учитывая, что у нас вся эта история тянется с 2015-го года, то она реализована как SQL-бэкапы, plain-текст, так называемый. Сейчас мы уже подумываем над тем, а не использовать ли нам custom формат дампов. И нет возможностей подключить те секции прямо из S3. Мы не можем использовать никакие механизмы Foreign data wrappers, поэтому приходится это все выкачивать руками, распаковывать, создавать ddl, туда это загружать и потом подключать к основной таблице. Это довольно-таки рутинный процесс. И он довольно-таки неудобный.
Этот вопрос как раз уже более близок к шардингу. Мы можем взять декларативное партиционирование и эти партиции вынести на другие хосты, на другие сервера с помощью Foreign data wrappers подключить их через протокол FDW. И они будут видны на основном узле.
Я об этом способе буду рассказывать дальше, но, с моей точки зрения, этот способ не очень хороший. И у него есть масса недостатков.
Поэтому если мы уперлись в производительность, наиболее простым и легким вариантом является scale-up. Мы просто наращиваем железо, увеличиваем количество ядер, количество памяти, насколько это возможно.
Если это какой-то облачный провайдер, мы берем более мощный (и более дорогой) instance. Если это какое-то арендованное "железо", мы арендуем еще более мощное "железо". Как правило, этот вариант гораздо проще сделать, хоть и дороже, вместо того, что мы будем городить какие-то распределенные системы на PostgreSQL.
Это путь для тех, кому хочется приключений и каких-то челленджей. Можно сделать, так называемый, шардинг. Можно взять несколько несвязанных между собой PostgreSQL на отдельных серверах. Какой-то из этих узлов представить в виде мастера узла. На этом мастере узле сделать декларативную партиционированную таблицу, а все секции разместить на отдельных узлах. И через механизм Foreign data wrappers эти таблицы включить в мастер узел. Таким образом, приложение работая с мастер узлом, оно будет работать с нашими листовыми узлами и данные брать оттуда. Но на самом деле приложению будет казаться, что оно работает с одним узлом.
По-другому, на мой взгляд, сделать не получится. Каких-то лучших, дешевых и простых возможностей в самом PostgreSQL нет. И это единственный вариант. И есть проблемы, т. е. это не самая идеальная схема, но других вариантов особо нет. Потому что нет инструментов, которые позволяют реализовывать какие-то другие схемы. Мы можем использовать только какие-то отдельные продукты, построенные на базе PostgreSQL. Например, Greenplum или PostgreS-XL. Они в свою очередь тоже несут свои проблемы и издержки. Но при этом дают пользователю дополнительную функциональность в плане горизонтального масштабирования.
Но у всех этих решений, есть одинаковые болезни. Например, издержки для обеспечения high availability. Все листовые узлы, на которых живут данные, должны быть подстрахованы репликами. Потому что, если узел вышел из строя, мы теряем данные, то нам нужна реплика, чтобы данные эти не потерять вообще.
Не самая лучшая производительность. Давайте возьмем Postgres-XL/XC, CitusDB, то там используется двухфазный коммит. И это небыстро. А если у нас еще сеть какая-то медленная, то при обмене данными нужно согласоваться между всеми участниками кворума. Это медленно.
Если брать вариант с декларативным партиционированием и Foreign data wrappers, то там нет никакого двухфазного коммита. И мы можем напрямую писать в листовые узлы данные, но нам нужно следить тогда на уровне приложения за тем, чтобы данные были согласованны. Это тоже такой челлендж, над которым надо очень хорошо подумать перед тем, как реализовывать схему.
И плюс производительность. Я как-то делал benchmark, взял довольно простые узлы под 2 ядра и 2 GB памяти. Я сравнил, как работает вот эта схема с декларативным партиционированием и Foreign data wrappers. Взял небольшой dataset, который влезает в память, чтобы исключить чтение с диска, чтобы в производительность запросов не вкладывалось дисковое чтение. И попробовал посмотреть, как это работает.
Таблица простая. Она состоит из трех полей: timestamp и 2 int’а. И все это партиционированно по timestamp’у.
Запросы тоже довольно простые. Первый запрос – это select по первичному ключу.
Baseline – это случай, как если бы все данные жили на одном хосте. Второй случай "sharding" (именно в кавычках, язык не поворачивается называть это шардингом) – это как, если данные размещены на листовых узлах, и мы работаем только с мастером узлом. Разница по времени выполнения запросов примерно в 6 раз. Но цифры, конечно, миллисекунды. Т. е. тут 0,5 миллисекунд, а второй результат – 2,9 миллисекунд. С точки зрения не сильно нагруженного приложения, это может быть допустимо. Но иногда это может играть и очень большую роль.
Возьмем запрос посложнее. И возьмем несколько строк. В этом запросе берется 1 000 срок. Время уже увеличивается с 2 миллисекунд до 26, т. е. разница примерно в 9 раз.
Теперь посчитаем агрегат по выбранным данным. Время примерно то же самое, не сильно изменилось. Было 2,24 и 2,26, т. е. примерно одинаковое.
И последний вариант – это мы просто делаем update также по первичному ключу. Получилась разница как примерно с первым запросом в 5 раз. Т. е. видно, что это решение с декларативным партиционированием и Foreign data wrappers – оно не самое быстрое.
Позже мне пришло в голову, что идеальнее было бы сделать тест, когда листовые узлы как бы находятся на одном узле. Т. е. здесь мы упираемся в производительность сети, а было бы интересно убрать сетевую составляющую и посмотреть, как это работает на одном узле, чтобы посмотреть overhead самого PostgreSQL при таком решении. Но мне эта мысль пришла поздно. И я, к сожалению, не сделал.
Есть варианты с Postgres-XC/XL, CitusDB. Но, как я уже сказал, там используется двухфазные коммит и это все небыстро. Плюс там есть transaction менеджеры, так называемые, GTM узлы, которые координируют тот поток транзакций, который генерирует система. Это тоже вносит свои задержки в обработку транзакций.
Плюс у нас у нас есть те же проблемы с high availability. Нам каждый узел нужно подпереть репликой, чтобы избежать потери данных в случаи аварии.
Можно использовать какие-то другие решения NoSQL баз данных: Cassandra, Riak (Примечание: в вопросах после доклада, кто-то из аудитории отметил что Riak мертв), но они оптимизированы на запись. Мы можем писать туда много данных, но это не полноценное замена PostgreSQL. У нас нет SQL языка, у нас нет возможностей делать JOINs. Да и еще есть масса других недостатков, которые присущи этим системам… В прочем, как и преимуществ.
И небольшой итог.
Я надеюсь, примерно понятно, что у PostgreSQL ограниченные возможности по масштабированию. Мы можем очень хорошо масштабировать чтение, но с записью есть проблемы.
У нас есть потоковая репликация и мы можем делать много реплик и направлять на них чтение, но с записью очень тяжело. Мы по-прежнему ограничены мощностью одного узла.
Мы можем придумывать какие-то решения другие. Но эти решения не будут бесплатными, у них есть свои проблемы и недостатки.
Это все, что я хотел сказать. Всем спасибо за внимание!
Вопросы:
Добрый день! Спасибо за доклад! Речь шла про hot standby. И написано предостережение в пункте 13.5 в официальной документации PostgreSQL, что hot standby поддерживает не все уровни изоляции. Т. е. если у нас мастер, например, работает repeatable read, то у нас проблем не будет, а если у нас мастер serializable, то у нас уже нюансы могут быть на реплике. Поэтому вот такая небольшая поправочка.
Да, верно. Есть свои нюансы. Но вообще, serializable — это довольные редкий случай, на мой взгляд. Такая строгая сериализация бывает нужна в довольно редких и исключительных случаях. Но, да, я согласен с замечанием.
Спасибо за доклад! У меня два вопрос. Первый вопрос. При шардировании, когда есть мастер база, который умеет ходить в другие базы, данных может быть много и они могут быть разными. И они могут изменяться в одной транзакции. Что в этом случае? Как транзакция будет работать?
Если мы работаем именно с мастером, то она [транзакция] будет работать как обычно. То есть у нас берутся все соответствующие блокировки необходимые этой транзакциии для работы с объектами. И плюс мы получаем еще те ограничения, которые у нас налагает Foreign data wrappers, т. е. надо посмотреть документацию, как он работает в каких-то сложных случаях в транзакционном режиме.
Т. е. если данные меняются, например, в мастер базе и в каком-нибудь из шардов, то как это будет согласовываться?
Если мы используем это колхозное шардирование, то будет просто обычная запись сначала в локальные данные в какую-то локальную таблицу, и плюс мы запишем в партиционированную таблицу. А дальше уже под капотом уже запрос поедет в Foreign data wrappers в листовую таблицу и там будет сделано изменение. Т. е. с точки зрения PostgreSQL – обычный режим работы. Тут больше интересует, что здесь могут быть нюансы при работе Foreign data wrappers, т. е. как он будет на это смотреть. Но, я думаю, что с точки зрения приложения никаких изменений практически делать не придется. Для него это мастер таблица и под капотное устройство шардов, оно будет незаметно и прозрачно.
Спасибо за доклад! Вопрос про PL/Proxy. Почему он не упоминается?
PL/Proxy – это довольно сложная штука. Если очень хочется, то, конечно, можно использовать. Ко мне подходил молодой человек и спрашивал: «Как обстоят дела с PgQ? Мы с MSSQL портируемся на PostgreSQL. У PgQ нет документации». По PgQ мало документации, а по PL/Proxy еще меньше. Т. е. можно использовать, но из нашей практики ни у кого из клиентов практически его нет. Вы правильно заметили, что это как вариант. Можно использовать процедуры из PL/Proxy. И можно строить шардирование на использование PL/Proxy процедур.
Спасибо за доклад! Вы в своем докладе упомянули про реплику на шарде. Я правильно понимаю, у нас FDW таблица смотрит в мастер базу, с нее есть реплика и мастер у нас уходит, и нас в этот момент падает сама…
Все запросы еще подвешиваются, которые работали.
Да-да. Одно только решение напрашивается. Мы подменяем DNS у шардированной реплики и по-другому нам никак нельзя перекинуть.
Да, это дополнительная сложность. Я об этом и говорю, что это путь для сильных духом. И возникают такие ситуации, которые с ходу не понятно, как решать. Да, есть варианты. Можно попробовать отстрелить, но там, по-моему, такой механизм, что он игнорирует попытки отстрела. И решения у меня с ходу нет.
Небольшое дополнение про FDW. Предлагаю тем, кто задумывается, следующий случай. Пользователи приклада являются пользователями базы данных и права на выполнение всяческие запросы: select, update, insert, delete, они не только на уровне приложения организованы, но и на уровне базы данных.
А, в виде хранимых процедур?
Не в виде хранимых, а на конкретной таблице, на выполнение конкретных функций, на конкретные вьюхи (views), на это все они прописаны в самой СУБД в соответствующих системных таблицах.
Можно. Но это дополнительные издержки на сопровождение и на реализацию.
Мне буквально только что пришел голову ответ на ваш вопрос. Вполне возможно на запросы выставлять тайм-ауты типа statement_timeout, lock_timeout в надежде, что при падении реплики запросы отменятся через внутренние механизмы PostgreSQL, т. е. по истечению тайм-аутов.
Как подменить реплику? У нас задача отстрелить активность и потом сделать maintenance. Допустим, мы по тайм-ауту отстрелили активность. Допустим, нам повезло и отстрелили. В этот момент мы делаем alter для user mappings и server.
С ходу сложно придумать что-то. Возможно, какой-то выставлять maintenance приложению, т. е. как-то информировать приложение, что мы деградируем. И частью этих данных, которые расшардированы, мы ими воспользоваться пока не можем. Приложение видит, что мы должны плавно деградировать и Ок. Оно пользователю возвращает какую-то ошибку, например, повторите запрос позже. И в это время мы выполняем maintenance. Это все костыли. Поэтому это путь для сильных духов. Очень много таких сценариев, в результате которых не знаешь, что в итоге будет.
Спасибо за доклад! Какой размер таблиц Foreign data, которые подключали? Были ли терабайтные таблицы, в которых миллиарды строк? И какие баги встречали? Может быть, какие-то сложности были?
У нас не было терабайтных таблиц. У нас обычно размер таблиц варьируется в десятки гигабайт, не считая индексов, т. е. там 90, может быть, за 100. Но терабайтных, к счастью, не было. Потому что это резервное копирование превратится в постоянно работающую перманентную задачу. Поэтому у нас секционирование, размер таблиц не настолько страшный.
Спасибо за доклад! Было очень интересно! Был пройден большой путь. Как небольшой лайфхак – мы проходили похожий путь и вопрос решения с задержками по сети решился вопросом внедрения InfiniBand, т. е. отказ от Ethernet и переход InfiniBand.
Если мне не изменяет память, там уже с 40 GB скорости начинаются?
Это очень хорошее решение, но дорогое.
Здравствуйте! Я коллега Алексея. Я хотел бы добавить еще один вариант нативного партиционирования именно встроенными инструментами PostgreSQL. Это когда вы можете получить мульти-мастер, используя при этом еще фишки логической репликации. И если у вас есть данные справочные небольшие, которые должны быть на всех нодах, и при этом вы как-то можете данные побить по нодам, вы можете поднять много мастеров PostgreSQL. А справочные данные с помощью логической репликации и механизма публикации таблицы и подписки на нее можете распространить по всем нодам.
Но на данный момент я не рекомендую с этим баловаться, потому что в логической репликации есть достаточно много багов, когда просто склеивает ласты мастер сервер, которые публицирует свои данные. Но как один из вариантов, когда там все баги исправят, можно это использовать. Но при этом надо понимать, что приложение должно уметь работать и понимать, что у вас много-много разных мастеров. Это один из вариантов без Foreign data wrappers. Foreign data wrappers – это инструмент, когда у вас одна точка мастер сервер и вы не заморачиваетесь с тем, куда дальше уходит запрос. Тут придется заморачиваться, но зато можете писать куда ходите и как хотите. Это небольшое дополнение.
Всем спасибо!