Вот еще один случай для локального чтения. У меня есть 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