PostgreSQL на K8s в Zalando: два года в бою. Александр Кукушкин (Zalando)
Мы все знаем, что большинство DBA очень консервативны и предпочитают, чтобы их базы жили исключительно на выделенных серверах. В современном мире с микросервисами, Kafka и Kubernetes количество баз начинает расти прямо пропорционально размеру организации и очень быстро выходит за пределы комфортного ручного или полуавтоматического управления.
Я работаю в Zalando уже почти 7 лет. Кто из вас слышал о Zalando?
Для тех, кто не слышал, это компания, похожая на российскую Lamoda.
Мы продаем одежду и обувь, но делаем это в Европе, в 17 странах.
У нас есть 7 своих логистических центров и складов.
В Zalando работает больше 15 000 человек.
И из них порядка 2 000 работает в technology. Люди в technology распределены примерно по 200 командам, которые пишут приложения.
В последнее время мы деплоим очень много всего на Kubernetes и работаем много с Kubernetes.
О чем доклад?
- В первую очередь для тех, кто не очень хорошо знаком с Kubernetes, я немножко расскажу о том, что это такое.
- Расскажу о том, как мы деплоим Postgres на Kubernetes с помощью Spilo и Patroni.
- Расскажу, какие задачи нам помогает решать Postgres-Operator на Kubernetes.
- И самое интересное — после всего этого пройдусь по проблемам, на которые мы натыкаемся, которые приходится лечить и очень тщательно исследовать.
- Мы используем Kubernetes очень активно. У нас более 140 кластеров. Это примерно 50/50 production/test environment. Т. е. каждый cost unit получает 2 своих Kubernetes-кластера. На тестовом можно гонять любые тесты, можно деплоить как хочешь. Туда есть прямой доступ безо всякого контроля.
- На production deployment исключительно через CI/CD. Даже docker image, который туда деплоится, должен быть собран через CI/CD.
- Доступа к production к Kubernetes-кластерам напрямую у нас нет, но доступ возможен. Для этого надо либо попросить коллегу предоставить такой request, так называемый принцип 4-х глаз, либо должен быть какой-то инцидент, должен быть открыт тикет. В этом случае доступ может быть открыт с помощью инцидент-тикета.
Сколько у нас Postgres на Kubernetes? На самом деле цифра достаточно большая. Получается примерно в среднем 10 Postgres-кластеров на один Kubernetes-кластер.
Postgres-Operator и Postgres на Kubernetes работают не во всех кластерах, а примерно на сотне из этих 140, так что в среднем такая цифра еще больше.
Кто из вас знаком с Kubernetes, Postgres? Практически большинство. Я не думаю, что следует уделять много времени объяснению, что такое Kubernetes.
Для инженеров, которые не знают, всегда можно найти какие-то ассоциации.
- Сервис в Kubernetes называется нодой. И ее можно посмотреть с помощью tools.
- Виртуальная машина на Kubernetes будет подобна под и т. д.
Все это выглядит на картинке более красочно. У нас есть одна или несколько мастер-нод. И есть worker-ноды, на которых несколько подов или приложений, которые исключительно для Kubernetes такие, как kubelet, docker, fluentd, kube-proxy и т. д.
И есть ряд подов. Это уже поды, в которых будет работать ваше приложение.
Как нам деплоить базы данных?
Для баз данных нам нужно где-то хранить данные. Как вы все знаете docker был изначально не очень приспособлен для работы с данными. На Kubernetes ситуация в этом плане немножко лучше. Там есть такая вещь, как PersistentVolumes и PersistentVolumeClaim.
И самая вишенка на торте — это StatefulSets, который, во-первых, позволяет нам запустить гарантированное количество подов со стабильными идентификаторами, т. е. у каждого пода будет всегда уникальное имя. И, во-вторых, StatefulSets позволяет с помощью PersistentVolumeClaim и PersistentVolumeClaim templates автоматически создавать volume, если они нужны или монтировать уже существующие volume к поду, которому были предназначены.
Чтобы запускать Postgres или приложения на Kubernetes, нам нужны контейнеры. Как правило, на Kubernetes используется docker. Я не знаю, можно ли сейчас использовать что-то другое.
- У нас есть свой docker image. Мы его называем Spilo. Spilo в переводе с грузинского — это слон. Внутри нашего image у нас есть несколько версий Postgres, т. е. на данный момент, начиная с 9.3 и заканчивая версией 12.
- В дополнение к этому мы инсталлируем всевозможные полезные postgres«овые extensions такие, как pg_partman, pg_cron, postgis, etc, timescaledb.
- И всевозможные дополнительные tools такие, как pgq, pgbouncer, wal-e/wal-g. Это немножко антипаттерн, к сожалению, в мире docker и Kubernetes, но нам это было необходимо, поскольку этот image мы используем помимо в Kubernetes на EC2 instance в Amazon.
- Для HA мы используем Patroni,
- И все это дело конфигурируется с помощью переменных окружения.
Что такое Patroni? Я думаю, что не стоит много рассказывать, все уже должны знать. Это решение для автоматического переключения Postgres, для HA.
Patroni написан на Python. И он интегрируется с Kubernetes. И делает Postgres first class citizen внутри Kubernetes, т. е. позволяет задеплоить Postgres нативно.
Patroni и Postgres могут работать на Kubernetes без supervisor вроде оператора, т. е. без внешней помощи.
Но Patroni — это немножко больше, чем решение, которое для автоматического failover построено. Patroni позволяет деплоить новые кластеры, т. е. выполнить UnitDB для Postgres, либо можно с помощью Patroni создать клон существующего кластера или выполнить point in time recovery, что тоже бывает очень полезно.
В случае, если мы добавляем новые реплики, Patroni будет их инициализировать и начнет стримить с мастера.
И последнее, в чем тоже помогает Patroni, это менеджмить конфигурацию Postgres. Если мы хотим поменять какой-то параметр в конфиге Postgres, то нам нужно всего лишь попросить Patroni: «Сделай это». И он применит это значение ко всем нодам.
Как все это выглядит? Мы деплоим StatefulSet. На картинке у нас есть две ноды. На каждой ноде будет свой под. И к каждому поду у нас приаттачен PersistentVolume. И эти поды внутри StatefulSet, поэтому у подов demo-0 и demo-1.
Кто из них будет мастер, кто из них будет реплика — решает Patroni. И Patroni использует для выбора лидера kubernetes«кий объект endpoint. Т. е. если мастер пропал и происходит гонка за лидером, то Patroni коннектится между собой и решает, кто будет новым лидером. И после этого тот, кто решил, что он должен быть лидером, попытается обновить endpoint, прописать свою имя туда и плюс прописать свой IP.
Есть у нас еще лидер-сервис. И если приложение будет коннектиться через этот сервис, оно всегда попадает на под с мастером.
Для реплик у нас есть отдельный сервис demo — repl. И тут важный ключевой момент, что мы должны использовать labelSelector: role = replica. И тогда он выберет все поды с ролью реплика, которые попадут в этот labelSelector.
Как все это деплоить?
Сначала мы, как и все, использовали YAML manifests. Это задача простая. Мы автоматизированы, но никто не любит иметь дело с YAML. Он должен быть человекочитаемым и легко человекосоздаваемым, но на самом деле он ни тот и ни другой.
Можно попробовать использовать Helm, т. е. будет CI/CD deployment. Всего лишь одна команда и у нас кластер готов. Но это не решает задачу rolling upgrade. Если мы хотим провести minor апгрейд Postgres, если мы хотим поменять docker image, что нам надо сделать? Нам надо обновить конфигурацию StatefulSet и убить поды, которые относятся к этому StatefulSet, т. е. убить одного за другим.
Есть другая задача, которая есть, в rolling upgrade. У нас может идти речь о rolling upgrade самого Kubernetes-кластера.
Что это такое? Допустим, что у нас есть кластер из трех нод: нода 1, нода 2, нода 3. И они расположены в разных availability зонах, т. е. в разных дата-центрах. Это тоже важно, потому что volumes с данными обязаны располагаться только в этих зонах.
Если Kubernetes выполняет роль upgrade, ему надо остановить и рестартовать все workers, т. е. перезапустить их. В случае с cloud environment как AWS, когда мы терминейтим какую-то EC2 instance, он просто уходит. И в итоге под будет запущен на новой ноде.
К чему это может приводить? Допустим, у нас есть 3 кластера, которые работают на 3-х нодах. 2 мастера в первой availability зоне и один во второй.
Kubernetes сначала убивает первую ноду, мастера переезжают. Patroni этим занимается. Это вызывает enter option для приложения, т. е. на пару секунд connections падают, надо будет переконнектиться. И это уже не очень хорошо, но с этим еще можно жить.
Следующий шаг.
Мастера переехали и Kubernetes при выполнении rolling upgrade убивает вторую ноду.
Еще один мастер переезжает. Второй мастер переезжает уже во второй раз. Уже гораздо хуже ситуация. Никому такое не нравится.
А у нас есть еще одна нода, на которой вдруг сейчас оказались все мастера.
И они при удалении последней ноды переезжают еще раз.
Что у нас в итоге? В итоге — ужас.
Если посчитать, то кластер А испытал 3 failover подряд, т. е. у нас было 3 пода и получилось 3 failover. Кластер B — 2, кластер C — 2.
С этим надо что-то делать, это надо оптимизировать.
И не только это.
Мы очень много страдали оттого, что управление кластерами было ручное, т. е. девелопер приходит к нам, просит: «Мы хотим кластер Postgres». Мы создаем конфигурацию, оформляем pull request в Git. Создаем кластер с помощью kubectl или утилит Amazon. Кластер работает.
Если после этого мы хотим его или заапгрейдить, или что-то там поменять на более мощный instance, то это снова требует ручных вмешательств.
Периодически надо создавать или удалять юзеров для сотрудников или нам нужно создавать юзеров для приложений.
В конечном итоге кластер либо продолжает жить, либо мы его удаляем.
Какие наши цели?
Надо автоматизировать абсолютно все:
- Deployments. С ними все понятно. Это и раньше не было проблемой.
- Upgrades clusters. Это rolling upgrade Postgres. И автоматизировать очень плохой rolling upgrade Kubernetes самих нод.
- Создание всевозможных пользователей: пользователи для приложений, пользователи для сотрудников, пользователи в базе.
- Минимизировать количество failovers при любом таком maintenance.
Мы решили все эти задачи с помощью нашего Postgres-Operator. Про паттерн оператора в Kubernetes, я не думаю, что стоит рассказывать. Это всем известно. Идея в том, что оператор в себя вбирает весь наш опыт и пытается автоматизировать все задачи, которые мы делали. Но поскольку оператор — это машина, он может это делать более качественно и реагировать гораздо быстрее.
Для того чтобы задеплоить кластер Postgres, достаточно написать небольшой YAML-файл. Здесь всего несколько ключевых моментов.
Во-первых, мы задаем имя кластера, а также мы еще должны задать ID команды, т. е. имя команды. Team, в котором я работаю, называется ACID. Почему? Потому что это основной принцип баз данных, т. е. Atomicity, Consistency, Isolation, Durability.
Во-вторых, мы задаем размер volume. В данном случае — 1 гигабайт. Количество подов — 2. И задаем версию Postgres. Мы хотим одиннадцатую. В дополнение к этому можно сказать: «Оператор, пожалуйста, создай нам юзеров и создай базу данных. И для базы данных укажи owner вот этого пользователя».
Как все это выглядит на практике?
Приходит DB deployer. Это либо человек, либо CI/CD. Он записывает вот этот YAML-файл внутрь …-объекта, который создал оператор. Postgres-operator получает event и начинает свою грязную работу. Он создает StatefulSet с каким-то количеством подов. Он создает endpoint, создает сервис. Создает секреты для приложения и для Postgres, т. е. для superuser и для юзера, через которого репликации работают.
Приложение будет использовать секреты из Kubernetes и коннектиться к этому сервису, который всегда будет подключать к мастеру.
Как нам оператор помогает бороться с проблемой rolling upgrade самого Kubernetes?
Возвращаемся назад к нашей картинке.
У нас есть 3 ноды, у нас есть 3 кластера. Оператор знает, что вот эти 3 ноды должны быть удалены из кластера, поэтому он начинает действовать.
В первую очередь он удаляет реплики, которые на этих нодах. Плюс Kubernetes запустит эти реплики уже на новых нодах, потому что на старых нодах это запрещено делать.
Получается вот такая картина. Одна нода полностью ушла, потому что там уже нет никаких подов, на двух старых нодах у нас еще работают мастера.
После этого оператор делает switchover.
И у нас мастера переезжают, т. е. количество switchover = 1.
После этого можно безопасно убить оставшиеся поды на нодах, которые еще не были заапрейдены и все хорошо.
Switchover был выполнен явным образом с помощью оператора и равен всего одному. Даже в течении рабочего времени, днем, когда максимальный трафик, большинство приложений способны пережить, т. е. им нужно будет переконнектиться, но downtime будет всего пару секунд.
Что мы видели плохого? Какие мы issues испытывали?
Во-первых, все наши Kubernetes-кластера работают в AWS. И отсюда возникают следующие проблемы.
AWS API ограничивают нас в количестве запросов, которые можно выполнять через API. В случае, если интенсивность запросов превышает какой-то порог, то AWS начинает троттлить.
Чем это плохо? Поскольку Kubernetes использует AWS API для того, чтобы монтировать volumes, то в случае, когда поды переезжают на новые ноды, подключение volumes может очень сильно задерживать, и мы будем иметь наш postgres«овый кластер немного в деградированном состоянии. Там будет мастер, но может быть не быть совсем реплик. Это не очень хорошо.
В случае, если происходит deployment нового кластера, то это тоже может задержать. Это немножко неприятно, но не фатально.
Иногда и EC2 instance умирают в Amazon. Но, к сожалению, они не умирают, как обычное железо, когда умерло и умерло. И с точки зрения Amazon, EBS volumes еще подключены к этим instances. Что это означает? Мы не можем их переиспользовать, т. е. наши данные где-то есть, но мы их не можем подключить к новым instances. И до тех пор, пока instance не выключен абсолютно полностью с точки зрения Amazon, volumes так и висят. Т. е. такой процесс занимает до 30 минут, а иногда даже больше. Очень неприятно, когда ночью ты проснулся и ждешь полчаса.
Это не совсем проблема Kubernetes, скорее, проблема Postgres, но если заканчивается место на диске, то ни к чему хорошему это не приводит. Postgres останавливается. Patroni пытается его рестартовать. Postgres стартует, Patroni его промоутит. И происходить обратно та же ситуация — недостаток места и crash loop. Надо обязательно мониторить все, включая дисковые пространства.
Особенно в облаках не принято использовать отдельные partitions для логов, как вы это делаете в дата-центре. Всегда используется просто один volume для всего. Это делается с точки зрения оптимизации затрат. Потому что маленький volume, как правило, дает маленькие ресурсы с точки зрения … или IOPS. Большой volume дает вам больше.
Почему мы не имплементируем auto-extend для volumes? В Amazon это можно сделать запросто. Это всего один запрос API. И был у нас volume в 100 гигабайт, мы сделали его в полтерабайта и можем продолжать жить счастливо.
Если проанализировать все ситуации, которые приводят к тому, что у нас место заканчивается, то всего лишь некоторые из них реально отражают, когда у нас количество данных выросло на то, что необходимо делать auto-extend. Во всех других случаях у нас очень много медленных запросов, очень много записывается логов, т. е. это все нужно оптимизировать.
Если расширять volumes до бесконечности, то это вылетает в копеечку.
Иногда приложение просто забывает удалять старые данные. Иногда приложение занимается этим, но по каким-то причинами такие jobs ломаются. Много всего происходит.
Еще что мы замечали? Поскольку HA очень важно, но без Disaster Recovery жить вообще нельзя, мы используем wal-e для continuous archiving и для того, чтобы делать basebackup.
У wal-e есть не очень хорошая черта — он отказывается делать бэкап, если встречает в дата-директории файл размером больше полутора гигабайт. В данном случае у нас pg_stat_statements вырос до 2-х гигабайтов. Я начал разбираться, как так. И выяснилось, что она забита вот такими запросами вида: APDATE WHERE id IN и 150 разных аргументов. Т. е. с точки зрения Postgres — это все разные запросы.
Pg_stat_statements вырастает до 2-х гигабайтов. Пользы от такого pg_stat_statements тоже нет, да еще и бэкапы ломает. Это тоже не совсем к Kubernetes относится, но поскольку у нас огромный масштаб, у нас много кластеров, то на такие проблемы мы натыкаемся. И это не очень приятно.
Еще одна проблема с wal-e в том, что он умеет брать только эксклюзивные бэкапы. И в случае, если вдруг под удалили или нода упала, то внутри postgres«овой дата-директории остается label-файл и все. Такую дата-директорию уже назад нельзя подцепить без reinitializing.
Выход — это использовать какую-либо tools, которая позволяет делать неэксклюзивные бэкапы, например, wal-g, pgBackRest. Но тут есть две проблемы. Во-первых, это работает, начиная с Postgres 9.6, а у нас местами есть 9.5 даже в облаках. Во-вторых, каждый раз, когда я начинаю его использовать, я нахожу новые и новые проблемы.
Сейчас мы решили включить его по умолчанию для восстановления из бэкапа. Мы не используем wal-e, а для архивирования, для basebackup мы до сих пор используем wal-e.
И теперь к самому мясу. Что такое Out-Of-Memory? Внутри docker или внутри Kubernetes — это еще более непонятный зверь. Мы смотрим в лог Postgres, видим, что один процессов убит сигналом 9. Мы точно знаем, что никто из людей не заходил на ноду и не убивал. Доступа к production нет.
Начинаем копаться. Смотрим dmesg. Видим, что Memory cgroup out of memory убивает Postgres. Но как понять, что это именно наш процесс?
В чем тут засада? Внутри контейнера у нас свои process ID, а на хосте они отличается.
Единственное, что мы можем сделать, это сопоставить время. Но при этом dmesg -T время выдает немножко смещенное почему-то. Можно попытаться отключить OOM или настроить system control параметр «oom_score_adj», но в случае с контейнером это мало чем поможет. У нас там всего Patroni и Postgres, т. е. в итоге все зависнет, если никого не убить.
В данном примере у нас memory limit был указан в 8 гигабайтов, но cgroup насчитала, что используется 6 гигабайтов + postgres«овый shared buffers еще 2 гигабайта. Откуда набежали эти 6 гигабайтов совершенно непонятно. Мы анализировали все postgres«овые процессы, которые работали, и никакие из этих процессов не занимали памяти больше, чем несколько гигабайт.
Т. е. у нас есть такое ощущение, что cgroup может посчитать одну и ту же страницу из shared memory несколько раз, если какой-то из бэкендов ее запачкает.
Чтобы немножко избавиться от этой проблемы, мы уменьшили размер shared buffers по умолчанию с 25% до 20%. И количество случаев, когда она так падала значительно сократилось, т. е. стало жить легче.
Postgres 11-ой версии вышел год назад. В production он попал спустя пару minor releases, как обычно. И к нам стали приходить люди с жалобой, что запросы падают с ошибкой, что нет места на диске.
Начинаем разбираться. Оказывается, что место на диске — это не там, где у нас дата-файлы лежат, а shared memory. И внутри docker по умолчанию shared memory 64 мегабайта всего.
Почему Postgres 11? В Postgres 11 появился parallel hash join. Как он работает? Сначала один worker создает hash, записывает его в shared memory. Но поскольку там всего 64 мегабайта, то этот hash туда не помещается.
Что можно сделать? На docker можно увеличить размер dev/shm, передав опцию и указав новый размер.
Но на Kubernetes так сделать нельзя. Т. е. единственный способ — это смонтировать tmpfs volume с именем dshm.
В операторе мы имплементировали всего лишь одну настройку, т. е. смонтировать вот такой volume — enableShmVolume. Соответственно, включаем настройку, получаем volume. Оно не включено по умолчанию, но видимо придется.
Postgres тоже иногда доставляет. Во-первых, логические слоты репликации при failover могут терять позиции, т. е. с точки зрения Patroni, мы пытаемся уменьшить вероятность пропустить какие-то events. Patroni создаст слот после failover логический, но нельзя указать на какой позиции его надо создавать.
Если приложение очень жадное, то оно может открыть настолько много соединений, что у нас появляется FATAL too many connections. Это ломает нам всю систему. Реплики не могут начать реплицировать с мастера. В 12-ой версии Postgres я это решил. Пришлось взять в руки компилятор и вынести max_wal_senders из max_connections. Теперь для wal_senders всегда есть отдельные слоты в Postgres. И больше мы таких проблем не увидим.
И последнее пожелание к Postgres — это было бы хорошо иметь Built-in connection pooler.
Самое вкусное напоследок — это человеческие ошибки:
Очень часто, когда люди описывают cluster manifest, задают либо слишком мало ресурсов, либо слишком много. Можно описать манифест, в котором указать: 100 мегабайт памяти. Оно, может быть, даже запуститься. Может быть, будет даже работать. Но его OOM-Killer все время будет убивать. Если указать еще меньше, то оно даже не запуститься.
Слишком большие ресурсы тоже приводят к проблемам. Как правило, ноды фиксированных параметров: 4 ядра, 32 гигабайта памяти. Если указать, что я хочу 5 ядер и 64 гигабайта, то такой запрос зависнет навсегда, потому что нода не будет автоматически запущена Kubernetes«ом в нашем случае. Может быть, кто-то эти проблемы решил.
Что еще мы увидели? В production кто-то удалил ServiceAccout, который используется оператором из Spilo. Соответственно, все просто останавливается, все Postgres переходят в real only. Чтобы удалить ServiceAccount надо было запросить доступ, надо было, чтобы какой-то твой коллега сидел рядом и проверял, что ты делаешь. Все равно это происходит.
YAML-форматирование.
Я очень люблю эту картинку.
Несмотря на то, что наш кластер манифест не такой сложный, мы периодически видели, что отступы не так выровнены, re… не так написаны и т. д.
Пришлось написать такую маленькую tools, в которой просто вбиваем название кластера, версия Postgres выбирается из списка, потому что мы видели, когда люди указывали 10.10, а оно так не будет работать. Надо указать ровно 10. А также выбирается размер volume и т. д.
В итоге эта tools генерирует манифест. Манифест можно скопировать, сохранить в файл, отправить в Git и выполнить.
На тестовом environment у нас появляется вот тут сверху еще одна кнопочка «Применить». И тогда оно прямо там создаст.
Нам оператор позволяет почти 1 500 postgres«овых кластеров. Они распределены практически на 100 Kubernetes-кластеров. На это мы затрачиваем минимальное количество усилий. По ночам просыпаться, если … среди недели, приходится, наверное, один раз, т. е. каких-то больших проблем нет.
Но мы активно работаем, чтобы не просыпаться вообще. Любые проблемы, которые мы идентифицируем, мы стараемся решать и имплементировать постоянное решение либо внутри Patroni, либо внутри Spilo, либо оператор этим будет заниматься.
Все, о чем я рассказывал у нас в open source. Оператор находится в своем репозитории. Patroni и Spilo в своем репозитории.
Всем спасибо! Если есть вопросы, то задавайте вопросы.
Questions
В рамках оператора можно настроить availability зону для каждого конкретного экземпляра?
Для каждого конкретного экземпляра чего?
Мастера или реплики.
В принципе, можно задать anti-affinity, т. е. это должно работать.
Спасибо за доклад! У меня несколько вопросов. Первый: используете ли вы это в production?*
Да, конечно. У нас порядка 600 кластеров из этих 1 400 в production. Т. е. именно вот эти 600 нас будят по ночам. Тестовые не будят, но тестовые мы тоже мониторим. Нам важно, чтобы они работали в течение дня, потому что тестовый environment используется для разработки. Если разработчик не может использовать базу для разработки, то у него вся работа стоит. Соответственно, мы используем в production более 2-х лет.
Правильно ли я понимаю, что оператор может работать только external volume, т. е. Host Pass это работать не будет, т. е. с какими-то сетевыми дисками?
В принципе, да. На данный момент все верно. Мы хотим добавить возможность использовать локальные диски. Те же самые i3-volume на Amazon выглядят очень вкусно. Но в чем тут засада? Сейчас мы можем подмонтировать существующий … на новую ноду, все данные здесь. Если данных нет, то нам надо их копировать. И размер кластеров бывает разный. Иногда это несколько мегабайт, а иногда это и терабайты. Копировать терабайты долго, дорого.
Для задач, где есть …bound на базу, вы используете обычное железные кластера?
Как правило, да. Хостим на Amazon на i3-instances. Там эфемерные NVMe диски. Если instance умирает, то его надо переналивать. Но, в принципе, это работает достаточно надежно. С нашим Kubernetes team это, наверное, не очень будет хорошо работать, потому что такой rolling upgrade всех нот внутри кластера у нас происходит достаточно регулярно, т. е. 1–2 раза в месяц. И 1–2 раза в месяц переливать так данные откуда-то тоже малоприятно.
Спасибо за доклад! Как у вас реализовано резервное копирование?
Мы используем wal-e. Внутри docker у нас работает crone, который в час ночи делает полный basebackup. И кроме этого у нас настроен archive_command, т. е. все wal, которые генерируются, отправляются в S3 Amazon. Соответственно, из basebackup + все wal мы можем откатиться назад на любую точку. И retention у нас по умолчанию — 5 дней, т. е. в течение 5 дней мы можем восстановиться на любую точку.
Спасибо большое за доклад! Немножко штормит от количества кластеров. Почему 1 400? Какую глобальную задачу вы решаете? Почему не 2?
У нас 200 команд разработки. У нас офисы находятся в Берлине, Дортмунде, Хельсинки, Лиссабоне, Дублине. Много микросервисов. Есть Kafka. Соответственно, все асинхронное. И каждый микросервис хочет базу, как правило. От микросервисов есть минусы и плюсы. Минус в том, что баз очень много. А плюсы в том, что большинство этих кластеров у нас маленькие, т. е. меньше гигабайта. И таких процентов 80, наверное. Остальные могут быть очень большими. Есть до терабайта и есть по несколько терабайт даже.
А размер команды, которая следит за тем, чтобы все эти Postgres работали?
В моей команде 7 человек. Один человек способен это поддерживать в работоспособном состоянии. И чем больше мы автоматизируем, тем больший размер кластеров мы можем пасти. Это как pets world cattle. Pet — это наш кластер, который работает в дата-центре, за которым мы ухаживаем. А это — скот, т. е. если что-то не так, мы этот под прибиваем.
Через год количество кластеров увеличится?
Да, количество кластеров растет.
Александр, спасибо за доклад! Какой тип EBS volumes вы используете?
Мы используем gp2 по той причине, потому что они дешевле. Io1 — очень дорого выходит. Если нам нужно 3 000 IOPS, то io1 будет, как минимум, в два раза дороже.
Не утыкались в предел EBS gp2, который не более 250 мегабайт в секунду?
Иногда утыкаемся. Но в этом случае мы такие базы уносим из Kubernetes. Для этой проблемы есть простое решение — сделать несколько volumes, сделать нулевой RAID. И тем самым увеличить пропускную способность. В Kubernetes это сделать нельзя. И мы в любом случае выносим из Kubernetes эту базу, запускаем на ES2либо i3-instance c NMVe, либо обычный instance, который с EBS работаем, но делаем stripe.
Какие есть еще ограничения у Kubernetes + AWS?
У нас отключены квоты, т. е. у нас нет квот. Иначе это било очень больно. Когда мы указываем SP-memory limit и request в 100 милли…, и вдруг у тебя контейнер превышает эти 100, то начинается жесткий троттлинг по 10 миллисекунд. Это совсем плохо. Т. е. когда у тебя запросы по миллисекунде отвечали, а потом вдруг по 101, то — это ужасно. Поэтому у нас квоты отключены глобально. С этим живем.
Какие RPO, RTO вы бизнесу по Postgres гарантируете?
Мы не используем синхронную репликацию по той причине, что большинство вещей абсолютно синхронные и прилетают из Kafka. Время восстановления зависит от размера кластера и от его активности. Это надо понимать. Чтобы восстановить один терабайт и накатить волы за сутки, это может занять много часов.
И данные можете потерять, соответственно.
Данные теряются, как правило, 1–2 wal-сегмента последних, если вообще плохо. Репликация у нас не отстает, как правило.
1–2 сегмента, если маленькая нагрузка, то это может быть и полдня.
Да, если нагрузки нет, то сегменты могут не ротироваться вообще, т. е. если нет транзакций даже по тайм-ауту.
Там же автоматически можно поставить?
По тайм-ауту он должен, но, если нет транзакций, они не ротируются. Я недавно с этим разбирался.