powered by simpleCommunicator - 2.0.52     © 2025 Programmizd 02
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / Согласованность данных на репликах при потоковой репликации
12 сообщений из 12, страница 1 из 1
Согласованность данных на репликах при потоковой репликации
    #39990130
alex_nur
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Добрый день. Готовим свой кластер на PostgreSQL 11.8 (PG), состоящий как минимум из 3 машин (Linux) с PG: мастера и 2 реплик, где в postgresql.conf параметр synchronous_standby_names имеет значение '*'.
Т.е. любая первая из реплик, применившая транзакцию, является синхронной.
Потенциально могут появиться другие реплики (но 100% асинхронные), которые территориально сильно удалены (в этом случае пропишем список реплик в ANY()).
Еще есть машина (FreeBSD) с NFS-каталогом для архивных WAL и резервных копий кластера и отдельных БД (pg_dump).
Имеется балансировщик haproxy (Linux), с двумя (применительно к PG) backend'ами и frontend'ами.
Первый frontend в haproxy слушает порт 5432/tcp, и все что приходит на него отправляет на backend, где в качестве сервера указан единственный адрес мастер-сервера PG и порт 5432.
Второй frontend в haproxy слушает порт 5433/tcp, и все что приходит на него отправляет на backend, где в качестве серверов указаны реплики, а также мастер-сервер, но с минимальным весом. В этом backend'е для всех серверов указан порт 5432.
Присутствие мастера в backend реплик обусловлено тем, чтобы балансировщик в случае большой сетевой нагрузки на реплики, мог отправлять запросы на чтение в том числе и мастеру.
Итого разработчики ПО имеют 2 контекста для работы с БД: один - "запись/чтение", другой - "только чтение". В connectionString обоих контекстов указан адрес балансировщика. Вся разница - в указанных портах (5432 - "запись/чтение, 5433 - "только чтение").
Разрабатываемое ПО в подавляющем большинстве случаев - это веб-приложения (ASP.NET). ORM также используется, за исключением тех моментов, где важна скорость.
В простейшем случае, если мы вносим изменения в БД из приложения, то используем контекст "запись/чтение". Если читаем - то контекст "только чтение". При таком подходе велика вероятность того, что вставив запись на мастере и прочитав список всех записей с реплики, пользователь, несмотря на честное сообщение об успешно добавленной записи, не обнаружит новой записи среди всех показанных. Т.е. мастер получил подтверждение от реплики 1, а балансировщик направил клиента к реплике 2, на которой данные еще не успели примениться (сетевые задержки, высокая загрузка CPU и т.п.).
При подготовке отчетов (fastreport) такое поведение будет являться нормальным. Но вот при редиректе со страницы добавления/изменения записи на индексную страницу (web), это является недопустимым.
Как решение - применение флага о прошлом изменении данных текущим пользователем (если изменялись, то читаем с мастера, если нет - то с одной из реплик). Т.е. метод Index в контроллере приложения анализирует необязательный параметр (флаг изменения данных), и применяет тот или иной контекст. В настоящее время это сводится к написанию одной строки в методе, а контекстом по-умолчанию является контекст "запись/чтение".
Таким образом, если разработчик в приложении забудет вызвать метод, возвращающий контекст, или задавать значения флагу (при редиректе с других веб-страниц), все будет железно работать (и запишем и прочитаем). Только вот особо забывчивые будут долбать только мастера.
Все три сервера PG установлены на 2 гипервизорах в одном помещении. Даже если разработчик просто тупо будет сразу читать данные с любой из реплик, после вставки их на мастере (даже не используя флага об изменении данных), то, скорее всего, 99.99% всех случаев не приведут к описанной ситуации, если это веб-приложение. Пока от контроллера до клиента дойдет ответ, а после запрос на индексную страницу в бэкенде, а там внутри метода запрос к СУБД... С огромной долей вероятности все будет хорошо. Но если приложение - десктопное, где на интерфейс нет никаких накладных расходов, вероятность считать не все данные - большая.
Особенно если добавляем в кластер асинхронную реплику, которая территориально удалена: территориально удаленный пользователь сделает INSERT на далеком мастере, и получит ответ быстрее или одновременно с тем, что данные только долетают от мастера до отдаленной реплики (но с которой такому клиенту гораздо выгоднее читать).
Отчего рождается вопрос: каким образом можно реализовать так, чтобы обращение к любой из реплик 100% принесло актуальную информацию? Т.е. чтобы программист знал, что он может использовать контекст "только чтение" без использования флага о том, что пользователь только что изменял данные?
Может проблема уже решена или решения не существует?
...
Рейтинг: 0 / 0
Согласованность данных на репликах при потоковой репликации
    #39990133
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alex_nur,

Вам сюда вот
https://www.postgresql.org/docs/12/runtime-config-wal.html#RUNTIME-CONFIG-WAL-SETTINGS
в районе
synchronous_commit (enum)
Specifies whether transaction commit will wait for WAL records to be written to disk before the command returns a “success” indication to the client. Valid values are on, remote_apply, remote_write, local, and off.

...
When set to remote_apply, commits will wait until replies from the current synchronous standby(s) indicate they have received the commit record of the transaction and applied it, so that it has become visible to queries on the standby (s).

издержек у такой настройки тоже не мало... потому что если какая то реплика не успевает - у вас вообще вся запись на мастере заблочится пока репликация не рассосется.

ну и очевидно что такой подход ИСКЛЮЧАЕТ использование асинхронных реплик.
...
Рейтинг: 0 / 0
Согласованность данных на репликах при потоковой репликации
    #39990145
alex_nur
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Maxim Boguk, спасибо, про это знаю.
Но, думаю очень медленно работать все будет. Особенно учитывая территориально удаленную реплику, которая задумывается как раз по той причине, что удаленным клиентам с нее выгоднее читать. Вообще, в документации по PG я не нашел ответа, за исключением нескольких синхронных реплик.
Тут скорее параметр synchronous_standby_names что-то вроде
FIRST 1 (реплика1, реплика2).
Но тогда выходит, что железно мы можем читать только с реплики 1.
В конфигурации балансировщика в бэкенде реплик должны быть указаны только реплика1 и сам мастер.
Вторая реплика, хоть и является сервером горячего резерва (wal_level = hot_standby), фактически будет использоваться только как теплого (запросов на чтение к ней не будет вообще). Если "реплика1" будет выключена, синхронной в приоритете станет "реплика2", однако в балансировщике, в бэкенде реплик реплика2 не указана. Возможно нужно крутить сам балансировщик в плане указания резервного сервера (только когда основные не доступны) в его бэкенде. Что-то такое я вроде даже делал когда-то (для другой задачи). Но тогда мастер-сервер следует удалить из бэкэнда реплик балансировщика (иначе переход на резервный, с точки зрения балансировщика, сервер никогда не наступит, т.к. доступен сам мастер).
Остается открытым вопрос с удаленной репликой. Делать ее синхронной - катастрофа как по времени подтверждения от нее, так и в случае потери связи с ней.
remote_apply уже применен. Кластер организован в одном помещении на виртуальных машинах, с быстрым каналом связи между собой, на двух гипервизорах. Думаю негативное влияние remote_apply в этом случае сильно нивелируется.
Но тогда получается, что территориально удаленная реплика - не имеет права на жизнь. Весь смысл тогда теряется.
Уже голову сломал.
...
Рейтинг: 0 / 0
Согласованность данных на репликах при потоковой репликации
    #39990148
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alex_nur

Но тогда получается, что территориально удаленная реплика - не имеет права на жизнь. Весь смысл тогда теряется.
Уже голову сломал.


Очевидно что в вашей постановке задача не решается и решаться не может просто из за законов мироздания.
Т.е. ответ на
авторОтчего рождается вопрос: каким образом можно реализовать так, чтобы обращение к любой из реплик 100% принесло актуальную информацию? Т.е. чтобы программист знал, что он может использовать контекст "только чтение" без использования флага о том, что пользователь только что изменял данные?
НИКАК

На удаленной реплике немного отстающей - можно считать отчеты и те вещи которые легко переживают 100-1000ms лага (а таких даже в desktop приложении где то 90% и больше обычно), но вот делить что можно читать с отстающей реплики а что нельзя - надо будет на уровне кода и только его автор может знать можно в этом месте с реплики читать или нет.
Как собственно вы и написали.
Ничего другого нет и быть не может.
...
Рейтинг: 0 / 0
Согласованность данных на репликах при потоковой репликации
    #39990205
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alex_nur
каким образом можно реализовать так, чтобы обращение к любой из реплик 100% принесло актуальную информацию?

Не использовать "только чтение" вперемешку с "запись/чтение". Как только сессия впервые использовала контекст "запись/чтение", она обречена использовать его до самого конца и уже не может вернуться к "только чтение".

Ну или кластер переводят в multimaster и "только чтение" выкидывают вообще как таковое.
...
Рейтинг: 0 / 0
Согласованность данных на репликах при потоковой репликации
    #39990213
alex_nur
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dimitry Sibiryakov

Не использовать "только чтение" вперемешку с "запись/чтение". Как только сессия впервые использовала контекст "запись/чтение", она обречена использовать его до самого конца и уже не может вернуться к "только чтение".


Если контекст уничтожается после каждого запроса (db.Dispose()), то ничего не мешает. Я не о подключении к серверу и продолжении работы на нем (Например, с помощью psql). Равно как не мешает и использование 2 контекстов одновременно (я о приложениях):

using (MyDbContext db = new MyDbContext())
using (MyDbReadonlyContext dbReadOnly = new MyDbReadonlyContext()){ }

Т.е. сессия заканчивается с запросом. Чтобы не плодить кучу коннектов можно pgbouncer использовать.
Использовать 2 контекста можно, но не совсем удобно. Придется смириться и использовать учитывая информацию о том, что делал пользователь в прошлом действии. Если сосед вносил изменения в БД, а спустя 1 сек коллега открыл эти записи и не обнаружил изменений - это не страшно, с этим можно мириться (спустя 1-10 сек изменения увидят все). Проблема в том, чтобы изменения увидел сам автор. И тут без информирования контроллера о том не изменял ли пользователь в прошлом действии данные, не обойтись.
Можно, например (сейчас придумалось) ввести приватную СТАТИЧЕСКУЮ переменную в контроллере, значение которой распространится на все экземпляры контроллера, в которую писать время когда в БД осуществлялись изменения (разумеется, отследить изменения можно только если они вносились из самого приложения, а не напрямую в БД), и видя, что программист использует вызов контекста "только чтение", а в статической переменной время менее 10 сек от текущего - подставлять вместо контекста только чтение контекст чтения с мастера. Тогда точно никто не сможет увидеть устаревших данных с реплики.
Нужно, наверное, будет организовать потокобезопасный доступ к этой статической переменной (особенно если вынести ее за пределы контроллера, и менять которую будут вообще при любых изменениях в БД в приложении (из любого его действия)), чтобы не возникло конфликтов при попытке одновременной записи в нее из нескольких параллельных потоков.
В общем, решения есть, но все они несколько костыльные.

Dimitry Sibiryakov

Ну или кластер переводят в multimaster и "только чтение" выкидывают вообще как таковое.


При таком подходе глюков и количества конфликтов будет больше чем при описанном сценарии, IMHO, конечно.
...
Рейтинг: 0 / 0
Согласованность данных на репликах при потоковой репликации
    #39990225
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alex_nur,

я могу вам подсказать одну методику как бы это можно было сделать (в production я ее не проверял но не вижу причин почему она может не работать).
если у вас у одного пользователя
1)логический блок действий оформлен в виде одной транзакции
2)нет параллельных транзакций одного пользователя в базе
то вот что можно сделать

1)перед вызом commit на МАСТЕР базе вы вызываете
txid_current_if_assigned() bigint same as txid_current() but returns null instead of assigning a new transaction ID if none is already assigned
и сохраняете ее у себя где то в контексте-сессии

2)когда вы в следующем запросе идете читать на РЕПЛИКУ вы там вызываете
txid_status(bigint) text report the status of the given transaction: committed, aborted, in progress, or null if the transaction ID is too old
и если вам говорят что эта транзакция уже committed там отмечена - значит ее реплика уже увидела и применила
и тогда вам можно безопасно идти читать с нее
если же реплика эту транзакцию еще не видит - значит надо делать fallback и читать с коннекта к мастер базе

как то так... если решите попробовать этот вариант - напишите тут что у вас с этим получилось.

Аналогично можно поиграть с парой
pg_xact_commit_timestamp(xid) timestamp with time zone get commit timestamp of a transaction
pg_last_committed_xact() xid xid, timestamp timestamp with time zone get transaction ID and commit timestamp of latest committed transaction
и
pg_last_xact_replay_timestamp() timestamp with time zone Get time stamp of last transaction replayed during recovery. This is the time at which the commit or abort WAL record for that transaction was generated on the primary. If no transactions have been replayed during recovery, this function returns NULL. Otherwise, if recovery is still in progress this will increase monotonically. If recovery has completed then this value will remain static at the value of the last transaction applied during that recovery. When the server has been started normally without recovery the function returns NULL.

но поскольку тут начинается игра со временем - это менее надежно с моей точки зрения.



--
Maxim Boguk
лучшая поддержка PostgreSQL: dataegret.ru
...
Рейтинг: 0 / 0
Согласованность данных на репликах при потоковой репликации
    #39990417
alex_nur
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Maxim Boguk
alex_nur,
1)перед вызом commit на МАСТЕР базе вы вызываете
txid_current_if_assigned() bigint same as txid_current() but returns null instead of assigning a new transaction ID if none is already assigned
и сохраняете ее у себя где то в контексте-сессии

2)когда вы в следующем запросе идете читать на РЕПЛИКУ вы там вызываете
txid_status(bigint) text report the status of the given transaction: committed, aborted, in progress, or null if the transaction ID is too old


Похоже это самое ближайшее к цели. Но использовать будет сложно. Я еще подумаю как бы это можно было упростить. Т.к. большинство запросов (95% и более) - это ORM. Если использовать ORM для вызова хранимок, то тогда весь смысл использования ORM теряется (хотя, в большинстве случаев - это правильный путь). Для чего-то тяжелого мы так и поступаем. И придется, похоже возвращать не один набор данных, а более, если сама хранимка, помимо работы по изменению в БД, должна еще что-то вернуть. Т.е. полезные данные плюс указанными вами результат, который следует сохранить в сессии. EF Core умеет вызывать хранимки PG, но не те, которые возвращают более 1 набора данных. Тогда только через dataReader.
Позже подумаю как это все можно подружить. Если что-то получится, напишу.
...
Рейтинг: 0 / 0
Согласованность данных на репликах при потоковой репликации
    #39990426
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alex_nur,

А вам не надо на orm все на хранимки перевести.
Вам надо в начале обработки пользовательского (http?) запроса проверить видит реплика последнюю закомиченную транзакцию пользователя или нет
и если видит - вызывать читающие запросы (хоть orm хоть какие) на реплике а если не видит - то на мастере
(фактически по результатам одного запроса решать какой контекст использовать для обработки дальнейшей).
...
Рейтинг: 0 / 0
Согласованность данных на репликах при потоковой репликации
    #39990539
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alex_nur
Если контекст уничтожается после каждого запроса (db.Dispose()), то ничего не мешает.

Кроме сабжа. Без разницы что происходит с контекстом после его использования. Если пользовательская сессия (в смысле физического пользователя, сидящего за компьютером) хотя бы раз что-то в базу записала, то после этого она вынуждена всегда использовать исключительно мастер. Как раз ради того чтобы видеть собой же записанные данные.
...
Рейтинг: 0 / 0
Согласованность данных на репликах при потоковой репликации
    #39990600
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dimitry Sibiryakov
alex_nur
Если контекст уничтожается после каждого запроса (db.Dispose()), то ничего не мешает.

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


совсем не обязательно... если там есть логичные этапы завершающиеся кнопкой save/сохранить - которая и производит сброс данных в базу и commit транзакции - после save если эта транзакция успела дойти в реплики - вполне себе можно с реплики и читать... до тех пор пока пользователь опять не начнет что то менять в базе открыв транзакцию в мастере (и тогда конечно опять до commit - работаем только в мастере).
А дальше все зависит от соотношения ro и rw нагрузки у пользователя в процессе его работы.
Ну и в общем если пользователь отчеты смотрит то ему мастер вообще не нужен.
...
Рейтинг: 0 / 0
Согласованность данных на репликах при потоковой репликации
    #39990783
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Maxim Boguk
если эта транзакция успела дойти в реплики

Ну так именно в это аффтар и уткнулся.

Maxim Boguk
А дальше все зависит от соотношения ro и rw нагрузки у пользователя в процессе его работы.
Ну и в общем если пользователь отчеты смотрит то ему мастер вообще не нужен.

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

PS: Хотя при правильном построении бизнес-процессов и БД конфликты мультимастера несколько преувеличены...
...
Рейтинг: 0 / 0
12 сообщений из 12, страница 1 из 1
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / Согласованность данных на репликах при потоковой репликации
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]