从 Postgres 到 ClickHouse:数据建模指南

news2024/12/24 21:14:39

图片

本文字数:7149;估计阅读时间:18 分钟

作者:Sai Srirampur

本文在公众号【ClickHouseInc】首发

图片

上个月,我们收购了专注于 Postgres CDC 的 PeerDB。PeerDB 使得数据从 Postgres 复制到 ClickHouse 变得既快速又简单。PeerDB 的用户经常会问,在数据复制到 ClickHouse 后,如何进行数据建模以充分发挥 ClickHouse 的优势。

这一问题的产生源于 ClickHouse 和 Postgres 在数据建模上的差异。两者分别为各自特定的工作负载量身打造——Postgres 是事务型(OLTP)数据库,而 ClickHouse 则是专注于分析的列式(OLAP)数据库。本指南将帮助来自 Postgres 的用户掌握在 ClickHouse 中关键的数据建模概念。这是该系列博客的第一篇,更多内容将在未来发布。

ReplacingMergeTree 表引擎

PeerDB 使用 ReplacingMergeTree 引擎将 PostgreSQL 表映射到 ClickHouse。ClickHouse 在追加写入的场景下表现最佳,并不推荐频繁的 UPDATE 操作,而 ReplacingMergeTree 正是专为此类需求而设计的。

ReplacingMergeTree 支持既包含数据导入又包含数据修改的工作负载。每个表都是追加写入的,用户的更新会作为带有版本号的 INSERT 记录。ReplacingMergeTree 引擎在后台自动处理行数据的去重和合并。这是 ClickHouse 能够在实时数据导入方面表现出色的关键原因之一。

在 PeerDB 中,Postgres 的 INSERT 和 UPDATE 操作会以不同版本(使用 _peerdb_version)的新行形式写入 ClickHouse。ReplacingMergeTree 表引擎会定期通过排序键(ORDER BY 列)在后台处理去重,保留最新版本的行。而 PostgreSQL 的 DELETE 操作则会作为标记为已删除的新行(通过 _peerdb_is_deleted 列)进行处理。下方代码片段展示了 ClickHouse 中 public_goals 表的目标表定义。

clickhouse-cloud :) SHOW CREATE TABLE public_goals;
CREATE TABLE peerdb.public_goals
(
    `id` Int64,
    `owned_user_id` String,
    `goal_title` String,
    `goal_data` String,
    `enabled` Bool,
    `ts` DateTime64(6),
    `_peerdb_synced_at` DateTime64(9) DEFAULT now(),
    `_peerdb_is_deleted` Int8,
    `_peerdb_version` Int64
)
ENGINE = SharedReplacingMergeTree
('/clickhouse/tables/{uuid}/{shard}', '{replica}', _peerdb_version)
PRIMARY KEY id
ORDER BY id
SETTINGS index_granularity = 8192

你可能仍然会看到重复的行数据——该如何处理?

ReplacingMergeTree 在后台异步去重,但无法保证完全消除重复数据。因此,查询时你可能会遇到同一行或主键存在不同版本的重复情况。这是正常现象。要去除重复数据,你可以考虑以下几种方法:

在查询中使用 FINAL

ClickHouse 提供了一个独特的修饰符 FINAL,它可以在查询时进行去重(合并行数据)。这种去重操作发生在过滤(WHERE 子句)之后,聚合(GROUP BY)之前。

过去人们担心 FINAL 会影响查询性能。虽然 FINAL 确实对性能有一定影响,但 ClickHouse 的最新版本对其进行了显著优化。因此,你可以放心使用 FINAL 子句,并观察查询性能。以下是使用 FINAL 子句的示例:

SELECT owner_user_id, COUNT(*) FROM goals FINAL 
WHERE enabled = true GROUP BY owner_user_id;

使用 argMax 函数在查询时去重

ClickHouse 提供了 argMax 函数,用于在查询时动态去除重复行。尤其在需要根据版本号或时间戳保留最新记录时,它非常有用。

例如,对于 peerdb.public_goals 表,如果 id 是主键,_peerdb_version 记录版本号,你可以使用 argMax 选出每个 id 下版本号最高的行。这种方式可以在不改变底层数据的前提下去除重复项。接着,你可以对去重后的结果集执行子查询来进行聚合分析。以下是使用 argMax 的示例查询。

SELECT
    owned_user_id,
    COUNT(*) AS active_goals_count,
    MAX(ts) AS latest_goal_time
FROM
(
    SELECT
        id,
        argMax(owned_user_id, _peerdb_version) AS owned_user_id,
        argMax(goal_title, _peerdb_version) AS goal_title,
        argMax(goal_data, _peerdb_version) AS goal_data,
        argMax(enabled, _peerdb_version) AS enabled,
        argMax(ts, _peerdb_version) AS ts,
        argMax(_peerdb_synced_at, _peerdb_version) AS _peerdb_synced_at,
        argMax(_peerdb_is_deleted, _peerdb_version) AS _peerdb_is_deleted,
        max(_peerdb_version) AS _peerdb_version
    FROM peerdb.public_goals
    WHERE enabled = true
    GROUP BY id
) AS deduplicated_goals
GROUP BY owned_user_id;

使用窗口函数

你也可以利用 ClickHouse 的窗口函数,通过在每个 id 分区中选出 _peerdb_version 最高的行来实现去重。下面是一个示例:

SELECT
    owned_user_id,
    COUNT(*) AS active_goals_count,
    MAX(ts) AS latest_goal_time
FROM
(
    SELECT
        *,
        ROW_NUMBER() OVER (PARTITION BY id ORDER BY _peerdb_version DESC) AS rn
    FROM peerdb.public_goals
    WHERE enabled = true
) AS ranked_goals
WHERE rn = 1
GROUP BY owned_user_id;

通过视图简化去重

将去重逻辑封装在视图中,使得 BI 工具能够更方便地查询最新数据。例如,在视图中使用窗口函数,只保留每行的最新版本:

CREATE VIEW goals AS
SELECT * FROM
(
    SELECT
        *,
        ROW_NUMBER() OVER (PARTITION BY id ORDER BY _peerdb_version DESC) AS rn
    FROM peerdb.public_goals
    WHERE enabled = true
) WHERE rn = 1;
SELECT
    owned_user_id,
    COUNT(*) AS active_goals_count,
    MAX(ts) AS latest_goal_time
FROM goals
GROUP BY owned_user_id;

可为空列

对于习惯使用 Postgres 的用户来说,ClickHouse 的一个特别之处在于,它不会存储列的 NULL 值,除非你明确将列类型定义为 Nullable。例如,对于日期列,ClickHouse 会将 1970-01-01 作为默认值,而不是存储 NULL,这可能让人意外。这是因为存储 NULL 值会影响 ClickHouse 作为列式数据库的查询性能。因此,ClickHouse 需要用户显式定义 Nullable 类型。

在 PeerDB 中,我们引入了一个名为 PEERDB_NULLABLE 的设置。当设置为 true 时,该功能会在复制过程中自动检测 Postgres 中的可为空列,并在 ClickHouse 中将其标记为 Nullable。这样你在复制过程中无需手动定义 Nullable 类型。有关此功能的更多信息,请查看以下 PR。

数据类型

ClickHouse 提供了丰富的数据类型,从数字、文本、时间戳、日期、数组,到新引入的 JSON 类型。在大多数情况下,Postgres 中的数据类型可以直接存储到 ClickHouse,而无需进行复杂的转换。

以下是我们在 PeerDB 中从 Postgres 复制数据到 ClickHouse 时使用的数据类型对照表,供参考。

排序键

什么是排序键?

在 ClickHouse 中,选择合适的排序键对查询性能至关重要。排序键由创建表时的 ORDER BY 子句定义,类似于 Postgres 中的索引,但其专为分析型工作负载进行优化。与 Postgres 中使用的 B-tree 树索引不同,ClickHouse 使用稀疏索引:

  1. 数据按排序键排序

    排序键确保磁盘上的数据按照指定的列进行排序。

    这种排序有助于提升数据压缩效果,因为相似的值会集中存储在一起。

  2. 排序键还生成稀疏索引

    排序键还会生成稀疏索引,仅存储列的范围,每个条目指向一组已排序的行。

    这保持了索引的小巧,使 ClickHouse 能够通过二进制搜索快速定位相关行组,从而高效执行查询。

    你可以在此处相关内容【https://clickhouse.com/docs/en/migrations/postgresql/designing-schemas#primary-ordering-keys-in-clickhouse】。

你可以将排序键与 Postgres 中的 BRIN 索引类比,但在 ClickHouse 中,数据会通过异步合并部分自动按排序键排序,无需在数据导入时手动排序。

如何选择合适的排序键

选择排序键时,优先考虑在查询的 WHERE 子句中最常使用的列,并根据列基数(即唯一值的数量)升序排列——从具有最少不同值的列开始。这样能够优化数据压缩与查询性能。有关该主题的更多深入探讨,请参考此处的详细指南【https://clickhouse.com/docs/en/data-modeling/schema-design#choosing-an-ordering-key】。

主键 (PRIMARY KEY) 与排序键 (Ordering Key) 的区别

在 public_goals 表的定义中,你可能会注意到它设置了主键 (PRIMARY KEY)。那么,主键与排序键 (Ordering Key) 之间的区别是什么呢?让我们深入探讨它们的不同之处:

  1. 如果定义了主键 (PRIMARY KEY),它将指定稀疏索引中的列,而 ORDER BY 子句则决定数据在磁盘上的排序方式。此外,主键和排序键也用于 ReplacingMergeTree 引擎的去重操作。

  2. 如果没有明确指定主键 (PRIMARY KEY),则排序键 (Ordering Key) 会自动作为主键,定义稀疏索引中的列。

注意:主键 (PRIMARY KEY) 中的列应该在排序键 (Ordering Key) 中优先排列。这可以确保索引和物理数据的排序一致,从而减少不必要的数据扫描,提升查询性能。

主键与排序键不同的应用场景

在一些场景中,主键和 ORDER BY 子句的列可能不完全相同。例如,如果你的查询大多基于 customer_id 进行过滤而非 id,这时你可以在 customer_id 上设置主键 (PRIMARY KEY),而 ORDER BY 子句中包括 customer_id 和 id。这样可以确保稀疏索引在查询时更紧凑高效,并且通过 id 进行数据去重,确保数据不会丢失。

注意:与 PostgreSQL 中的主键不同,PostgreSQL 的主键通过 B-tree 索引确保数据唯一性,而在 ClickHouse 中,主键并不强制唯一性。它主要用于指定哪些列应该被纳入稀疏索引中。

修改排序键 (Ordering Key)

在 ClickHouse 中,选择合适的排序键对于查询性能至关重要,因为它相当于查询数据时的索引。默认情况下,PeerDB 会将 PostgreSQL 的主键 (PRIMARY KEY) 用作 ClickHouse 表的排序键 (Ordering Key),但你可以通过以下方法进行更改:

使用物化视图 (Materialized Views)

通过物化视图,你可以创建一个适用于特定工作负载的表,使用不同的排序键。在定义排序键时,确保主键 (PRIMARY KEY) 列位于排序键的末尾。这是因为 ReplacingMergeTree 引擎在去重时依赖 ORDER BY 子句的顺序,包含主键可以确保数据不会丢失。

CREATE MATERIALIZED VIEW goals_mv
ENGINE = ReplacingMergeTree(_peerdb_version)
ORDER BY (enabled, ts, id)  POPULATE AS
SELECT * FROM peerdb.public_goals;

注意:在创建物化视图后,务必按照前述处理重复数据的步骤进行操作,以确保在查询时正确去重。

预先定义目标表及所需排序键

如果需要更改排序键,可以预先定义一个包含目标排序键的新表,并将其替换现有表。具体步骤如下:

1. 创建虚拟镜像:首先在 PeerDB 中创建一个虚拟镜像,它将生成一个具有正确元数据列和数据类型的默认表。

2. 创建新表并设置排序键:基于 PeerDB 创建的表定义一个新表,包含你需要的排序键。在排序键中,确保将主键列放在末尾以确保正确的去重。

CREATE TABLE public_events_new AS public_events
ENGINE = ReplacingMergeTree(_peerdb_version)
ORDER BY (user_id,id);

3. 删除旧表:将旧表删除以腾出空间。

DROP TABLE public_events;

4. 重命名新表:将新表命名为原表的名称。

RENAME TABLE public_events_new TO public_events;

5. 配置镜像指向新表:通过调整配置,使 PeerDB 的镜像指向新表。PeerDB 在后台使用 CREATE TABLE IF NOT EXISTS 命令,确保数据继续写入新表。

处理 DELETE 操作

正如前面提到的,PostgreSQL 中的 DELETE 操作会以标记为已删除的行(通过 _peerdb_is_deleted 列)形式同步到 ClickHouse。如果你希望在查询中排除这些已删除的数据,可以基于 _peerdb_is_deleted 列在 ClickHouse 中创建行级别的策略 (row-level policy)。例如:

CREATE ROW POLICY policy_name ON table_name
FOR SELECT USING _peerdb_is_deleted = 0;

这个策略会确保在查询表数据时,只有 _peerdb_is_deleted 等于 0 的行是可见的。

总结

希望这篇博客对你有所帮助。我尽量涵盖了从 PostgreSQL 迁移到 ClickHouse 过程中常见的数据建模挑战。在接下来的博客中,我将深入探讨更高级的话题,例如如何进行表连接 (joins) 和编写高效的 SQL 查询等。如果你有兴趣尝试 PeerDB 和 ClickHouse,并开始从 PostgreSQL 复制数据到 ClickHouse,请查看下面的链接,或者直接联系我们!

1. 免费试用 ClickHouse Cloud【https://clickhouse.com/docs/en/cloud-quick-start】

2. 免费试用 PeerDB Cloud【https://auth.peerdb.cloud/en/signup?glxid=81340839-0371-47e4-aea0-f0b994d2c85d&pagePath=%2Fblog%2Fpostgres-to-clickhouse-data-modeling-tips&origPath=%2Fblog%2Fhow-to-learn-clickhouse-and-become-a-certified-clickhouse-developer&experiments=mktg-website-nav-cta-btn%3A0%2Cmktg-website-rockset-eyebrow%3A0】

3. Postgres 到 ClickHouse 复制文档 【https://docs.peerdb.io/mirror/cdc-pg-clickhouse】

4. 直接与 PeerDB 团队联系【https://www.peerdb.io/sign-up】

征稿启示

面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出&图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:Tracy.Wang@clickhouse.com

​​联系我们

手机号:13910395701

邮箱:Tracy.Wang@clickhouse.com

满足您所有的在线分析列式数据库管理需求

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2134715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iceoryx共享内存通信

共享内存原理 当POSIX系统中的进程启动时,它会被赋予自己的虚拟地址空间。 虚拟地址空间跨越的范围对于不同的进程可能是相同的,但是在特定地址可访问的数据对于每个进程可能是不同的。 在进程的虚拟地址空间内,有许多“内存区域”用于加载或映射数据。这些内存区域通常是…

内存魔术师:精通内存函数的艺术

嘿嘿,家人们,今天咱们来详细剖析C语言中的内存函数,好啦,废话不多讲,开干! 目录 1.memcpy使用与模拟实现 1.1:memcpy的使用 1.2:memcpy的模拟实现 2:memmove的使用与模拟实现 2.1:memmove的使用 2.1.1:memcpy处理重叠空间 2.1.2:memmove处理重叠空间 2.2:memove的模拟实…

【机器学习随笔】基于kmeans的车牌类型分类注意点

kmeans是无监督的聚类算法,可用于数据的分类。本文尝试用kmeans对车牌类型进行分类,记录使用过程中的注意点。 kmeans使用过程中涉及两个大部分,模型与分析。模型部分包括训练模型和使用模型,分析部分主要为可视化分析。两部分的主…

这东西有点上头,不小心刷到天亮了。。。

相信很多每天勤奋刷题的小伙伴已经发现了,面试鸭又又又升级更新了! 打开首页就让人眼前一亮,优化了岗位分类导航栏,找起目标题库更轻松了。毕竟鸭鸭目前已经有 6000 道面试题、上百个题库,一不小心就会淹没在浩瀚题海…

如何优化MySql的性能

优化MySQL的性能是一个复杂但至关重要的任务,它涉及到多个层面的调整和优化。以下是一些关键的步骤和策略,可以帮助你提高MySQL数据库的性能: 1. 优化数据库设计 选择合适的数据类型:确保你使用的数据类型是适合你的数据的&#…

Three.js 实战【4】—— 3D地图渲染

初始化场景&准备工作 在vue3threejs当中,初始化场景的代码基本上是一样的,可以参考前面几篇文章的初始化场景代码。在这里进行渲染3D地图还需要用到d3这个库,所以需要安装一下d3,直接npm i即可。 再从阿里云这里提供的全国各…

SQL server 6.5升级到SQL server 2019的方法

背景: 对日项目,客户的旧系统的数据库用的是SQL server 6.5,操作系统是windows NT。新系统要求升级到SQL server 2019,查了下资料发现旧系统的版本实在是太久远了,90年代的。 数据库部分的升级思路是这样的&#xff…

git 更新LingDongGui问题解决

今天重新更新灵动gui的代码,以便使用最新的arm-2d,本来以为是比较简单的一件事情(因为以前已经更新过一次),却搞了大半天,折腾不易啊,简单记录下来,有同样遇到问题的同学参考&#x…

AI算法部署方式对比分析:哪种方案性价比最高?

随着人工智能技术的飞速发展,AI算法在各个领域的应用日益广泛。AI算法的部署方式直接关系到系统的性能、实时性、成本及安全性等多个方面。本文将探讨AI算法分析的三种主要部署方式:本地计算、边缘计算和云计算,并详细分析它们的优劣性。 一、…

基于vue框架的宠物交流平台1n2n3(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能:会员,宠物信息,宠物类型,团队信息,申请领养,团队申请,领养宠物 开题报告内容 基于Vue框架的宠物交流平台开题报告 一、项目背景 随着现代生活节奏的加快与人们情感需求的日益增长,宠物已成为众多家庭不可或缺的重要成员。…

基于Python的影视数据可视化---附源码75141

摘 要 本文基于Python语言,设计并实现了一个影视数据可视化系统,包括首页、公告通知、新闻资讯和电影信息等功能模块。通过对影视数据的采集、处理和可视化展示,该系统旨在为用户提供全面的影视信息和数据分析服务。在研究背景中&#xff0c…

编译运行 webAssembly(wasm)

环境准备&#xff1a; lunix下docker 参考https://hub.docker.com/r/emscripten/emsdk 拉编译环境 docker pull emscripten/emsdk 编译 随便找个目录&#xff0c;敲下面命令&#xff0c;编译一个webAssembly 程序 # create helloworld.cpp cat << EOF > hellowo…

Android Studio 新生成key store 打包apk报 Invalid keystore format

Android Studio 新生成key store 打包apk报错 Execution failed for task :app:packageDebug. > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade > com.android.ide.common.signing.KeytoolException: Failed …

充电宝什么品牌比较好用?2024年最值得推荐充电宝品牌!

近年来&#xff0c;随着电子设备使用需求的增加&#xff0c;充电宝市场呈现出蓬勃发展的态势。优秀的充电宝产品不仅能够提供稳定的充电速度&#xff0c;还具备方便携带的体验&#xff0c;深受用户喜爱。然而&#xff0c;面对市场上众多品牌和型号的选择&#xff0c;如何找到最…

C++库std::clamp

C库std::clamp std::clamp: 轻松掌握值的范围限制 目录 1. 引言2. std::clamp 基本概念2.1 函数签名2.2 参数说明2.3 返回值 3. 基本用法4. 深入理解 std::clamp4.1 实现原理4.2 注意事项 5. 高级用法5.1 自定义比较函数5.2 与 lambda 表达式结合 6. 实际应用场景6.1 图形编程…

全球安防监控、工业检测摄像机市场规模情况一览

一、全球安防监控市场规模情况综合分析 &#xff08;1&#xff09;全球安防监控摄像机市场规模 全球市场研究公司Research Nester统计&#xff0c;2023年全球安防监控摄像机市场规模为811.1亿元&#xff0c;预测到2028年&#xff0c;全球安全与监控市场规模预计将达到1869.3亿…

将 Parallels Desktop(PD虚拟机)安装在移动硬盘上,有影响吗?

当我们谈论在移动硬盘上安装 Parallels Desktop&#xff08;简称PD虚拟机&#xff09;及其对性能的影响时&#xff0c;特别是在运行如Unigraphics这样的资源密集型软件时&#xff0c;用户需要在便携性与性能之间找到最佳平衡。本文将深入探讨PD虚拟机装在移动硬盘有影响吗&…

(javaweb)mysql---DDL

一.数据模型&#xff0c;数据库操作 1.二维表&#xff1a;有行有列 2. 3.客户端连接数据库&#xff0c;发送sql语句给DBMS&#xff08;数据库管理系统&#xff09;&#xff0c;DBMS创建--以文件夹显示 二.表结构操作--创建 database和schema含义一样。 这样就显示出了之前的内容…

类和对象(中)【上篇】(构造,析构,拷贝函数)

&#x1f31f;个人主页&#xff1a;落叶 目录 类的默认成员函数 构造函数 无参构造 带参构造函数 全缺省构造函数 析构函数 对⽐C和C解决括号匹配问题 C语言版的Stack C版的Stack 拷⻉构造函数 类的默认成员函数 默认成员函数就是⽤⼾没有显式实现&#xff0c;编译器会…

如何查看微信聊天记录,防员工私单(有效监管员工电脑微信聊天的方法)

在企业管理中&#xff0c;防止员工私单&#xff08;即员工绕过公司直接与客户交易&#xff09;是管理中的一大难题。 许多员工使用微信进行日常工作沟通&#xff0c;而如果管理不到位&#xff0c;容易产生私单问题&#xff0c;影响企业的利益。 为了解决这一问题&#xff0c;…