insérer des données
Pour insérer des données dans une table distribuée, vous pouvez utiliser la commande PostgreSQL
INSERT standard . Par exemple, nous sélectionnons au hasard deux lignes dans l'ensemble de données Github
archivé .
- INSÉRER
/*
CREATE TABLE github_events
(
event_id bigint,
event_type text,
event_public boolean,
repo_id bigint,
payload jsonb,
repo jsonb,
actor jsonb,
org jsonb,
created_at timestamp
);
*/
INSERT INTO github_events VALUES (2489373118,'PublicEvent','t',24509048,'{}','{"id": 24509048, "url": "https://api.github.com/repos/SabinaS/csee6868", "name": "SabinaS/csee6868"}','{"id": 2955009, "url": "https://api.github.com/users/SabinaS", "login": "SabinaS", "avatar_url": "https://avatars.githubusercontent.com/u/2955009?", "gravatar_id": ""}',NULL,'2015-01-01 00:09:13');
INSERT INTO github_events VALUES (2489368389,'WatchEvent','t',28229924,'{"action": "started"}','{"id": 28229924, "url": "https://api.github.com/repos/inf0rmer/blanket", "name": "inf0rmer/blanket"}','{"id": 1405427, "url": "https://api.github.com/users/tategakibunko", "login": "tategakibunko", "avatar_url": "https://avatars.githubusercontent.com/u/1405427?", "gravatar_id": ""}',NULL,'2015-01-01 00:00:24');
复制代码
Lors de l'insertion d'une ligne dans une table distribuée, vous devez spécifier la colonne de distribution pour la ligne insérée. En fonction de la colonne de distribution, Citus
déterminez la partition correcte vers laquelle les insertions doivent être acheminées. La requête est ensuite transmise à la partition appropriée et la commande d'insertion à distance est exécutée sur toutes les répliques de cette partition.
insert
Parfois, insert
il est pratique de mettre plusieurs instructions dans une seule instruction avec plusieurs lignes. Il est également plus efficace que de répéter les requêtes de base de données. Par exemple, l'exemple de la section précédente peut être chargé en une seule fois comme ceci :
INSERT INTO github_events VALUES
(
2489373118,'PublicEvent','t',24509048,'{}','{"id": 24509048, "url": "https://api.github.com/repos/SabinaS/csee6868", "name": "SabinaS/csee6868"}','{"id": 2955009, "url": "https://api.github.com/users/SabinaS", "login": "SabinaS", "avatar_url": "https://avatars.githubusercontent.com/u/2955009?", "gravatar_id": ""}',NULL,'2015-01-01 00:09:13'
), (
2489368389,'WatchEvent','t',28229924,'{"action": "started"}','{"id": 28229924, "url": "https://api.github.com/repos/inf0rmer/blanket", "name": "inf0rmer/blanket"}','{"id": 1405427, "url": "https://api.github.com/users/tategakibunko", "login": "tategakibunko", "avatar_url": "https://avatars.githubusercontent.com/u/1405427?", "gravatar_id": ""}',NULL,'2015-01-01 00:00:24'
);
复制代码
Clause "From Select" (agrégation distribuée)
Citus
INSERT ... SELECT
Les instructions sont également prises en charge - insertion de lignes en fonction des résultats d'une requête de sélection. C'est un moyen pratique de remplir une table et permet également l'utilisation de ON CONFLICT
clauses “更新插入(upserts)”
, ce qui est le moyen le plus simple de faire des résumés distribués .
- Agrégation distribuée
Citus
Dans , il existe trois manières select
d'insérer à partir d'une instruction. La première est si les tables source et cible sont colocalisées et que l' select/insert
instruction contient toutes deux des colonnes de distribution. Dans ce cas, Citus
l' INSERT ... SELECT
instruction poussée vers le bas pour une exécution parallèle sur tous les nœuds.
L'optimisation de la répartition peut se produire lorsqu'une SELECT
requête ne nécessite pas d'étape de fusion sur le coordinateur. Il ne fonctionne pas avec les SQL
fonctions :
ORDER BY
LIMIT
OFFSET
GROUP BY
Lorsque la colonne de distribution ne fait pas partie de lagroup
clé- 按源表中的非分布列分区时的
Window(窗口)
函数 - 非同位表之间的
Join(连接)
(即重新分区连接)
当源表和目标表没有在同一位置,并且无法应用重新分区优化时,Citus
使用第三种方式执行 INSERT ... SELECT
。 它从工作节点中选择结果,并将数据拉到协调节点。协调器将行重定向回适当的分片。 因为所有数据都必须通过单个节点,所以这种方法效率不高。
如果对 Citus
使用哪种方法有疑问,请使用 EXPLAIN
命令,如 PostgreSQL
调优中所述。 当目标表的分片数量非常大时,禁用重新分区可能是明智之举, 请参阅 citus.enable_repartitioned_insert_select (boolean)。
- PostgreSQL 调优
- citus.enable_repartitioned_insert_select (boolean)
COPY 命令(批量加载)
要从文件中批量加载数据,您可以直接使用 PostgreSQL
的 \COPY
命令。
首先通过运行下载我们的示例 github_events
数据集:
wget http://examples.citusdata.com/github_archive/github_events-2015-01-01-{0..5}.csv.gz
gzip -d github_events-2015-01-01-*.gz
复制代码
然后,您可以使用 psql
复制数据(注意,此数据需要数据库具有 UTF8
编码):
\COPY github_events FROM 'github_events-2015-01-01-0.csv' WITH (format CSV)
复制代码
注意:
没有跨分片的快照隔离的概念,这意味着与
COPY
并发运行的多分片SELECT
可能会看到它在某些分片上提交,但在其他分片上没有。 如果用户正在存储事件数据,他可能偶尔会观察到最近数据中的小间隙。 如果这是一个问题,则由应用程序来处理(例如,从查询中排除最新数据,或使用一些锁)。如果
COPY
未能打开分片放置的连接,那么它的行为方式与INSERT
相同,即将放置标记为非活动,除非没有更多活动的放置。 如果连接后发生任何其他故障,事务将回滚,因此不会更改元数据。
使用汇总缓存聚合
事件数据管道和实时仪表板等应用程序需要对大量数据进行亚秒级查询。使这些查询快速的一种方法是提前计算和保存聚合。 这称为“汇总”数据,它避免了在运行时处理原始数据的成本。 作为一个额外的好处,将时间序列数据汇总到每小时或每天的统计数据中也可以节省空间。 当不再需要其全部详细信息并且聚合足够时,可能会删除旧数据。
例如,这是一个通过 url
跟踪页面浏览量的分布式表:
CREATE TABLE page_views (
site_id int,
url text,
host_ip inet,
view_time timestamp default now(),
PRIMARY KEY (site_id, url)
);
SELECT create_distributed_table('page_views', 'site_id');
复制代码
一旦表中填充了数据,我们就可以运行聚合查询来计算每个 URL
每天的页面浏览量,限制到给定的站点和年份。
-- how many views per url per day on site 5?
SELECT view_time::date AS day, site_id, url, count(*) AS view_count
FROM page_views
WHERE site_id = 5 AND
view_time >= date '2016-01-01' AND view_time < date '2017-01-01'
GROUP BY view_time::date, site_id, url;
复制代码
上述设置有效,但有两个缺点。首先,当您重复执行聚合查询时,它必须遍历每个相关行并重新计算整个数据集的结果。 如果您使用此查询来呈现仪表板,则将聚合结果保存在每日页面浏览量表中并查询该表会更快。 其次,存储成本将随着数据量和可查询历史的长度成比例增长。 在实践中,您可能希望在短时间内保留原始事件并查看较长时间窗口内的历史图表。
为了获得这些好处,我们可以创建一个 daily_page_views
表来存储每日统计信息。
CREATE TABLE daily_page_views (
site_id int,
day date,
url text,
view_count bigint,
PRIMARY KEY (site_id, day, url)
);
SELECT create_distributed_table('daily_page_views', 'site_id');
复制代码
在此示例中,我们在 site_id
列上同时分配了 page_views
和 daily_page_views
。 这确保了与特定站点相对应的数据将位于同一节点上。 在每个节点上将两个表的行保持在一起可以最大限度地减少节点之间的网络流量并实现高度并行执行。
一旦我们创建了这个新的分布式表,我们就可以运行 INSERT INTO ... SELECT
将原始页面视图汇总到聚合表中。 在下文中,我们每天汇总页面浏览量。Citus
用户通常在一天结束后等待一段时间来运行这样的查询,以容纳迟到的数据。
-- roll up yesterday's data
INSERT INTO daily_page_views (day, site_id, url, view_count)
SELECT view_time::date AS day, site_id, url, count(*) AS view_count
FROM page_views
WHERE view_time >= date '2017-01-01' AND view_time < date '2017-01-02'
GROUP BY view_time::date, site_id, url;
-- now the results are available right out of the table
SELECT day, site_id, url, view_count
FROM daily_page_views
WHERE site_id = 5 AND
day >= date '2016-01-01' AND day < date '2017-01-01';
复制代码
上面的汇总查询汇总了前一天的数据并将其插入 daily_page_views
。 每天运行一次查询意味着不需要更新汇总表行,因为新一天的数据不会影响之前的行。
当处理迟到的数据或每天多次运行汇总查询时,情况会发生变化。 如果任何新行与汇总表中已有的天数匹配,则匹配计数应增加。 PostgreSQL
可以使用 “ON CONFLICT”
来处理这种情况, 这是它进行 upserts
的技术。 这是一个例子。
- upserts
-- roll up from a given date onward,
-- updating daily page views when necessary
INSERT INTO daily_page_views (day, site_id, url, view_count)
SELECT view_time::date AS day, site_id, url, count(*) AS view_count
FROM page_views
WHERE view_time >= date '2017-01-01'
GROUP BY view_time::date, site_id, url
ON CONFLICT (day, url, site_id) DO UPDATE SET
view_count = daily_page_views.view_count + EXCLUDED.view_count;
复制代码
更新和删除
您可以使用标准 PostgreSQL
UPDATE 和 DELETE 命令更新或删除分布式表中的行。
DELETE FROM github_events
WHERE repo_id IN (24509048, 24509049);
UPDATE github_events
SET event_public = TRUE
WHERE (org->>'id')::int = 5430905;
复制代码
当更新/删除
影响如上例中的多个分片时,Citus
默认使用单阶段提交协议。 为了提高安全性,您可以通过设置启用两阶段提交
SET citus.multi_shard_commit_protocol = '2pc';
复制代码
如果更新或删除仅影响单个分片,则它在单个工作节点内运行。在这种情况下,不需要启用 2PC
。 当按表的分布列更新或删除过滤器时,通常会发生这种情况:
-- since github_events is distributed by repo_id,
-- this will execute in a single worker node
DELETE FROM github_events
WHERE repo_id = 206084;
复制代码
此外,在处理单个分片时,Citus
支持 SELECT ... FOR UPDATE
。这是对象关系映射器 (ORM
) 有时使用的一种技术,用于安全地:
- 加载行
- 在应用程序代码中进行计算
- 根据计算更新行
选择要更新的行会对它们设置写锁定,以防止其他进程导致“丢失更新(lost update)”
异常。
BEGIN;
-- select events for a repo, but
-- lock them for writing
SELECT *
FROM github_events
WHERE repo_id = 206084
FOR UPDATE;
-- calculate a desired value event_public using
-- application logic that uses those rows...
-- now make the update
UPDATE github_events
SET event_public = :our_new_value
WHERE repo_id = 206084;
COMMIT;
复制代码
Cette fonctionnalité est prise en charge uniquement pour les tables de distribution et de référence de hachage, et uniquement replication_factor
pour les 1
tables avec as .
- replication_factor
Maximiser les performances d'écriture
Sur les grandes machines, INSERT
les UPDATE/DELETE
instructions peuvent évoluer jusqu'à environ 50,000
requêtes par seconde. Cependant, pour atteindre cette vitesse, vous devrez utiliser de nombreuses connexions parallèles à longue durée de vie et réfléchir à la manière de gérer le verrouillage. Pour plus d'informations, vous pouvez consulter la section Scale- Out Data Ingest de notre documentation.
- Ingestion de données évolutive
Suite
Citus Distributed PostgreSQL Cluster - Référence SQL (Créer et modifier le DDL de table distribuée)