Cluster PostgreSQL distribué Citus - Référence SQL (Ingest, Modify Data DML)

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 Githubarchivé .

/*
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, Citusdé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.

insertParfois, insertil 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)

CitusINSERT ... SELECTLes 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 CONFLICTclauses “更新插入(upserts)”, ce qui est le moyen le plus simple de faire des résumés distribués .

CitusDans , il existe trois manières selectd'insérer à partir d'une instruction. La première est si les tables source et cible sont colocalisées et que l' select/insertinstruction contient toutes deux des colonnes de distribution. Dans ce cas, Citusl' INSERT ... SELECTinstruction 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 SELECTrequête ne nécessite pas d'étape de fusion sur le coordinateur. Il ne fonctionne pas avec les SQLfonctions :

  • ORDER BY
  • LIMIT
  • OFFSET
  • GROUP BYLorsque la colonne de distribution ne fait pas partie de la groupclé
  • 按源表中的非分布列分区时的 Window(窗口)函数
  • 非同位表之间的Join(连接)(即重新分区连接)

当源表和目标表没有在同一位置,并且无法应用重新分区优化时,Citus 使用第三种方式执行 INSERT ... SELECT。 它从工作节点中选择结果,并将数据拉到协调节点。协调器将行重定向回适当的分片。 因为所有数据都必须通过单个节点,所以这种方法效率不高。

如果对 Citus 使用哪种方法有疑问,请使用 EXPLAIN 命令,如 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_viewsdaily_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 的技术。 这是一个例子。

-- 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 UPDATEDELETE 命令更新或删除分布式表中的行。

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) 有时使用的一种技术,用于安全地:

  1. 加载行
  2. 在应用程序代码中进行计算
  3. 根据计算更新行

选择要更新的行会对它们设置写锁定,以防止其他进程导致“丢失更新(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_factorpour les 1tables avec as .

Maximiser les performances d'écriture

Sur les grandes machines, INSERTles UPDATE/DELETEinstructions peuvent évoluer jusqu'à environ 50,000requê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.

Suite

Citus Distributed PostgreSQL Cluster - Référence SQL (Créer et modifier le DDL de table distribuée)

Je suppose que tu aimes

Origine juejin.im/post/7080008290112897031
conseillé
Classement