Содержание

За последние пару лет я помогал десяткам пользователей и организаций строить пайплайны захвата изменений данных (Change Data Capture, CDC) для их баз данных Postgres. Один из ключевых вопросов в этом процессе — настройка и управление replication slots. Это механизм Postgres, который гарантирует, что сегменты журнала предзаписи базы данных будут сохраняться до тех пор, пока зарегистрированные потребители репликации не обработают их.

Если подойти к делу без должной аккуратности, replication slot может привести к тому, что база будет удерживать неоправданно большой объём WAL-сегментов. В этой статье я разберу лучшие практики, которые помогают предотвратить эту и другие проблемы: Heartbeats, переключение replication slots при отказе узла, мониторинг, управление публикациями Postgres и многое другое. Хотя материал в первую очередь основан на моём опыте работы с replication slots через коннектор Debezium для Postgres, эти принципы в целом универсальны и их стоит учитывать и при использовании других CDC-инструментов для Postgres, основанных на логической репликации.

Используйте модуль вывода логического декодирования 

Postgres использует модули вывода логического декодирования для сериализации данных, отправляемых клиентам логической репликации. При создании replication slot нужно указать, какой именно модуль использовать. Вариантов несколько, но я рекомендую pgoutput — это стандартный модуль декодирования, который также используется для логической репликации между серверами Postgres. У него есть несколько преимуществ:

  • pgoutput доступен «из коробки» в Postgres 10+ (в том числе в управляемых сервисах AWS, GCP и Azure) и не требует дополнительной установки

  • По сравнению с другими модулями, которые используют JSON в качестве формата сериализации, pgoutput использует эффективный двоичный формат сообщений репликации Postgres.

  • Он даёт тонкую настройку того, какие таблицы, столбцы и строки будут реплицироваться (подробнее об этом ниже)

При использовании CDC-инструментов вроде Debezium replication slot обычно создаётся автоматически. Чтобы вручную создать slot с использованием модуля pgoutput, вызовите функцию pg_create_logical_replication_slot() следующим образом:

SELECT * FROM pg_create_logical_replication_slot('my_slot', 'pgoutput');

+-----------+-----------+
| slot_name | lsn       |
|-----------+-----------|
| my_slot   | 0/15AF761 |
+-----------+-----------+

Возвращаемое значение LSN (log sequence number) — это 64-битный указатель, который однозначно определяет позицию в WAL: первая часть указывает на WAL-сегмент, вторая — на смещение внутри этого сегмента. Потребитель, подписанный на этот слот, будет получать события изменений, начиная с этого LSN.

В отличие от текстовых модулей декодирования, таких как test_decoding, pgoutput выдаёт данные в двоичном формате. Это означает, что вы не сможете просматривать сообщения для replication slot с помощью функций Postgres вроде pg_logical_slot_peek_changes(). Если вы хотите быстро посмотреть сообщения, которые формирует pgoutput, из командной строки, не разворачивая полноценное CDC-решение вроде Debezium, можно воспользоваться моим инструментом pgoutput-cli:

docker run -it --rm --network my-network
  gunnarmorling/pgoutput-cli \
  pgoutput-cli --host=postgres --port=5432 \
    --database=inventorydb --user=dbz_user \
    --password=kusnyf-maczuz-7qabnA \
  --publication=dbz_publication --slot=my_slot
{
  "op": "I",
  "message_id": "b14952a5-4033-4252-a33c-6039f700e9db",
  "lsn": 26691112,
  "transaction": {
    "tx_id": 755,
    "begin_lsn": 26691432,
    "commit_ts": "2025-07-02T12:09:05.854104Z"
  },
  "table_schema": {
    "column_definitions": [
      {
        "name": "id",
        "part_of_pkey": true,
        "type_id": 23,
        "type_name": "integer",
        "optional": false
      },
      ...
    ],
    "db": "inventorydb",
    "schema_name": "inventory",
    "table": "customers",
    "relation_id": 16391
  },
  "before": null,
  "after": {
    "id": 1028,
    "first_name": "Sarah",
    "last_name": "O'Brian",
    "email": "sarah@example.com",
    "is_test_account": "f"
  }
}

Задайте максимальный размер replication slot

Протоколы репликации, такие как в Postgres, требуют баланса между двумя задачами: с одной стороны, журналы транзакций должны храниться достаточно долго, чтобы потребители могли догнать источник после простоя, например во время обновления; с другой — эти журналы не должны занимать неоправданно много места на диске сервера базы данных.

Разные системы по-разному решают этот компромисс. Например, в MySQL можно задать максимальное время хранения binlog; при этом ответственность за то, чтобы успеть обработать все события из binlog, лежит на потребителях. В Postgres ситуация иная: replication slots требуют явного подтверждения от потребителя, чтобы уже обработанные WAL-сегменты можно было удалить. База данных будет удерживать все WAL-сегменты столько, сколько нужно, чтобы все потребители репликации успели обработать данные. Исторически это означало, что replication slot мог привести к неограниченному накоплению отставания по WAL, если потребитель переставал обрабатывать этот слот. Без вмешательства это потенциально могло привести к исчерпанию дискового пространства на сервере базы данных.

К счастью, начиная с Postgres 13 ситуация изменилась: появилась возможность ограничить максимальный объём WAL, который может удерживать replication slot. Для этого укажите параметр max_slot_wal_keep_size в файле postgresql.conf:

max_slot_wal_keep_size=50GB

Если разница между restart LSN слота и текущим LSN превышает этот предел, база данных сделает replication slot недействительным и удалит старые WAL-сегменты. После этого слот становится непригодным к использованию, а значит, нужно создать новый. При работе с Debezium это обычно также означает повторное снятие начального снимка данных. Для потребителей событий это, конечно, неудобно, но всё же гораздо лучше, чем ситуация, при которой рабочая база данных в итоге останется без свободного места на диске.

Включите Heartbeats

Одна из ситуаций, особенно подверженных непреднамеренному росту WAL, — это размещение на одном хосте Postgres нескольких логических баз данных с разным характером нагрузки. Причина в том, что WAL общий для всего экземпляра Postgres, тогда как replication slots привязаны к конкретным базам данных. Представим ситуацию: в одной базе выполняется много транзакций, из-за чего в WAL постоянно добавляются новые записи, а другая база при этом простаивает. Replication slot для второй базы не может продвинуться вперёд, потому что не получает никаких событий изменений, и в результате всё больше WAL-сегментов продолжает удерживаться.

Решение здесь — сгенерировать немного «искусственного» трафика во второй базе, чтобы её replication slot мог продвигаться. Исторически для этого часто использовали отдельную heartbeat-таблицу. Однако в Postgres 14+ появился более элегантный вариант, не требующий таблиц: с помощью функции pg_logical_emit_message() можно записывать в WAL произвольное содержимое, не сохраняя его ни в одной таблице. У таких сообщений логического декодирования есть несколько интересных применений, и одно из них — продвижение replication slots в базах с низкой активностью. Чтобы включить heartbeat-сообщения в Debezium, добавьте в конфигурацию коннектора следующее:

{
  ...
  "heartbeat.interval.ms" : "60000",
  "heartbeat.action.query" : "SELECT pg_logical_emit_message(false, 'heartbeat', now()::varchar)",
  ...
}

Коннектор выполняет этот запрос каждые 60 секунд, записывая в WAL сообщение логического декодирования с текущей временной меткой. Затем он получает это сообщение через логическую репликацию, что позволяет соответствующему slot продвигаться вперёд. Обратите внимание: пользователю базы данных, под которым Debezium подключается к Postgres, должно быть выдано право EXECUTE на эту функцию:

GRANT EXECUTE ON FUNCTION pg_logical_emit_message(transactional boolean, prefix text, content text)
TO <debezium_user>;

Используйте публикации на уровне таблиц

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

Модуль pgoutput опирается на публикации Postgres, которые определяют, какие именно изменения должны публиковаться через логическую репликацию. Чтобы создать публикацию на уровне таблиц, укажите все таблицы, изменения по которым должны публиковаться:

CREATE PUBLICATION mypublication FOR TABLE customers, purchase_orders;

Если в вашей базе несколько схем, но вам нужно захватывать изменения только по таблицам в одной конкретной схеме, публикацию можно создать так:

CREATE PUBLICATION mypublication FOR TABLES IN SCHEMA inventory;

При использовании Debezium публикации могут создаваться автоматически. По умолчанию он создаёт публикацию FOR ALL TABLES. Однако для этого нужны права суперпользователя, и, кроме того, такой подход может приводить к передаче событий по таблицам, которые вы всё равно потом отфильтруете в коннекторе.

В качестве альтернативы можно настроить Debezium так, чтобы он создавал публикации на уровне таблиц, задав для параметра коннектора publication.autocreate.mode значение filtered. В этом случае Debezium создаст публикацию, соответствующую набору захватываемых таблиц, определённому через фильтры включения и исключения таблиц и схем в конфигурации коннектора. Обратите внимание, что для этого пользователю коннектора нужны права владельца на все затронутые таблицы.

Поэтому, если вы придерживаетесь принципа минимально необходимых привилегий, стоит рассмотреть вариант ручного создания публикации для коннектора. Это позволит сократить набор прав, которые нужно выдавать пользователю коннектора. По умолчанию ожидается, что публикация будет называться "dbz_publication", но это имя можно переопределить через свойство коннектора publication.name. Если вы настраиваете несколько коннекторов для захвата разных наборов таблиц в одной и той же базе данных, для каждого коннектора нужно создать отдельную публикацию.

Используйте фильтры по столбцам и строкам

Начиная с Postgres 15 публикации позволяют ещё сильнее сузить содержимое потока репликации. С помощью списков столбцов можно указать, какие именно столбцы таблицы должны публиковаться. Это может быть очень полезно, если нужно исключить крупные столбцы, например двоичные данные, которые не нужны в конкретном сценарии использования. К сожалению, исключить один конкретный столбец нельзя. Вместо этого при создании публикации нужно явно перечислить имена всех столбцов, которые должны захватываться:

CREATE PUBLICATION mypublication
    FOR TABLE customers (id, first_name, last_name);

Если эта публикация используется через Debezium, убедитесь, что список столбцов в коннекторе, заданный через параметры column.include.list и column.exclude.list, соответствует списку столбцов в публикации.

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

CREATE PUBLICATION mypublication
    FOR TABLE customers WHERE (is_test_account IS FALSE);

Фильтры по строкам могут быть очень полезны, если нужно исключить из репликации часть рабочего набора данных, например тестовые данные или данные, помеченные как логически удалённые. 

Если вы используете в Debezium функцию снятия снимка (snapshotting), при которой строки извлекаются не через логическую репликацию, а путём сканирования самих таблиц в базе данных, то для согласованности между начальным снимком и потоком событий нужно указать то же самое выражение фильтра через параметр snapshot.select.statement.overrides.

Включите отказоустойчивые replication slots

Долгое время одним из слабых мест логической репликации в Postgres было отсутствие поддержки переключения при отказе (failover). Ещё сравнительно недавно replication slots можно было создавать только на первичных экземплярах. Если у вас был кластер Postgres, состоящий из основного сервера и реплики для чтения, то после отказа и перевода реплики в роль основного сервера логическую репликацию нельзя было просто продолжить с этой реплики. Как правило, приходилось создавать новый replication slot, а это также означало запуск нового начального снимка, если до создания нового slot на новом основном сервере уже успели произойти записи.

К счастью, в последних версиях Postgres эта проблема наконец начала решаться. В Postgres 16 появилась поддержка создания replication slots на репликах. Хотя само по себе это ещё не решает проблему failover полностью, это очень важное улучшение: оно позволяет иметь slots и на основном сервере, и на резервном, а затем вручную поддерживать их в синхронном состоянии. Для этого нужно отслеживать прогресс slot на основном сервере и соответствующим образом продвигать slot на резервном с помощью функции pg_replication_slot_advance()

В Postgres 17 наконец появилась полноценная поддержка отказоустойчивых slots. Теперь система может автоматически синхронизировать состояние replication slot на резервном сервере с соответствующим slot на основном без какого-либо ручного вмешательства. После failover потребители могут продолжить чтение из slot на новом основном сервере без потери событий и без большого количества дубликатов. Некоторое количество дубликатов всё же возможно в ситуации, когда потребитель уже успел получить события из slot на основном сервере, а failover произошёл до того, как состояние slot успело соответствующим образом синхронизироваться на сервере-реплике. Чтобы включить failover slots, потребуется выполнить несколько настроек.

На основном сервере:

  • Передайте failover=true при вызове pg_create_logical_replication_slot() для создания replication slot на основном сервере; в Debezium 3.0.5 и новее можно поручить Debezium создание отказоустойчивого slot, установив для параметра коннектора slot.failover значение true

  • Установите для параметра synchronized_standby_slots имя физического slot, который соединяет основной и резервный сервер; это гарантирует, что ни один slot логической репликации не сможет продвинуться дальше последнего LSN, синхронизированного с основного сервера на реплику

А на резервном сервере:

  • Установите параметр sync_replication_slots в значение on; это запустит рабочий процесс, который будет автоматически синхронизировать состояние всех slots логической репликации с основного сервера на резервный; в качестве альтернативы для синхронизации состояния slot можно вручную вызвать функцию pg_sync_replication_slots()

  • Добавьте имя базы данных slot в строку подключения, используемую для соединения с основным сервером (primary_conninfo), например: …dbname=inventorydb; если вы используете Postgres в Amazon RDS, укажите имя базы данных через параметр rds.logical_slot_sync_dbname

  • Установите параметр hot_standby_feedback в значение true

Если вы подключаетесь к Postgres через прокси, то перевод реплики в роль основного сервера можно сделать полностью прозрачным для потребителей репликации, таких как Debezium, и они смогут без разрывов продолжить обработку событий изменений после failover. 

Подумайте об использовании Replica Identity FULL

В Postgres replica identity таблицы определяет, какие поля строки будут записываться в WAL как старое состояние строки для событий UPDATE и DELETE. По умолчанию старые значения сохраняются только для первичных столбцов. Кроме того, значения TOAST-столбцов попадают в новое состояние строки только в том случае, если они изменились.

Из-за этих особенностей обработка событий изменений для потребителей может становиться более сложной. При инкрементной потоковой обработке потока событий изменений отсутствие старого состояния строки, то есть части before в событиях Debezium, требует дорогостоящей материализации состояния. А поскольку значения неизменённых TOAST-столбцов отсутствуют в событиях UPDATE и Debezium представляет их специальным значением __debezium_unavailable_value, потребители не могут применить такое событие изменений с помощью простой семантики upsert. 

Чтобы избежать этих проблем, стоит рассмотреть изменение replica identity ваших таблиц с DEFAULT на FULL:

ALTER TABLE inventory.customers REPLICA IDENTITY FULL;

В этом случае в WAL будет записываться полное старое и новое состояние строки, включая TOAST-столбцы, и, соответственно, эти данные будут доступны в событиях изменения данных. Некоторые администраторы Postgres опасаются возможного влияния такого режима на использование диска и потребление процессора. Однако на практике эти накладные расходы во многих случаях вполне приемлемы. Конкретные цифры зависят от вашего профиля нагрузки, поэтому точное влияние лучше оценивать с помощью собственных нагрузочных испытаний. Но, например, в одной из статей упоминается умеренное увеличение пикового потребления CPU с 30% до 35% после включения replica identity FULL. Во многих случаях это вполне допустимо, а взамен можно существенно упростить потребление и обработку потоков событий изменений.

Мониторинг, мониторинг и ещё раз мониторинг!

Глубокая наблюдаемость — ключевое условие для успешной эксплуатации систем данных в продакшене. При работе с Postgres стоит обязательно настроить мониторинг и оповещения для replication slots, чтобы они никогда не начинали удерживать неоправданно большие объёмы WAL. С помощью таких средств наблюдаемости, как Prometheus и Grafana, Datadog, Elastic и им подобных, нужно постоянно отслеживать следующие метрики:

  • Общий размер WAL

  • Объём WAL, удерживаемый каждым replication slot

  • Оставшийся допустимый объём WAL для каждого replication slot

  • Состояние каждого replication slot (active/inactive/invalid)

Чтобы получить общий размер WAL, можно просуммировать размеры всех файлов, возвращаемых функцией pg_ls_waldir(). Метрики, относящиеся к конкретным slots, можно получить из представления pg_replication_slots, например так:

SELECT
  slot_name,
  plugin,
  database,
  restart_lsn,
  CASE
    WHEN invalidation_reason IS NOT NULL THEN 'invalid'
    ELSE
      CASE
        WHEN active IS TRUE THEN 'active'
        ELSE 'inactive'
      END
    END as "status",
  pg_size_pretty(
    pg_wal_lsn_diff(
      pg_current_wal_lsn(), restart_lsn)) AS "retained_wal",
  pg_size_pretty(safe_wal_size) AS "safe_wal_size"
FROM
  pg_replication_slots
ORDER BY slot_name;
+----------------+----------+-------------+-------------+----------+--------------+---------------+
| slot_name      | plugin   | database    | restart_lsn | status   | retained_wal | safe_wal_size |
|----------------+----------+-------------+-------------+----------+--------------+---------------|
| logical_slot_1 | pgoutput | inventorydb | 0/1983A40   | inactive | 2386 MB      | 48 GB         |
| logical_slot_2 | pgoutput | inventorydb | 0/96BFA970  | active   | 3920 bytes   | 50 GB         |
+----------------+----------+-------------+-------------+----------+--------------+---------------+

Объём удерживаемого WAL можно вычислить как разницу между restart LSN slot, то есть самым ранним LSN, который он ещё удерживает, и текущим LSN базы данных. Поле safe_wal_size в этом представлении показывает, сколько байт slot ещё может дополнительно удержать до достижения лимита, заданного через max_slot_wal_keep_size, о котором говорилось выше.

Все эти метрики можно довольно легко получать из экземпляра Postgres с помощью проекта postgres_exporter, который предоставляет конечную точку, совместимую с Prometheus. Кроме того, имеет смысл отслеживать и оставшееся свободное место на диске или томе, где хранится WAL. Сам Postgres это значение не предоставляет, поэтому его придётся получать из операционной системы, оркестратора задач (job orchestrator), например Kubernetes, или от облачного провайдера, если Postgres работает в сервисе вроде Amazon RDS. И наконец, рекомендуется следить за метрикой MilliSecondsBehindSource, которую Debezium предоставляет для каждого экземпляра коннектора. Она показывает, сколько времени проходит с момента внесения изменения в базу данных до момента, когда это событие начинает обрабатываться Debezium. Метрики Debezium публикуются через JMX; с помощью компонента jmx_exporter из Prometheus их можно отдавать по HTTP в формате, совместимом с Prometheus.

В качестве отправной точки для собственной системы наблюдаемости за slots логической репликации Postgres можно использовать панель Grafana, в которой отображается большинство этих метрик здесь:

mastering postgres replication slots dashboard

Пример показывает результаты 30-минутного запуска pgbench с 20 соединениями и четырьмя потоками на каждое. В системе работают три коннектора Debezium с соответствующими slots логической репликации. Replication slot 1 показывает постоянный уровень удержания WAL, потому что этот коннектор непрерывно работает и постоянно выдаёт события. Коннектор, которому принадлежит slot 2, останавливается на несколько минут в середине теста, что видно по красным столбцам на панели состояния активности. В это время накопленное отставание по WAL для этого slot растёт, но затем снова сокращается, когда коннектор после перезапуска догоняет источник. Slot 3, напротив, всё время удерживает всё больше и больше WAL. Причина в том, что этот slot настроен на другую базу данных на том же хосте Postgres, и в этой базе не происходит никаких изменений. Поэтому Debezium так и не получает возможности подтвердить продвижение этого slot. Как уже обсуждалось выше, решить эту проблему помогают heartbeat-события.

Что делать, если активный потребитель репликации не успевает обрабатывать изменения в исходной базе данных, и его задержка репликации постоянно растёт? Я планирую отдельно написать статью о настройке производительности коннектора Debezium для Postgres, но одним из решений может быть использование нескольких replication slots, каждый из которых экспортирует изменения для отдельного подмножества таблиц. Это позволит распределить нагрузку по обработке между несколькими процессами, работающими на разных машинах. Для этого можно скопировать существующий replication slot с помощью pg_copy_logical_replication_slot(). Тогда второй коннектор сможет продолжить обработку с того же LSN, что и исходный slot.

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

  • Использование диска превышает 60–70%

  • Replication slot неактивен дольше 30 минут

  • Replication slot удерживает более 10–20 ГБ WAL-данных

Во многих случаях интересны не столько сами абсолютные значения, сколько их первая производная, то есть изменение этих значений во времени. Именно такие изменения тоже стоит использовать для алертов, например если использование диска быстро растёт или если объём WAL, удерживаемый replication slot, медленно, но стабильно увеличивается на протяжении длительного времени.

Если в вашей базе данных Postgres выполняются крупные и длительные транзакции, это может привести к тому, что при декодировании содержимого WAL логическая репликация будет сбрасывать состояние на диск. Это увеличивает нагрузку на операции ввода-вывода (I/O) и замедляет процесс репликации.

В Postgres 14 и новее вы можете проанализировать объём такого сброса на диск для replication slot, обратившись к представлению pg_stat_replication_slots:

SELECT
  slot_name,
  total_txns,
  spill_txns,
  pg_size_pretty(spill_bytes) as spilled,
  pg_size_pretty(total_bytes) as total
FROM pg_stat_replication_slots;

+-----------+------------+------------+---------+--------+
| slot_name | total_txns | spill_txns | spilled | total  |
|-----------+------------+------------+---------+--------|
| my_slot   | 3          | 1          | 66 MB   | 122 MB |
+-----------+------------+------------+---------+--------+

Если вы наблюдаете чрезмерно большой объём сброса данных на диск, имеет смысл увеличить параметр logical_decoding_work_mem (по умолчанию — 64 МБ).

Удаляйте неиспользуемые replication slots

И напоследок — рекомендация по «гигиене»: не забывайте удалять неиспользуемые replication slots. В частности, при остановке и удалении коннектора Debezium соответствующий replication slot в Postgres автоматически не удаляется. Если этот слот больше не используется другими коннекторами или потребителями репликации, его следует удалить, чтобы он не мешал очистке сегментов WAL. Для этого вызовите функцию pg_drop_replication_slot() следующим образом:

SELECT pg_drop_replication_slot('my_replication slot');

После выхода Postgres 18 (релиз был запланирован на сентябрь 2025 года) для этих задач станет полезен новый параметр idle_replication_slot_timeout. Это временной аналог ранее упомянутого параметра max_slot_wal_keep_size: он позволяет помечать replication slots как недействительные после заданного периода неактивности. Установка значения, например, 48 или 72 часа поможет гарантировать, что неактивные слоты будут своевременно инвалидированы и не будут удерживать всё больше WAL-сегментов.

Подведем итоги

Слоты логической репликации — это ключевой элемент при построении пайплайнов захвата изменений данных (CDC) в Postgres. Хотя опасения по поводу возможного разрастания WAL иногда вызывают у пользователей тревогу, на практике при корректной настройке replication slots эти риски в значительной степени преувеличены.

Грамотно настраивая такие параметры, как максимальный размер слота, детализированные публикации и heartbeat-сообщения, вы можете обеспечить стабильность и производительность как самой базы данных Postgres, так и ваших CDC-пайплайнов. Отказоустойчивые слоты, поддерживаемые начиная с Postgres 17, позволяют безболезненно продолжить репликацию после перевода резервного сервера в роль основного. И, наконец, важно внедрить полноценный мониторинг и систему оповещений, чтобы быть уверенными, что replication slots работают корректно. Показанный выше дашборд Grafana может служить хорошей отправной точкой; его можно найти в моём репозитории streaming-examples на GitHub. Буду рад любым улучшениям и доработкам этого дашборда.

-15% за прохождение входного теста по Postgres [до конца апреля]
-15% за прохождение входного теста по Postgres [до конца апреля]

Если вам важно не просто знать, где что подкрутить в Postgres, а уверенно работать с репликацией, бэкапами, мониторингом, производительностью, Kubernetes и облачной инфраструктурой в реальной эксплуатации, курс «Администрирование PostgreSQL. Экспертный уровень» помогает собрать это в системную практику, чтобы не тушить проблемы по одной, а держать PostgreSQL-инфраструктуру под контролем.

Начать можно с бесплатного демо-урока 9 апреля в 20:00 «PostgreSQL как векторная база данных: ИИ‑поиск без лишних сервисов». Узнаете больше о формате обучения, сможете задать вопросы преподавателю. Записаться на урок.