分布式 PostgreSQL 集群(Citus)官方示例 - 时间序列数据

本文涉及的产品
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
简介: 分布式 PostgreSQL 集群(Citus)官方示例 - 时间序列数据
  • 在时间序列工作负载中,应用程序(例如一些实时应用程序查询最近的信息,同时归档旧信息。
  • 为了处理这种工作负载,单节点 PostgreSQL 数据库通常会使用表分区将一个按时间排序的大数据表分解为多个继承表,每个表包含不同的时间范围。
  • 将数据存储在多个物理表中会加速数据过期。在单个大表中,删除行会产生扫描以查找要删除的行,然后清理清空空间的成本。另一方面,删除分区是一种与数据大小无关的快速操作。这相当于简单地删除磁盘上包含数据的文件。
  • 将数据存储在多个物理表中会加快数据过期的速度。在一个大表中,删除行需要扫描以找到要删除的行,然后清空空的空间。另一方面,删除分区是一种与数据大小无关的快速操作。这相当于简单地删除磁盘上包含数据的文件。
  • image.png


  • 对表进行分区还可以使每个日期范围内的索引更小更快。对最近数据进行的查询很可能对适合内存的 hot 索引进行操作。这加快了读取速度。


  • image.png


  • 插入也有更小的索引要更新,所以它们也更快。
  • image.gif

image.png


  • 在以下情况下,基于时间的分区最有意义:
  1. 大多数查询只访问最近数据的一个非常小的子集
  2. 旧数据定期过期(删除/丢弃)
  • 请记住,在错误的情况下,读取所有这些分区对开销的伤害大于帮助。但是,在正确的情况下,它非常有帮助。例如,保留一年的时间序列数据并定期仅查询最近一周。


扩展 Citus 上的时间序列数据



  • 我们可以将单节点表分区技术与 Citus 的分布式分片相结合,形成一个可扩展的时间序列数据库。这是两全其美的。它在 Postgres 的声明性表分区之上特别优雅。
  • image.gif
  • 例如,让我们 distributepartition 一个包含历史 GitHub 事件数据的表。
  • GitHub 事件数据
  • GitHub 数据集中的每条记录代表在 GitHub 中创建的事件,以及有关事件的关键信息,例如事件类型、创建日期和创建事件的用户。
  • 第一步是按时间创建和分区(partition)表,就像我们在单节点 PostgreSQL 数据库中一样:


-- declaratively partitioned table
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
) PARTITION BY RANGE (created_at);


  • 注意 PARTITION BY RANGE (created_at)。这告诉 Postgres 该表将由 created_at 列在有序范围内进行分区。不过,我们还没有为特定范围创建任何分区。
  • 在创建特定分区之前,让我们在 Citus 中分布表。我们将按 repo_id 进行分片,这意味着事件将被聚集到每个存储库的分片中。


SELECT create_distributed_table('github_events', 'repo_id');


  • 此时 Citus 已跨工作节点为该表创建分片。在内部,每个分片是一个表,每个分片标识符 N 的名称为 github_events_N。此外,Citus 传播了分区信息,每个分片都声明了 Partition key: RANGE (created_at)
  • 分区表不能直接包含数据,它更像是跨分区的视图。因此,分片还没有准备好保存数据。我们需要创建分区并指定它们的时间范围,之后我们可以插入与范围匹配的数据。


自动创建分区



  • Citus 为分区管理提供了辅助函数。我们可以使用 create_time_partitions() 创建一批每月分区:


SELECT create_time_partitions(
  table_name         := 'github_events',
  partition_interval := '1 month',
  end_at             := now() + '12 months'
);


  • Citus 还包括一个视图 time_partitions,以方便地调查它创建的分区。
  • image.png


  • 随着时间的推移,您将需要进行一些维护以创建新分区并删除旧分区。最好设置一个定期 job 来运行带有 pg_cron 之类的扩展的维护功能:
  • pg_cron


-- set two monthly cron jobs:
-- 1. ensure we have partitions for the next 12 months
SELECT cron.schedule('create-partitions', '0 0 1 * *', $$
  SELECT create_time_partitions(
      table_name         := 'github_events',
      partition_interval := '1 month',
      end_at             := now() + '12 months'
  )
$$);
-- 2. (optional) ensure we never have more than one year of data
SELECT cron.schedule('drop-partitions', '0 0 1 * *', $$
  CALL drop_old_time_partitions(
      'github_events',
      now() - interval '12 months' /* older_than */
  );
$$);


  • 一旦设置了定期维护,您就不必再考虑分区了,它们可以正常工作。
  • 请注意,Postgres 中的原生分区仍然很新,并且有一些怪癖。对分区表的维护操作将获取可能会短暂停止查询的激进锁。目前在 postgres 社区中正在进行大量工作来解决这些问题,因此预计 Postgres 中的 time 分区只会变得更好。


使用列式存储归档



  • 一些应用程序的数据在逻辑上分为可更新的小部分和“冻结(frozen)”的较大部分。示例包括日志、点击流或销售记录。在这种情况下,我们可以将分区与列式表存储(在 Citus 10 中引入)结合起来压缩磁盘上的历史分区。Citus 柱状表目前是仅追加的,这意味着它们不支持更新或删除,但我们可以将它们用于不可变的历史分区。
  • 列式表存储
  • 分区表可以由行分区列分区的任意组合组成。在 timestampkey 上使用范围分区时,我们可以将最新的分区制作成行表,并定期将最新的分区滚动到另一个历史列式分区中。
  • 让我们看一个例子,再次使用 GitHub 事件。我们将创建一个名为 github_columnar_events 的新表,以消除前面示例中的歧义。为了完全专注于列式存储方面,我们不会分布此表。
  • 接下来,下载示例数据:


wget http://examples.citusdata.com/github_archive/github_events-2015-01-01-{0..5}.csv.gz
gzip -c -d github_events-2015-01-01-*.gz >> github_events.csv


-- our new table, same structure as the example in
-- the previous section
CREATE TABLE github_columnar_events ( LIKE github_events )
PARTITION BY RANGE (created_at);
-- create partitions to hold two hours of data each
SELECT create_time_partitions(
  table_name         := 'github_columnar_events',
  partition_interval := '2 hours',
  start_from         := '2015-01-01 00:00:00',
  end_at             := '2015-01-01 08:00:00'
);
-- fill with sample data
-- (note that this data requires the database to have UTF8 encoding)
\COPY github_columnar_events FROM 'github_events.csv' WITH (format CSV)
-- list the partitions, and confirm they're
-- using row-based storage (heap access method)
SELECT partition, access_method
  FROM time_partitions
 WHERE parent_table = 'github_columnar_events'::regclass;


  • image.png


-- convert older partitions to use columnar storage
CALL alter_old_partitions_set_access_method(
  'github_columnar_events',
  '2015-01-01 06:00:00' /* older_than */,
  'columnar'
);
-- the old partitions are now columnar, while the
-- latest uses row storage and can be updated
SELECT partition, access_method
  FROM time_partitions
 WHERE parent_table = 'github_columnar_events'::regclass;


  • image.png


  • 要查看柱状表的压缩率,请使用 VACUUM VERBOSE。我们三个柱状分区的压缩比相当不错:


VACUUM VERBOSE github_columnar_events;


INFO:  statistics for "github_columnar_events_p2015_01_01_0000":
storage id: 10000000003
total file size: 4481024, total data size: 4444425
compression rate: 8.31x
total row count: 15129, stripe count: 1, average rows per stripe: 15129
chunk count: 18, containing data for dropped columns: 0, zstd compressed: 18
INFO:  statistics for "github_columnar_events_p2015_01_01_0200":
storage id: 10000000004
total file size: 3579904, total data size: 3548221
compression rate: 8.26x
total row count: 12714, stripe count: 1, average rows per stripe: 12714
chunk count: 18, containing data for dropped columns: 0, zstd compressed: 18
INFO:  statistics for "github_columnar_events_p2015_01_01_0400":
storage id: 10000000005
total file size: 2949120, total data size: 2917407
compression rate: 8.51x
total row count: 11756, stripe count: 1, average rows per stripe: 11756
chunk count: 18, containing data for dropped columns: 0, zstd compressed: 18


  • 分区表 github_columnar_events 的一个强大之处在于它可以像普通表一样被完整地查询。


SELECT COUNT(DISTINCT repo_id)
  FROM github_columnar_events;



  • 只要分区键上有一个 WHERE 子句,它可以完全过滤到行表分区中,条目就可以被更新或删除。


将行分区归档到列式存储



  • 当行分区已填满其范围时,您可以将其归档到压缩的列式存储中。我们可以使用 pg_cron 自动执行此操作,如下所示:


-- a monthly cron job
SELECT cron.schedule('compress-partitions', '0 0 1 * *', $$
  CALL alter_old_partitions_set_access_method(
    'github_columnar_events',
    now() - interval '6 months' /* older_than */,
    'columnar'
  );
$$);


  • 有关详细信息,请参阅列式存储。
  • 列式存储
相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
9天前
|
关系型数据库 分布式数据库 数据库
PostgreSQL+Citus分布式数据库
PostgreSQL+Citus分布式数据库
39 15
|
16天前
|
存储 缓存 算法
分布式缓存有哪些常用的数据分片算法?
【10月更文挑战第25天】在实际应用中,需要根据具体的业务需求、数据特征以及系统的可扩展性要求等因素综合考虑,选择合适的数据分片算法,以实现分布式缓存的高效运行和数据的合理分布。
|
20天前
|
存储 分布式计算 负载均衡
分布式计算模型和集群计算模型的区别
【10月更文挑战第18天】分布式计算模型和集群计算模型各有特点和优势,在实际应用中需要根据具体的需求和条件选择合适的计算架构模式,以达到最佳的计算效果和性能。
44 2
|
23天前
|
JSON 分布式计算 前端开发
前端的全栈之路Meteor篇(七):轻量的NoSql分布式数据协议同步协议DDP深度剖析
本文深入探讨了DDP(Distributed Data Protocol)协议,这是一种在Meteor框架中广泛使用的发布/订阅协议,支持实时数据同步。文章详细介绍了DDP的主要特点、消息类型、协议流程及其在Meteor中的应用,包括实时数据同步、用户界面响应、分布式计算、多客户端协作和离线支持等。通过学习DDP,开发者可以构建响应迅速、适应性强的现代Web应用。
|
16天前
|
存储 监控 大数据
构建高可用性ClickHouse集群:从单节点到分布式
【10月更文挑战第26天】随着业务的不断增长,单一的数据存储解决方案可能无法满足日益增加的数据处理需求。在大数据时代,数据库的性能、可扩展性和稳定性成为企业关注的重点。ClickHouse 是一个用于联机分析处理(OLAP)的列式数据库管理系统(DBMS),以其卓越的查询性能和高吞吐量而闻名。本文将从我的个人角度出发,分享如何将单节点 ClickHouse 扩展为高可用性的分布式集群,以提升系统的稳定性和可靠性。
43 0
|
1月前
|
分布式计算 Hadoop
Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
47 1
|
1月前
|
存储 SQL 消息中间件
Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
47 0
|
1月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
3月前
|
NoSQL Redis
基于Redis的高可用分布式锁——RedLock
这篇文章介绍了基于Redis的高可用分布式锁RedLock的概念、工作流程、获取和释放锁的方法,以及RedLock相比单机锁在高可用性上的优势,同时指出了其在某些特殊场景下的不足,并提到了ZooKeeper作为另一种实现分布式锁的方案。
110 2
基于Redis的高可用分布式锁——RedLock
|
7天前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
40 16

热门文章

最新文章