14.6. Настройка производительности Postgres Pro Shardman #

Ниже приведён список советов для настройки производительности распределённых систем.

14.6.1. Изучение планов #

Настраивать выполнение запросов лучше на подмножестве производственных данных, представляющем фактическое распределение данных. Посмотрите на некоторые примеры планов.

EXPLAIN VERBOSE
SELECT bid,avg(abalance) FROM pgbench_accounts
WHERE bid IN (10,20,30,40)
GROUP BY bid;
                                                                QUERY PLAN                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------
 Append  (cost=0.29..21.98 rows=4 width=36)
   ->  GroupAggregate  (cost=0.29..18.98 rows=1 width=36)
         Output: pgbench_accounts.bid, avg(pgbench_accounts.abalance)
         Group Key: pgbench_accounts.bid
         ->  Index Scan using pgbench_accounts_15_pkey on public.pgbench_accounts_15 pgbench_accounts  (cost=0.29..18.96 rows=1 width=8)
               Output: pgbench_accounts.bid, pgbench_accounts.abalance
               Index Cond: (pgbench_accounts.bid = ANY ('{10,20,30,40}'::integer[]))
   ->  Async Foreign Scan  (cost=0.99..0.99 rows=1 width=36)
         Output: pgbench_accounts_1.bid, (avg(pgbench_accounts_1.abalance))
         Relations: Aggregate on (public.pgbench_accounts_16_fdw pgbench_accounts_1)
         Remote SQL: SELECT bid, avg(abalance) FROM public.pgbench_accounts_16 WHERE ((bid = ANY ('{10,20,30,40}'::integer[]))) GROUP BY 1
         Transport: Silk
   ->  Async Foreign Scan  (cost=0.99..0.99 rows=1 width=36)
         Output: pgbench_accounts_2.bid, (avg(pgbench_accounts_2.abalance))
         Relations: Aggregate on (public.pgbench_accounts_17_fdw pgbench_accounts_2)
         Remote SQL: SELECT bid, avg(abalance) FROM public.pgbench_accounts_17 WHERE ((bid = ANY ('{10,20,30,40}'::integer[]))) GROUP BY 1
         Transport: Silk
   ->  Async Foreign Scan  (cost=1.00..1.00 rows=1 width=36)
         Output: pgbench_accounts_3.bid, (avg(pgbench_accounts_3.abalance))
         Relations: Aggregate on (public.pgbench_accounts_19_fdw pgbench_accounts_3)
         Remote SQL: SELECT bid, avg(abalance) FROM public.pgbench_accounts_19 WHERE ((bid = ANY ('{10,20,30,40}'::integer[]))) GROUP BY 1
         Transport: Silk
 Query Identifier: -1714706980364121548

Здесь видно, что запросы, сканирующие три секции, будут отправляться на другие узлы, данные координатора также будут сканироваться с использованием Index Scan. Неизвестно, какой план будет использоваться на удалённой стороне, но видно, какие запросы будут отправлены (отмечены как Remote SQL). Обратите внимание, что секция Transport: Silk присутствует в описании стороннего сканирования. Это означает, что для передачи результатов будет использоваться транспорт Silk. Видно, что будет использоваться стороннее сканирование Async, это нормально. Чтобы узнать, какие серверы используются в запросе, нужно посмотреть определения сторонних таблиц. Например, можно узнать, что public.pgbench_accounts_19_fdw находится на сервере shardman_rg_2 и прослушивает 127.0.0.2:65432:

SELECT srvname,srvoptions FROM pg_foreign_server s JOIN pg_foreign_table ON ftserver = s.oid
WHERE ftrelid = 'public.pgbench_accounts_19_fdw'::regclass;
-[ RECORD 1 ]-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
srvname    | shardman_rg_2
srvoptions | {async_capable=on,batch_size=100,binary_format=on,connect_timeout=5,dbname=postgres,extended_features=on,fdw_tuple_cost=0.2,fetch_size=50000,host=127.0.0.2,port=65432,silk_port=8000,tcp_user_timeout=10000}

Теперь можно подключиться к серверу shardman_rg_2 и узнать, какой план используется для локального запроса, показанного выше в EXPLAIN:

EXPLAIN SELECT bid, avg(abalance)
FROM public.pgbench_accounts_19
WHERE ((bid = ANY ('{10,20,30,40}'::integer[]))) GROUP BY 1;

                                   QUERY PLAN                                    
---------------------------------------------------------------------------------
 HashAggregate  (cost=3641.00..3641.01 rows=1 width=36)
   Group Key: bid
   ->  Seq Scan on pgbench_accounts_19  (cost=0.00..3141.00 rows=100000 width=8)
         Filter: (bid = ANY ('{10,20,30,40}'::integer[]))

При рассмотрении планов распределённых запросов видно, что иногда агрегатные функции не проталкиваются:

EXPLAIN VERBOSE
SELECT avg(abalance) FROM pgbench_accounts;
                                                                                    QUERY PLAN                                                                                     
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=156209.38..156209.39 rows=1 width=32) (actual time=590.359..590.371 rows=1 loops=1)
   Output: avg(pgbench_accounts.abalance)
   ->  Append  (cost=2891.00..156209.33 rows=20 width=32) (actual time=56.815..590.341 rows=20 loops=1)
         ->  Partial Aggregate  (cost=2891.00..2891.01 rows=1 width=32) (actual time=56.812..56.813 rows=1 loops=1)
               Output: PARTIAL avg(pgbench_accounts.abalance)
               ->  Seq Scan on public.pgbench_accounts_0 pgbench_accounts  (cost=0.00..2641.00 rows=100000 width=4) (actual time=0.018..38.478 rows=100000 loops=1)
                     Output: pgbench_accounts.abalance
         ->  Partial Aggregate  (cost=23991.00..23991.01 rows=1 width=32) (actual time=75.133..75.134 rows=1 loops=1)
               Output: PARTIAL avg(pgbench_accounts_1.abalance)
               ->  Foreign Scan on public.pgbench_accounts_1_fdw pgbench_accounts_1  (cost=100.00..23741.00 rows=100000 width=4) (actual time=41.281..67.293 rows=100000 loops=1)
                     Output: pgbench_accounts_1.abalance
                     Remote SQL: SELECT abalance FROM public.pgbench_accounts_1
                     Transport: Silk
.....

Здесь avg() вычисляется на стороне координатора. Это может привести к значительному росту объёма данных, передаваемых между узлами. Фактическую передачу данных можно отслеживать, используя параметр NETWORK EXPLAIN ANALYZE (посмотрите на поле Network Received самого верхнего узла плана):

EXPLAIN (ANALYZE, VERBOSE, NETWORK)
SELECT avg(abalance) FROM pgbench_accounts
                                                                                     QUERY PLAN                                                                                     
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=156209.38..156209.39 rows=1 width=32) (actual time=589.014..589.027 rows=1 loops=1)
   Output: avg(pgbench_accounts.abalance)
   Network: FDW bytes sent=3218 received=14402396
   ->  Append  (cost=2891.00..156209.33 rows=20 width=32) (actual time=52.111..588.999 rows=20 loops=1)
         Network: FDW bytes sent=3218 received=14402396
         ->  Partial Aggregate  (cost=2891.00..2891.01 rows=1 width=32) (actual time=52.109..52.109 rows=1 loops=1)
               Output: PARTIAL avg(pgbench_accounts.abalance)
               ->  Seq Scan on public.pgbench_accounts_0 pgbench_accounts  (cost=0.00..2641.00 rows=100000 width=4) (actual time=0.020..34.472 rows=100000 loops=1)
                     Output: pgbench_accounts.abalance
         ->  Partial Aggregate  (cost=23991.00..23991.01 rows=1 width=32) (actual time=78.616..78.617 rows=1 loops=1)
               Output: PARTIAL avg(pgbench_accounts_1.abalance)
               Network: FDW bytes sent=247 received=2400360
               ->  Foreign Scan on public.pgbench_accounts_1_fdw pgbench_accounts_1  (cost=100.00..23741.00 rows=100000 width=4) (actual time=42.359..69.984 rows=100000 loops=1)
                     Output: pgbench_accounts_1.abalance
                     Remote SQL: SELECT abalance FROM public.pgbench_accounts_1
                     Transport: Silk
                     Network: FDW bytes sent=247 received=2400360
.....

В таких случаях иногда можно переписать запрос:

EXPLAIN  (ANALYZE, NETWORK, VERBOSE)
SELECT sum(abalance)::float/count(abalance) FROM pgbench_accounts where abalance is not null;

                                                                             QUERY PLAN                                                                             
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=12577.20..12577.22 rows=1 width=8) (actual time=151.632..151.639 rows=1 loops=1)
   Output: ((sum(pgbench_accounts.abalance))::double precision / (count(pgbench_accounts.abalance))::double precision)
   Network: FDW bytes sent=3907 received=872
   ->  Append  (cost=3141.00..12577.10 rows=20 width=16) (actual time=55.589..151.621 rows=20 loops=1)
         Network: FDW bytes sent=3907 received=872
         ->  Partial Aggregate  (cost=3141.00..3141.01 rows=1 width=16) (actual time=55.423..55.424 rows=1 loops=1)
               Output: PARTIAL sum(pgbench_accounts.abalance), PARTIAL count(pgbench_accounts.abalance)
               ->  Seq Scan on public.pgbench_accounts_0 pgbench_accounts  (cost=0.00..2641.00 rows=100000 width=4) (actual time=0.023..37.212 rows=100000 loops=1)
                     Output: pgbench_accounts.abalance
                     Filter: (pgbench_accounts.abalance IS NOT NULL)
         ->  Async Foreign Scan  (cost=1.00..1.00 rows=1 width=16) (actual time=0.055..0.089 rows=1 loops=1)
               Output: (PARTIAL sum(pgbench_accounts_1.abalance)), (PARTIAL count(pgbench_accounts_1.abalance))
               Relations: Aggregate on (public.pgbench_accounts_1_fdw pgbench_accounts_1)
               Remote SQL: SELECT sum(abalance), count(abalance) FROM public.pgbench_accounts_1 WHERE ((abalance IS NOT NULL))
               Transport: Silk
               Network: FDW bytes sent=300 received=800
....

Переписав запрос здесь, удалось уменьшить входящий сетевой трафик, генерируемый запросом, с 13 МБ до 872 байт.

Теперь посмотрите на два почти идентичных соединения.

EXPLAIN ANALYZE SELECT count(*) FROM pgbench_branches b
JOIN pgbench_history h ON b.bid = h.bid
WHERE mtime > '2023-03-14 10:00:00'::timestamptz AND b.bbalance > 0;

                                                                         QUERY PLAN                                                                         
------------------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=8125.68..8125.69 rows=1 width=8) (actual time=27.464..27.543 rows=1 loops=1)
   ->  Append  (cost=3.85..8125.63 rows=20 width=8) (actual time=0.036..27.475 rows=20 loops=1)
         ->  Partial Aggregate  (cost=3.85..3.86 rows=1 width=8) (actual time=0.033..0.036 rows=1 loops=1)
               ->  Nested Loop  (cost=0.00..3.69 rows=67 width=0) (actual time=0.025..0.027 rows=0 loops=1)
                     Join Filter: (b.bid = h.bid)
                     ->  Seq Scan on pgbench_branches_0 b  (cost=0.00..1.01 rows=1 width=4) (actual time=0.023..0.024 rows=0 loops=1)
                           Filter: (bbalance > 0)
                           Rows Removed by Filter: 1
                     ->  Seq Scan on pgbench_history_0 h  (cost=0.00..1.84 rows=67 width=4) (never executed)
                           Filter: (mtime > '2023-03-14 10:00:00+03'::timestamp with time zone)
         ->  Partial Aggregate  (cost=222.65..222.66 rows=1 width=8) (actual time=3.969..3.973 rows=1 loops=1)
               ->  Nested Loop  (cost=200.00..222.43 rows=86 width=0) (actual time=3.736..3.920 rows=86 loops=1)
                     Join Filter: (b_1.bid = h_1.bid)
                     ->  Foreign Scan on pgbench_branches_1_fdw b_1  (cost=100.00..101.22 rows=1 width=4) (actual time=1.929..1.932 rows=1 loops=1)
                     ->  Foreign Scan on pgbench_history_1_fdw h_1  (cost=100.00..120.14 rows=86 width=4) (actual time=1.795..1.916 rows=86 loops=1)
                           Filter: (mtime > '2023-03-14 10:00:00+03'::timestamp with time zone)
         ->  Partial Aggregate  (cost=864.54..864.55 rows=1 width=8) (actual time=1.780..1.786 rows=1 loops=1)
               ->  Hash Join  (cost=200.01..864.53 rows=5 width=0) (actual time=1.769..1.773 rows=0 loops=1)
                     Hash Cond: (h_2.bid = b_2.bid)
                     ->  Foreign Scan on pgbench_history_2_fdw h_2  (cost=100.00..760.81 rows=975 width=4) (never executed)
                           Filter: (mtime > '2023-03-14 10:00:00+03'::timestamp with time zone)
                     ->  Hash  (cost=100.00..100.00 rows=1 width=4) (actual time=1.740..1.742 rows=0 loops=1)
                           Buckets: 1024  Batches: 1  Memory Usage: 8kB
                           ->  Foreign Scan on pgbench_branches_2_fdw b_2  (cost=100.00..100.00 rows=1 width=4) (actual time=1.738..1.738 rows=0 loops=1)
....
 Planning Time: 6.066 ms
 Execution Time: 33.851 ms

Интересно, что объединение секций pgbench_branches и pgbench_history происходит локально. Это универсальный план — его можно узнать по соединениям, расположенным над сторонними сканированиями. Не всегда очевидно, почему не происходит проталкивания соединения. Но если посмотреть на определение pgbench_history, видно, что mtime имеет тип timestamp without time zone.

\d pgbench_history
              Partitioned table "public.pgbench_history"
 Column |            Type             | Collation | Nullable | Default 
--------+-----------------------------+-----------+----------+---------
 tid    | integer                     |           |          | 
 bid    | integer                     |           |          | 
 aid    | integer                     |           |          | 
 delta  | integer                     |           |          | 
 mtime  | timestamp without time zone |           |          | 
 filler | character(22)               |           |          | 
Partition key: HASH (bid)
Number of partitions: 20 (Use \d+ to list them.)

В приведённом выше запросе строка, описывающая время, преобразуется в timestamp with timezone. Для этого требуется сравнение столбца mtime (типа timestamp) и значения timestamptz. Сравнение выполняется неявно с использованием стабильной функции timestamp_gt_timestamptz. Фильтр, содержащий функцию переменной природы, не может быть передан на сторонний сервер, поэтому соединение выполняется локально. Если переписать запрос, преобразовав строку в отметку времени, станет заметно не только то, что соединения проталкиваются, но и то, что удалённые запросы могут выполняться асинхронно, поскольку сторонние сканирования в дереве плана расположены сразу после Append:

EXPLAIN ANALYZE SELECT count(*) FROM pgbench_branches b
JOIN pgbench_history h ON b.bid = h.bid
WHERE mtime > '2023-03-14 10:00:00'::timestamp AND b.bbalance > 0;
                                                                   QUERY PLAN                                                                   
------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=84.30..84.31 rows=1 width=8) (actual time=22.962..22.990 rows=1 loops=1)
   ->  Append  (cost=3.85..84.25 rows=20 width=8) (actual time=0.196..22.927 rows=20 loops=1)
         ->  Partial Aggregate  (cost=3.85..3.86 rows=1 width=8) (actual time=0.032..0.034 rows=1 loops=1)
               ->  Nested Loop  (cost=0.00..3.69 rows=67 width=0) (actual time=0.024..0.026 rows=0 loops=1)
                     Join Filter: (b.bid = h.bid)
                     ->  Seq Scan on pgbench_branches_0 b  (cost=0.00..1.01 rows=1 width=4) (actual time=0.023..0.023 rows=0 loops=1)
                           Filter: (bbalance > 0)
                           Rows Removed by Filter: 1
                     ->  Seq Scan on pgbench_history_0 h  (cost=0.00..1.84 rows=67 width=4) (never executed)
                           Filter: (mtime > '2023-03-14 10:00:00'::timestamp without time zone)
         ->  Async Foreign Scan  (cost=0.99..0.99 rows=1 width=8) (actual time=10.870..10.871 rows=1 loops=1)
               Relations: Aggregate on ((pgbench_branches_1_fdw b_1) INNER JOIN (pgbench_history_1_fdw h_1))
         ->  Async Foreign Scan  (cost=0.99..0.99 rows=1 width=8) (actual time=0.016..0.017 rows=1 loops=1)
               Relations: Aggregate on ((pgbench_branches_2_fdw b_2) INNER JOIN (pgbench_history_2_fdw h_2))
...
 Planning Time: 7.729 ms
 Execution Time: 14.603 ms

Обратите внимание, что сторонние сканирования здесь включают список соединённых отношений. Ожидаемая стоимость стороннего соединения ниже 1,0. Это связано с оптимистичной методикой оценки стоимости стороннего соединения, включённой настройкой postgres_fdw.enforce_foreign_join. Сравните общее время выполнения (время планирования + время выполнения) исходного и изменённого запроса — его удалось уменьшить примерно с 40 до 22 мс.

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

14.6.1.1. Параметры EXPLAIN #

В этом разделе перечисляются параметры EXPLAIN для распределённой системы.

NETWORK (boolean) #

Включать в вывод команды EXPLAIN ANALYZE фактическую передачу данных между узлами. Если параметр не указан, предполагается значение off. Если параметр указан без значения, предполагается значение on.

REMOTE (boolean) #

Включать планы запросов, выполненных на внешних серверах. Если параметр не указан или его значение не задано, предполагается значение on.

14.6.2. Оптимизация DML #

При оценке производительности операторов DML важно понимать, как они обрабатываются в Postgres Pro Shardman.

Прежде всего, выполнение оператора INSERT существенно отличается от выполнения UPDATE и DELETE. Поведение INSERT для сегментированных таблиц контролируется параметром стороннего сервера batch_size, который можно установить в разделе FDWOptionsфайла конфигурации Postgres Pro Shardman. Если batch_size больше 0, INSERT в одном операторе для нескольких значений, которые попадают в одну и ту же стороннюю секцию, приводит к тому, что значения группируются вместе в порции указанного размера. Удалённые операторы INSERT подготавливаются с необходимым количеством параметров и затем выполняются с заданными значениями. Если количество значений не совпадает с количеством подготовленных аргументов, снова подготавливается изменённый оператор с необходимым количеством параметров. Оптимизация массовой вставки может завершиться ошибкой, если транзакция вставляет записи одну за другой или записи, направляемые в разные сторонние таблицы, смешиваются в одном операторе INSERT. Порция формируется для одной операции стороннего изменения. Она отправляется на удалённый сервер при заполнении порции или при завершении операции изменения. Операция изменения завершается, когда кортежи начинают перенаправляться в другую секцию сегментированной таблицы. Таким образом, для массовой загрузки рекомендуется вставка нескольких значений в одной команде INSERT или использование команды COPY (поскольку она оптимизируется аналогичным образом). Большие значения batch_size позволяют выдавать меньше операторов INSERT на удалённой стороне и, таким образом, значительно снижают стоимость связи. Однако при формировании параметров для подготовленных операторов INSERT все вставленные значения должны быть скопированы в память, выделенную libpq. Это может привести к неограниченному использованию памяти на стороне координатора запросов при загрузке нескольких больших объектов text или bytea.

Операторы UPDATE и DELETE могут выполняться в прямом или непрямом режиме. Прямой режим используется, когда оператор может быть напрямую отправлен на сторонний сервер. В этом режиме для изменения таблицы на удалённом сервере создаётся новый оператор на основе исходного узла плана ModifyTable. Использование прямого изменения не всегда возможно. В частности, оно невозможно когда некоторые условия должны оцениваться локально. В этом случае используется гораздо менее эффективное непрямое изменение. Непрямое изменение включает в себя несколько операторов. Первый — SELECT FOR UPDATE для блокировки строк на удалённой части. Второй — фактический UPDATE или DELETE, который готовится один раз, а затем выполняется с разными параметрами для каждой строки результата оператора SELECT FOR UPDATE после применения к результаты локальных фильтров. Очевидно, что прямые модификации намного эффективнее.

Можно легко определить, в прямом или непрямом режиме будет выполняться оператор DML, посмотрев на план запроса. Типичный пример непрямого изменения:

EXPLAIN VERBOSE DELETE FROM pgbench_history
WHERE bid = 20 AND mtime > '2023-03-14 10:00:00'::timestamptz;
                                                  QUERY PLAN                                                  
--------------------------------------------------------------------------------------------------------------
 Delete on public.pgbench_history  (cost=100.00..142.66 rows=0 width=0)
   Foreign Delete on public.pgbench_history_17_fdw pgbench_history_1
     Remote SQL: DELETE FROM public.pgbench_history_17 WHERE ctid = $1
   ->  Foreign Scan on public.pgbench_history_17_fdw pgbench_history_1  (cost=100.00..142.66 rows=4 width=10)
         Output: pgbench_history_1.tableoid, pgbench_history_1.ctid
         Filter: (pgbench_history_1.mtime > '2023-03-14 10:00:00+03'::timestamp with time zone)
         Remote SQL: SELECT mtime, ctid FROM public.pgbench_history_17 WHERE ((bid = 20)) FOR UPDATE

При выборе другого типа строковой константы это было бы прямым изменением.

EXPLAIN VERBOSE DELETE FROM pgbench_history
WHERE bid = 20 AND mtime > '2023-03-14 10:00:00'::timestamp;
explain verbose delete from pgbench_history where bid = 20 and mtime > '2023-03-14 10:00:00'::timestamp;
                                                                   QUERY PLAN                                                                    
-------------------------------------------------------------------------------------------------------------------------------------------------
 Delete on public.pgbench_history  (cost=100.00..146.97 rows=0 width=0)
   Foreign Delete on public.pgbench_history_17_fdw pgbench_history_1
   ->  Foreign Delete on public.pgbench_history_17_fdw pgbench_history_1  (cost=100.00..146.97 rows=4 width=10)
         Remote SQL: DELETE FROM public.pgbench_history_17 WHERE ((mtime > '2023-03-14 10:00:00'::timestamp without time zone)) AND ((bid = 20))

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

14.6.2.1. Оптимизация DML для глобальных таблиц #

Настраиваемый параметр конфигурации shardman.gt_batch_size задаёт размер промежуточного буфера до отправки данных на удалённый сервер.

Операция INSERT использует бинарный протокол и формирует порции данных размером shardman.gt_batch_size. Высокие значения размера буфера позволяют выдавать меньше запросов по сети на удалённой стороне и, таким образом, значительно снижают стоимость связи. Однако это может привести к неограниченному использованию памяти на стороне координатора запросов. Поэтому важно найти золотую середину между стоимостью связи и размером используемой памяти.

Для операции UPDATE на координаторе формируется запрос для каждого столбца и каждой строки и отправляется на удалённые узлы.

Для операции DELETE на координаторе формируется запрос для порции данных с размером shardman.gt_batch_size и рассылается на удалённые узлы.

14.6.3. Синхронизация по времени #

Алгоритм, обеспечивающий согласованность данных на всех узлах кластера, использует время системных часов, установленных на машине. Таким образом, зависит от расхождения времени на разных машинах, поскольку координатор запросов всегда дожидается машины с наибольшим отставанием. Поэтому принципиально важно, чтобы время на всех связанных узлах кластера Postgres Pro Shardman было синхронизировано, поскольку отсутствие этой синхронизации может негативно повлиять на производительность Postgres Pro Shardman из-за увеличенной задержки фиксации транзакции.

Во-первых, для обеспечения синхронизации времени на всех узлах кластера установите и разверните демон chrony.

  sudo apt update
  sudo apt install -y chrony
  sudo systemctl enable --now chrony

Проверьте, что chrony работает правильно.

chronyc tracking

Ожидаемый вывод:

      Reference ID    : C0248F82 (Time100.Stupi.SE)
      Stratum         : 2
      Ref time (UTC)  : Tue Apr 18 11:50:44 2023
      System time     : 0.000019457 seconds slow of NTP time
      Last offset     : -0.000005579 seconds
      RMS offset      : 0.000089375 seconds
      Frequency       : 30.777 ppm fast
      Residual freq   : -0.000 ppm
      Skew            : 0.003 ppm
      Root delay      : 0.018349268 seconds
      Root dispersion : 0.000334640 seconds
      Update interval : 1039.1 seconds
      Leap status     : Normal

Обратите внимание, что контроль величины расхождения часов должен выполняться инструментарием операционной системы. Средства диагностики Postgres Pro Shardman не могут являться единственным достоверным средством измерения.

Чтобы проверить, существует ли на данный момент существенная рассинхронизация, используйте представление shardman.pg_stat_csn, которое выводит статистику задержек, возникающих во время импорта снимков CSN. Расчёт значений производится непосредственно в момент соответствующего действия или при вызове функций shardman.trim_csnxid_map() или shardman.pg_oldest_csn_snapshot(). Вызов этих функций осуществляется из служебного процесса csn trimmer routine (процесс усечения CSN), поэтому его отключение приведёт к остановке сбора этой статистики.

Поле csn_max_shift представления shardman.pg_stat_csn отображает максимальную задержку снимка CSN, вызвавшего задержку. Это значение определяет расхождение часов между узлами кластера. Последовательный рост этого значения является индикатором того, что в кластере есть по крайней мере одни часы, не синхронизованные с другими. Если данное значение превышает 1000 микросекунд, рекомендуется проверить параметры синхронизации по времени.

Другим индикатором расхождения может быть повышение значения csn_total_import_delay при неизменном значении csn_max_shift. Однако разовое повышение может быть вызвано единичным сбоем, а не проблемами с синхронизацией по времени.

Кроме того, если разница между CSNXidMap_head_csn и shardman.oldest_csn превышает значение параметра csn_snapshot_defer_time и долгое время не меняется, это значит, что заполнилась карта CSNSnapshotXidMap. Это может привести к сбою глобальной транзакции.

У этой проблемы две возможные причины.

  • В системе существует длительная активная транзакция, которая живет больше csn_snapshot_defer_time секунд и этим удерживает горизонт во всем кластере, препятствуя нормальной работе VACUUM. В этом случае следует использовать значение поля xid представления shardman.oldest_csn для определения идентификатора этой транзакции и значение поля rgid для определения узла кластера, на котором эта транзакция запущена.

  • Карта CSNSnapshotXidMap имеет недостаточный размер. При штатной работе системы могут быть транзакции, которые выполняются дольше csn_snapshot_defer_time секунд. Следует увеличить значение csn_snapshot_defer_time до такого размера, чтобы длительность таких транзакций не превышала этого значения.

При выполнении команды EXPLAIN для распределённых запросов, при включённом параметре конфигурации shardman.silk_tracepoints выводится информация о трассировке времени прохождения запроса и результата его выполнения по компонентам системы. Эта информация состоит из строк со значениями метрик, которые показывают время прохождения через каждый компонент. Метрики net (qry), net (1st tup) и net (last tup) вычисляются как разница отметок времени на разных серверах. Эта величина включает в себя время передачи сообщения и величину (положительную или отрицательную) сдвига часов между серверами. Таким образом, по величине этих метрик возможно косвенно оценить расхождение часов между серверами.

14.6.4. Диагностика распределённых запросов #

Postgres Pro Shardman расширяет возможности команды EXPLAIN, предоставляя дополнительную информацию по запросу, если он распределённый. Работа с распределёнными таблицами построена на узлах плана с типом ForeignScan. Запрос к каждой удалённой секции определяется одним узлом плана такого типа. При этом Postgres Pro Shardman предоставляет дополнительную информацию в блоки EXPLAIN, описывающие эти узлы.

При выполнении распределённого запроса часть плана (поддерево), которая касается конкретной удалённой секции, сериализуется в SQL-выражение (происходит deparsing). После этого такое выражение отправляется на удалённый сервер. Результат выполнения этого запроса виден в выводе для узла ForeignScan. Он участвует в сборке окончательного результата выполнения распределённого запроса.

Когда задано значение on для параметра VERBOSE команды EXPLAIN, в блоке узла ForeignScan в поле Remote SQL будет показано выражение, отправляемое на удалённый сервер. При этом в поле Server будет указано имя сервера в том же виде, в каком оно задано при конфигурировании кластера и как оно отображается в каталоге pg_foreign_server, а также вид транспорта, применяющегося для отправки этого выражения. Поле transport может принимать два значения: silk для использования оптимизированного транспортного механизма Postgres Pro Shardman или libpq для отправки через стандартный протокол PostgreSQL.

14.6.4.1. Показ планов с удалённого сервера #

Чтобы увидеть под блоком EXPLAIN узла ForeignScan, какой план выполнения будет реализован на удалённом сервере, используется параметр конфигурации postgres_fdw.foreign_explain. Возможные значения: none не включает вывод EXPLAIN с удалённых серверов, full включает вывод EXPLAIN с удалённых серверов для всех узлов ForeignScan, collapsed включает вывод EXPLAIN только для первого узла ForeignScan под узлами Append/MergeAppend.

В производственной системе рекомендуется выключать этот параметр (задать значение none) или оставлять его со значением collapsed, так как получение любого фрагмента EXPLAIN является дополнительным неявным запросом к серверу. Кроме того, этот запрос выполняется в синхронном режиме, то есть общий вывод EXPLAIN будет построен после последовательного опроса всех серверов. Для таблиц с большим количеством секций это может быть затратной операцией.

Необходимо учитывать, что внутренний запрос на получение блоков EXPLAIN для удалённого плана осуществляется с принудительным отключением некоторых параметров (вне зависимости от того, какие параметры указал пользователь при запросе EXPLAIN от координатора): ANALYZE OFF, TIMING OFF, SUMMARY OFF, SETTINGS OFF, NETWORK OFF. Соответствующие метрики в блоках EXPLAIN удалённого плана будут отсутствовать. Остальные параметры EXPLAIN (FORMAT, VERBOSE, COSTS, BUFFERS, WAL) наследуются с координатора.

Внутренний запрос EXPLAIN к удалённому серверу осуществляется с опцией GENERIC_PLAN. Поэтому при анализе планов удалённой стороны необходимо учитывать, что в блоках EXPLAIN отображается именно общий план.

14.6.4.2. Сетевые метрики и время ожидания #

При включённом параметре NETWORK команды EXPLAIN как для отдельных узлов плана ForeignScan, так и для общих узлов Append или MergeAppend отображаются метрики сетевых операций.

Для каждого узла плана отображаются параметры FDW bytes, sent и received для исходящего и входящего трафика, потраченного при выполнении этого узла (вне зависимости от вида транспорта). Необходимо учитывать, что эти метрики выводятся, только если задано значение on параметра ANALYZE команды EXPLAIN.

При включённом параметре конфигурации track_fdw_wait_timing дополнительно выводится метрика wait_time. Она обобщает все этапы выполнения данного узла плана, начиная от отправки запроса на удалённый сервер, включая время, потраченное собственно на выполнение запроса, а также время до получения полного объёма результатов для этого узла плана.

Необходимо учитывать, что узел ForeignScan может выполняться как в синхронном, так и в асинхронном режиме. В случае асинхронного выполнения функция выполнения узла отправляет запрос на удалённый сервер и завершает выполнение, не дожидаясь получения результата, который будет учтён и обработан позже, по факту получения. В этом случае метрика wait_time может не отражать реальное время выполнения.

14.6.4.3. Трассировка прохождения запроса для транспорта Silk #

Для транспорта Silk существует возможность вывести расширенную отладочную информацию по трассировке прохождения запроса от координатора на удалённый сервер, включающую получение результатов с удалённого сервера. Эта информация отображается, если задано значение on для параметра ANALYZE команды EXPLAIN и включён параметр конфигурации shardman.silk_tracepoints.

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

Таблица 14.1. Метрики трассировки прохождения запроса для транспорта Silk

ИнтервалОписание
bk shm->mp1 (qry)Время передачи SQL-запроса от координатора к его мультиплексору через общую память.
mp1 shm->net (qry)Время между приёмом запроса внутри мультиплексора из общей памяти и передачей по сети.
net (qry)Время прохождения SQL-запроса по сети между мультиплексорами.
mp2 recv->shm (qry)Время между приёмом SQL-запроса из сети и передачей в очередь в общей памяти на удалённом мультиплексоре.
wk exec (1st tup)Время выполнения запроса на Silkworm до получения первой строки результата.
wk exec (all tups)Время выполнения запроса на Silkworm до получения полного результата.
wk->shm (1st tup)Время передачи в очередь Silkworm первой строки результата.
wk->shm (last tup)Время передачи в очередь Silkworm последней строки результата.
mp2 shm->net (1st tup)Время между чтением из очереди удаленным мультиплексором первой строки результата и передачей ее в сеть.
net (1st tup)Время передачи по сети первой строки результата между мультиплексорами.
mp1 recv->shm (1st tup)Время между приемом из сети и передачей в очередь первой строки результата локальным мультиплексором.
mp1 shm->bk (1st tup)Время получения координатором из очереди первой строки результата.
mp2 shm->net (last tup)Время между чтением из очереди удалённым мультиплексором последней строки результата и передачей её в сеть.
net (last tup)Время передачи по сети между мультиплексорами последней строки результата.
mp1 recv->shm (last tup)Время между приёмом из сети и передачей в очередь последней строки результата локальным мультиплексором.
mp1 shm->bk (last tup)Время получения координатором из очереди последней строки результата.
END-TO-ENDОбщее время от отправки запроса до получения последней строки результата. Примерно совпадает с wait_time.

Для метрик net (qry), net (1st tup) и net (last tup) значение интервала считается как разница отметок времени, измеренных на разных серверах. Поэтому в этих строках допускается появление отрицательных значений. Величина такого интервала по сути представляет собой сумму времени передачи сообщения по сети и величины (положительной или отрицательной) сдвига часов между серверами. Поэтому даже при незначительном сдвиге, если его абсолютное значение превышает длительность передачи по сети, будут встречаться отрицательные значения. Несмотря на то, что это не является сбоем, при достаточно больших значениях необходимо удостовериться в правильной синхронизации времени в кластере. Более подробно информация описана в Подразделе 14.6.3.