Направление запросов чтения в реплику

AWSBeginner
Практиковаться сейчас

Введение

Приложение направляет чтение и запись в одну основную базу. Вы создадите реплику PostgreSQL, перенаправите чтение и докажете, что новые записи основного экземпляра становятся доступны на реплике, а запись в неё отклоняется.

Это необязательное расширение после работ о подключении, SQL и восстановлении. Среда начинается независимо с основной базой и тремя заказами. В конце вы удалите реплику и основной экземпляр.

Связь с сертификацией

Эта работа даёт начальную практику по следующим темам экзаменов.

Создание и проверка реплики чтения

На этом этапе вы создадите второй PostgreSQL, принимающий изменения основного экземпляра.

Реплика чтения отвечает на запросы из скопированных данных. PostgreSQL передаёт изменения через журнал предзаписи — WAL. Резервный сервер воспроизводит изменения. Это отдельный движок, и репликация может отставать от только что подтверждённой записи.

Реплика чтения и резервный экземпляр Multi-AZ имеют разные цели. Реплика предоставляет endpoint для запросов приложения. Резервный сервер стандартного развёртывания DB-экземпляра Multi-AZ обеспечивает доступность и переключение при отказе, но не обслуживает чтение приложения. Здесь реализована реплика, а не переключение Multi-AZ.

Загрузите настройки подключения:

cd /home/labex/project
source database.env

Проверьте источник:

aws rds \
  describe-db-instances \
  --db-instance-identifier orders-db \
  --query 'DBInstances[0].{Identifier:DBInstanceIdentifier,Status:DBInstanceStatus,BackupRetention:BackupRetentionPeriod,Endpoint:Endpoint}'

RDS требует ненулевой срок хранения резервных копий для источника реплики. Этот модуль предоставляет основную базу со сроком 1. Вы используете готовую конфигурацию; планирование автоматических копий не проверяется.

Создайте реплику:

aws rds \
  create-db-instance-read-replica \
  --db-instance-identifier orders-reader \
  --source-db-instance-identifier orders-db \
  --db-instance-class db.t3.micro \
  --no-publicly-accessible \
  --query 'DBInstance.{Identifier:DBInstanceIdentifier,Source:ReadReplicaSourceDBInstanceIdentifier,Status:DBInstanceStatus,Endpoint:Endpoint}'
aws rds \
  wait db-instance-available \
  --db-instance-identifier orders-reader

Проверьте оба ресурса:

aws rds \
  describe-db-instances \
  --query 'DBInstances[].{Identifier:DBInstanceIdentifier,Source:ReadReplicaSourceDBInstanceIdentifier,Endpoint:Endpoint.Address}' \
  --output table

Ожидаются отдельный endpoint orders-reader и связь с источником orders-db. AWS View должен показать основную базу и реплику.

Запросите настоящий движок реплики:

psql \
  "service=orders-reader" \
  --command 'SELECT current_database(), pg_is_in_recovery();'

Ожидаются база orders и pg_is_in_recovery равное t (true). PostgreSQL действительно работает в режиме восстановления резервного сервера, а не просто имеет метку реплики в RDS.

Разделение endpoints чтения и записи

На этом этапе вы оставите запись на основном экземпляре, а чтение направите в реплику.

Запись идёт в основную БД; чтение может использовать асинхронно обновляемую реплику

Концептуальная схема: Запись идёт в основную БД; чтение может использовать асинхронно обновляемую реплику.

Проверьте конфигурацию приложения:

cat app-config.json

Оба хоста указывают на основную базу. Получите endpoint реплики:

READER_ENDPOINT=$(aws rds \
  describe-db-instances \
  --db-instance-identifier orders-reader \
  --query 'DBInstances[0].Endpoint.Address' \
  --output text)

Измените только read_host:

jq \
  --arg host "$READER_ENDPOINT" \
  '.read_host = $host' \
  app-config.json > app-config.new
mv app-config.new app-config.json

Прочитайте данные через приложение:

curl \
  --silent \
  --show-error \
  --fail \
  http://127.0.0.1:8080/application/orders | jq

Ожидаются три исходных заказа и read_only: true. Ответ приходит из реального SQL-подключения к резервному серверу. AWS View должен определить источник данных приложения как реплику чтения.

Реплика использует учётные записи базы, скопированные из источника. Выбор endpoint определяет экземпляр назначения; аутентификация и SQL-права по-прежнему действуют.

Наблюдение репликации и отказа записи

На этом этапе вы сохраните новый заказ через основную базу и прочитаете его из реплики.

Добавьте заказ через приложение. Его обработчик POST использует write_host, всё ещё указывающий на основную базу:

curl \
  --silent \
  --show-error \
  --fail \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{"order_id":104,"customer":"Kai","total":"5.00"}' \
  http://127.0.0.1:8080/application/orders

Ожидается saved: true. Запросите источник и реплику независимо:

psql \
  "service=orders-db" \
  --command 'SELECT order_id, customer, total FROM orders WHERE order_id = 104;'
psql \
  "service=orders-reader" \
  --command 'SELECT order_id, customer, total FROM orders WHERE order_id = 104;'

Ожидается заказ Kai на 5.00 на обоих. Если первый запрос реплики ещё не видит строку, повторите его через некоторое время: асинхронная репликация допускает задержку. Реплика полезна для частого чтения, но немедленная согласованность может требовать чтения только что записанного значения из основной базы.

Прочитайте список приложения снова:

curl \
  --silent \
  --show-error \
  --fail \
  http://127.0.0.1:8080/application/orders | jq

Ожидаются четыре заказа и read_only: true.

Попробуйте выполнить настоящую вставку в реплику:

psql \
  "service=orders-reader" \
  --set=ON_ERROR_STOP=1 \
  --command "INSERT INTO orders (order_id, customer, total) VALUES (105, 'Lena', 9.00);"

Ожидается ошибка о невозможности INSERT в транзакции только для чтения. Режим восстановления отвергает запись даже для главного пользователя. Убедитесь, что попытка не создала заказ в любом из движков:

psql \
  "service=orders-db" \
  --command 'SELECT count(*) FROM orders WHERE order_id = 105;'
psql \
  "service=orders-reader" \
  --command 'SELECT count(*) FROM orders WHERE order_id = 105;'

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

Удаление реплики и основной базы

На этом этапе вы удалите два учебных экземпляра.

Сначала удалите реплику, затем источник:

aws rds \
  delete-db-instance \
  --db-instance-identifier orders-reader \
  --skip-final-snapshot \
  --query 'DBInstance.DBInstanceIdentifier'
aws rds \
  wait db-instance-deleted \
  --db-instance-identifier orders-reader
aws rds \
  delete-db-instance \
  --db-instance-identifier orders-db \
  --skip-final-snapshot \
  --query 'DBInstance.DBInstanceIdentifier'
aws rds \
  wait db-instance-deleted \
  --db-instance-identifier orders-db
aws rds \
  describe-db-instances \
  --query 'DBInstances[].DBInstanceIdentifier'

Ожидаются [] и отсутствие баз в AWS View. Сохраните приложение и подготовленные сетевые ресурсы.

Итоги

Вы создали реплику PostgreSQL и проверили настоящий режим восстановления. Разделили endpoints чтения и записи, увидели новую запись основного экземпляра в реплике и подтвердили отказ прямой записи. Затем удалили оба экземпляра.

Реплика поддерживает чтение; её цель и поведение отличаются от резервного сервера стандартного развёртывания DB-экземпляра Multi-AZ. Финальное задание применяет изученные навыки подключения и диагностики.