Сначала считывается локальный раздел, затем глобальный, если он не найден локально

Вот еще один случай для локального чтения. У меня есть customer_id, но я не знаю ее страну. Однако велики шансы, что я подключен к тому месту, где находятся ее данные. Подумайте об этом как о том, что клиенты европейского банка, вероятно, подключаются из Европы. В этом случае мне нужны запросы с низкой задержкой. Если клиент путешествует и делает запрос, например, из США, он, вероятно, согласится на большую задержку, зная, что его банковский счет находится в Европе.

Я запускаю это демо на тех же данных, что и в предыдущем сообщении, все DDL и DML были в предыдущем сообщении. Я использовал yb_is_local_table(tableoid) для чтения из локальной таблицы.

подключение к региону пользователя

Пользователь 9f0345c1-ff88-477d-8f87-b6ae3717ba37 находится в регионе earth (порт 5433):


yugabyte=# c - - - 5433

psql (13.5, server 11.2-YB-2.15.1.0-b0)
You are now connected to database "yugabyte" as user "postgres".

yugabyte=# show listen_addresses;

       listen_addresses
------------------------------
 yb-tserver-0.base.earth.star
(1 row)

yugabyte=# select * from customers
           where id in ('9f0345c1-ff88-477d-8f87-b6ae3717ba37','3a890dfc-2a99-4ef4-9939-9fea1c9241ad')
           and yb_is_local_table(tableoid);

                  id                  | planet | info
--------------------------------------+--------+------
 9f0345c1-ff88-477d-8f87-b6ae3717ba37 | earth  | 1465
(1 row)


Войдите в полноэкранный режим Выход из полноэкранного режима

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

Объединение всех

Я знаю, что пользователь может находиться только в одном регионе. Это не гарантируется базой данных, поскольку используемое здесь декларативное разбиение, пришедшее из PostgreSQL, не имеет глобальных индексов. Первичный ключ включает регион (planet в моем примере):

yugabyte=# d customers
           Partitioned table "public.customers"
 Column | Type | Collation | Nullable |      Default
--------+------+-----------+----------+-------------------
 id     | uuid |           | not null | gen_random_uuid()
 planet | text |           | not null |
 info   | text |           |          |
Partition key: LIST (planet)
Indexes:
    "customers_pkey" PRIMARY KEY, lsm (id HASH, planet ASC)
Number of partitions: 3 (Use d+ to list them.)
Войти в полноэкранный режим Выйти из полноэкранного режима

Однако, если я знаю, что создал их уникально, я могу положиться на то, что мой запрос для одного id вернет только одну строку.

select * from customers 
 where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
 and yb_is_local_table(tableoid)
union all
select * from customers 
 where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
and not yb_is_local_table(tableoid)
limit 1;

                  id                  | planet | info
--------------------------------------+--------+------
 9f0345c1-ff88-477d-8f87-b6ae3717ba37 | earth  | 1465

Войти в полноэкранный режим Выход из полноэкранного режима

Давайте посмотрим на выполнение при подключении к earth.

c - - - 5433

explain (costs off, analyze, summary off)
select * from customers 
 where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
 and yb_is_local_table(tableoid)
union all
select * from customers 
 where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
 and not yb_is_local_table(tableoid)
limit 1;

                                                      QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
 Limit (actual time=0.653..0.655 rows=1 loops=1)
   ->  Append (actual time=0.614..0.614 rows=1 loops=1)
         ->  Append (actual time=0.614..0.614 rows=1 loops=1)
               ->  Index Scan using customers_earth_pkey on customers_earth (actual time=0.613..0.613 rows=1 loops=1)
                     Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                     Filter: yb_is_local_table(tableoid)
         ->  Append (never executed)
               ->  Index Scan using customers_earth_pkey on customers_earth customers_earth_1 (never executed)
                     Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                     Filter: (NOT yb_is_local_table(tableoid))
               ->  Index Scan using customers_mars_pkey on customers_mars (never executed)
                     Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                     Filter: (NOT yb_is_local_table(tableoid))
               ->  Index Scan using customers_moon_pkey on customers_moon (never executed)
                     Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                     Filter: (NOT yb_is_local_table(tableoid))

Вход в полноэкранный режим Выход из полноэкранного режима

Только первая ветвь, читающая customers_earth (локальная) была выполнена, вернув rows=1, остальные были пропущены ((never executed)).

То же самое при запуске из moon:

c - - - 5434

explain (costs off, analyze, summary off)
select * from customers 
 where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
 and yb_is_local_table(tableoid)
union all
select * from customers 
 where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
and not yb_is_local_table(tableoid)
limit 1;

                                                      QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
 Limit (actual time=1.122..1.124 rows=1 loops=1)
   ->  Append (actual time=1.121..1.121 rows=1 loops=1)
         ->  Append (actual time=0.481..0.481 rows=0 loops=1)
               ->  Index Scan using customers_moon_pkey on customers_moon (actual time=0.480..0.480 rows=0 loops=1)
                     Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                     Filter: yb_is_local_table(tableoid)
         ->  Append (actual time=0.640..0.640 rows=1 loops=1)
               ->  Index Scan using customers_earth_pkey on customers_earth (actual time=0.639..0.639 rows=1 loops=1)
                     Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                     Filter: (NOT yb_is_local_table(tableoid))
               ->  Index Scan using customers_mars_pkey on customers_mars (never executed)
                     Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                     Filter: (NOT yb_is_local_table(tableoid))
               ->  Index Scan using customers_moon_pkey on customers_moon customers_moon_1 (never executed)
                     Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                     Filter: (NOT yb_is_local_table(tableoid))
(16 rows)
Войти в полноэкранный режим Выход из полноэкранного режима

Первая ветвь, которая сейчас находится на customers_moon (локальная), не вернула ни одной строки (rows=0), а затем вторая ветвь выполнялась, раздел за разделом, пока не вернула строку.

Это соответствует нашей цели: низкая задержка, когда пользователь локален, более высокая задержка, если он путешествует.

Однако у него есть две проблемы:

  • мы можем дважды прочитать локальный раздел (один раз, потому что он локальный, а другой — если он первый прочитан глобальной ветвью)
  • мы полагаемся на порядок выполнения UNION ALL. Даже если это предположение можно проверить (YugabyteDB имеет открытый исходный код), это нарушает язык SQL, который является декларативным, а не процедурным. Однажды придет оптимизация, которая изменит порядок, и мы получим ошибку.

С РЕКУРСИВНОСТЬЮ

Рекурсивные общие табличные выражения (CTE), даже если это все еще декларативный SQL, лучше гарантируют процедурный порядок выполнения, поскольку один уровень должен быть выполнен до вложенного. Мой запрос будет таким:

with recursive my_cte as (
 select * from customers 
  where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
  and yb_is_local_table(tableoid)
 union all 
 (
  select * from customers 
  where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
  and not yb_is_local_table(tableoid)
  union all select * from my_cte 
 )
) select * from my_cte limit 1;
Войти в полноэкранный режим Выйти из полноэкранного режима

Вот план выполнения при подключении к earth — дому моего пользователя:

c - - - 5433

explain (costs off, analyze, summary off)
with recursive my_cte as (
 select * from customers 
  where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
  and yb_is_local_table(tableoid)
 union all 
 (
  select * from customers 
  where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
  and not yb_is_local_table(tableoid)
  union all select * from my_cte 
 )
) select * from my_cte limit 1;

                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Limit (actual time=0.650..0.651 rows=1 loops=1)
   CTE my_cte
     ->  Recursive Union (actual time=0.648..0.648 rows=1 loops=1)
           ->  Append (actual time=0.647..0.647 rows=1 loops=1)
                 ->  Index Scan using customers_earth_pkey on customers_earth (actual time=0.646..0.646 rows=1 loops=1)
                       Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                       Filter: yb_is_local_table(tableoid)
           ->  Append (never executed)
                 ->  Append (never executed)
                       ->  Index Scan using customers_earth_pkey on customers_earth customers_earth_1 (never executed)
                             Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                       ->  Index Scan using customers_mars_pkey on customers_mars (never executed)
                             Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                       ->  Index Scan using customers_moon_pkey on customers_moon (never executed)
                             Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                 ->  WorkTable Scan on my_cte my_cte_1 (never executed)
   ->  CTE Scan on my_cte (actual time=0.649..0.649 rows=1 loops=1)
(17 rows)

Войти в полноэкранный режим Выход из полноэкранного режима

И то же самое при подключении к moon:

c - - - 5434

explain (costs off, analyze, summary off)
with recursive my_cte as (
 select * from customers 
  where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
  and yb_is_local_table(tableoid)
 union all 
 (
  select * from customers 
  where id='9f0345c1-ff88-477d-8f87-b6ae3717ba37'
  and not yb_is_local_table(tableoid)
  union all select * from my_cte 
 )
) select * from my_cte limit 1;

                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Limit (actual time=0.650..0.651 rows=1 loops=1)
   CTE my_cte
     ->  Recursive Union (actual time=0.648..0.648 rows=1 loops=1)
           ->  Append (actual time=0.647..0.647 rows=1 loops=1)
                 ->  Index Scan using customers_earth_pkey on customers_earth (actual time=0.646..0.646 rows=1 loops=1)
                       Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                       Filter: yb_is_local_table(tableoid)
           ->  Append (never executed)
                 ->  Append (never executed)
                       ->  Index Scan using customers_earth_pkey on customers_earth customers_earth_1 (never executed)
                             Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                       ->  Index Scan using customers_mars_pkey on customers_mars (never executed)
                             Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                       ->  Index Scan using customers_moon_pkey on customers_moon (never executed)
                             Index Cond: (id = '9f0345c1-ff88-477d-8f87-b6ae3717ba37'::uuid)
                 ->  WorkTable Scan on my_cte my_cte_1 (never executed)
   ->  CTE Scan on my_cte (actual time=0.649..0.649 rows=1 loops=1)
(17 rows)

Войти в полноэкранный режим Выход из полноэкранного режима

Это решает мою проблему с порядком выполнения, ценой немного более сложного SQL-запроса. У меня все еще остается проблема, что локальный раздел может быть прочитан дважды. Например, при запросе пользователей из mars:

explain (costs off, analyze, summary off)
with recursive my_cte as (
 select * from customers 
  where id='841efbb7-4833-4f65-ab07-d557ebc4427a'
  and yb_is_local_table(tableoid)
 union all 
 (
  select * from customers 
  where id='841efbb7-4833-4f65-ab07-d557ebc4427a'
  and not yb_is_local_table(tableoid)
  union all select * from my_cte 
 )
) select * from my_cte limit 1;

                                                          QUERY PLAN                                                      
------------------------------------------------------------------------------------------------------------------------------
 Limit (actual time=2.364..2.365 rows=1 loops=1)
   CTE my_cte
     ->  Recursive Union (actual time=2.362..2.362 rows=1 loops=1)
           ->  Append (actual time=0.871..0.871 rows=0 loops=1)
                 ->  Index Scan using customers_moon_pkey on customers_moon (actual time=0.870..0.870 rows=0 loops=1)
                       Index Cond: (id = '841efbb7-4833-4f65-ab07-d557ebc4427a'::uuid)
                       Filter: yb_is_local_table(tableoid)
           ->  Append (actual time=1.490..1.490 rows=1 loops=1)
                 ->  Append (actual time=1.489..1.489 rows=1 loops=1)
                       ->  Index Scan using customers_earth_pkey on customers_earth (actual time=0.716..0.716 rows=0 loops=1)
                             Index Cond: (id = '841efbb7-4833-4f65-ab07-d557ebc4427a'::uuid)
                             Filter: (NOT yb_is_local_table(tableoid))
                       ->  Index Scan using customers_mars_pkey on customers_mars (actual time=0.772..0.772 rows=1 loops=1)
                             Index Cond: (id = '841efbb7-4833-4f65-ab07-d557ebc4427a'::uuid)
                             Filter: (NOT yb_is_local_table(tableoid))
                       ->  Index Scan using customers_moon_pkey on customers_moon customers_moon_1 (never executed)
                             Index Cond: (id = '841efbb7-4833-4f65-ab07-d557ebc4427a'::uuid)
                             Filter: (NOT yb_is_local_table(tableoid))
                 ->  WorkTable Scan on my_cte my_cte_1 (never executed)
   ->  CTE Scan on my_cte (actual time=2.363..2.363 rows=1 loops=1)
(20 rows)

Войти в полноэкранный режим Выход из полноэкранного режима

Строка не была найдена локально (customers_moon (actual time=0.870..0.870 rows=0 loops=1)), причем это произошло и во второй итерации:

                 ->  Append (actual time=1.489..1.489 rows=1 loops=1)
                       ->  Index Scan using customers_earth_pkey on customers_earth (actual time=0.716..0.716 rows=0 loops=1)
                             Index Cond: (id = '841efbb7-4833-4f65-ab07-d557ebc4427a'::uuid)
                             Filter: (NOT yb_is_local_table(tableoid))
Войти в полноэкранный режим Выход из полноэкранного режима

Причина в том, что, очевидно, мы не делаем обрезку разделов для where not yb_is_local_table(), как мы делаем для where yb_is_local_table(). Это не должно иметь значения, поскольку обычно это добавляет одну миллисекунду к чтению, которое, вероятно, составляет 10 или 100 мс в разных регионах. Но если это проблема, вы можете открыть git-выпуск для улучшения.

Эта серия блогов основана на наших пользовательских требованиях. Если у вас есть другие идеи, пожалуйста, поделитесь. Геораспределение YugabyteDB предлагает множество возможностей. Если вы хотите узнать больше о табличных пространствах для гео-разметки, я расскажу об этом в пятничном YFTTT

Оцените статью
devanswers.ru
Добавить комментарий