高性能实时分析数据库:Apache Druid 数据管理教程 Configure data retention Append data Update data
文中内容仅限技术学习与代码实践参考,市场存在不确定性,技术分析需谨慎验证,不构成任何投资建议。
Druid 是一款高性能实时分析数据库,主要价值在于缩短洞察和行动的时间。Druid 专为需要快速查询和数据采集的工作流而设计。Druid 擅长于支持 UI、运行操作型(即席)查询或处理高并发性。您可以将 Druid 视为适用于各种用例的数据仓库的开源替代方案。
配置数据保留
Configure data retention
本教程演示如何对数据源配置 retention rules(保留规则),以设定将被保留或删除的数据时间区间。
本教程假设您已按照 single-machine quickstart 中所述下载 Apache Druid 并已在本地运行。
完成 Load a file 和 Query data 教程也会有所帮助。
载入示例数据
本教程将使用 Wikipedia edits 样本数据,并采用一个 ingestion task spec(摄取任务规范),该规范会为输入数据中的每个小时创建一个单独的 segment(段)。
摄取规范位于 quickstart/tutorial/retention-index.json
。请提交该规范,它会创建一个名为 retention-tutorial
的数据源:
bin/post-index-task --file quickstart/tutorial/retention-index.json --url http://localhost:8081
摄取完成后,在浏览器中访问 http://localhost:8888/unified-console.html#datasources 以打开 Web 控制台的 datasource 视图。
该视图显示可用数据源及每个数据源的 retention rules(保留规则)摘要:
目前 retention-tutorial
数据源尚未设置任何规则。注意,集群存在默认规则:在 _default_tier
中永久加载并保留 2 个副本。
这意味着所有数据无论时间戳如何都会被加载,并且每个 segment(段)将在默认 tier 中复制到两个 Historical 进程。
在本教程中,我们暂时忽略 tiering 和冗余概念。
点击 “Fully Available” 旁边的 “24 Segments” 链接,查看 retention-tutorial
数据源的 segments(段)。
segments 视图 (http://localhost:8888/unified-console.html#segments) 提供关于数据源包含哪些 segments 的信息。页面显示共有 24 个 segments,每个 segment 包含 2015-09-12 某一小时的数据:
设置保留规则
假设我们希望删除 2015-09-12 前 12 小时的数据,并保留后 12 小时的数据。
前往 datasources 视图,点击 retention-tutorial
数据源旁边 Cluster default: loadForever
的蓝色铅笔图标。
将弹出规则配置窗口:
现在点击两次 + New rule
按钮。
在上方的规则框中,选择 Load
和 by interval
,然后在 by interval
旁的字段中输入 2015-09-12T12:00:00.000Z/2015-09-13T00:00:00.000Z
。Replicas(副本数)可保持为 2,位于 _default_tier
。
在下方的规则框中,选择 Drop
和 forever
。
规则应如下所示:
现在点击 Next
。规则配置流程将要求输入用户名和注释,用于变更日志。您可以均输入 tutorial
。
现在点击 Save
。您可以在 datasources 视图中看到新规则:
等待集群几分钟以应用规则变更,然后前往 Web 控制台的 segments 视图。
2015-09-12 前 12 小时的 segments 现已消失:
最终得到的保留规则链如下:
-
loadByInterval 2015-09-12T12/2015-09-13(12 小时)
-
dropForever
-
loadForever(默认规则)
规则链自上而下进行评估,默认规则链始终添加在底部。
我们刚刚创建的教程规则链仅在数据位于指定的 12 小时区间内时加载数据。
如果数据不在 12 小时区间内,规则链接下来评估 dropForever
,这将删除任何数据。
dropForever
终止规则链,从而覆盖默认的 loadForever
规则,该规则在此规则链中将永远不会被触发。
请注意,在本教程中我们针对特定区间定义了加载规则。
如果您希望根据数据新旧来保留数据(例如,保留从 3 个月前到当前时间的数据),则应定义 Period load rule(周期加载规则)。
延伸阅读
- Load rules
追加数据
Append data
本教程演示如何使用 Apache Druid SQL 的 INSERT 函数向 datasource 追加数据,而不会影响现有数据。
示例使用 多阶段查询(MSQ) 任务引擎执行 SQL 语句。
前提条件
跟随本教程前,请先按快速开始(本地)下载 Druid 并在本地启动。无需向 Druid 集群加载任何数据。
您应熟悉 Druid 数据查询;如果尚未完成,请先阅读查询数据教程。
加载示例数据
使用 INSERT 和 EXTERN 函数加载示例数据集。EXTERN 函数允许读取外部数据或写入外部位置。
在 Druid Web 控制台中,进入 查询 视图并执行以下查询:
INSERT INTO "append_tutorial"
SELECTTIME_PARSE("timestamp") AS "__time","animal","number"
FROM TABLE(EXTERN('{"type":"inline","data":"{\"timestamp\":\"2024-01-01T07:01:35Z\",\"animal\":\"octopus\", \"number\":115}\n{\"timestamp\":\"2024-01-01T05:01:35Z\",\"animal\":\"mongoose\", \"number\":737}\n{\"timestamp\":\"2024-01-01T06:01:35Z\",\"animal\":\"snake\", \"number\":1234}\n{\"timestamp\":\"2024-01-01T01:01:35Z\",\"animal\":\"lion\", \"number\":300}\n{\"timestamp\":\"2024-01-02T07:01:35Z\",\"animal\":\"seahorse\", \"number\":115}\n{\"timestamp\":\"2024-01-02T05:01:35Z\",\"animal\":\"skunk\", \"number\":737}\n{\"timestamp\":\"2024-01-02T06:01:35Z\",\"animal\":\"iguana\", \"number\":1234}\n{\"timestamp\":\"2024-01-02T01:01:35Z\",\"animal\":\"opossum\", \"number\":300}"}','{"type":"json"}')) EXTEND ("timestamp" VARCHAR, "animal" VARCHAR, "number" BIGINT)
PARTITIONED BY DAY
生成的 append_tutorial
datasource 包含两天内八种动物的记录。
要查看结果,打开新标签页并执行以下查询:
SELECT * FROM "append_tutorial"
__time | animal | number |
---|---|---|
2024-01-01T01:01:35.000Z | lion | 300 |
2024-01-01T05:01:35.000Z | mongoose | 737 |
2024-01-01T06:01:35.000Z | snake | 1234 |
2024-01-01T07:01:35.000Z | octopus | 115 |
2024-01-02T01:01:35.000Z | opossum | 300 |
2024-01-02T05:01:35.000Z | skunk | 737 |
2024-01-02T06:01:35.000Z | iguana | 1234 |
2024-01-02T07:01:35.000Z | seahorse | 115 |
追加数据
可以使用 INSERT 函数向 datasource 追加数据,而不更改现有数据。
在新标签页中执行以下查询,以摄取并追加数据到 append_tutorial
datasource:
INSERT INTO "append_tutorial"
SELECTTIME_PARSE("timestamp") AS "__time","animal","number"
FROM TABLE(EXTERN('{"type":"inline","data":"{\"timestamp\":\"2024-01-03T01:09:35Z\",\"animal\":\"zebra\", \"number\":233}\n{\"timestamp\":\"2024-01-04T07:01:35Z\",\"animal\":\"bear\", \"number\":577}\n{\"timestamp\":\"2024-01-04T05:01:35Z\",\"animal\":\"falcon\", \"number\":848}\n{\"timestamp\":\"2024-01-04T06:01:35Z\",\"animal\":\"giraffe\", \"number\":113}\n{\"timestamp\":\"2024-01-04T01:01:35Z\",\"animal\":\"rhino\", \"number\":473}"}','{"type":"json"}')) EXTEND ("timestamp" VARCHAR, "animal" VARCHAR, "number" BIGINT)
PARTITIONED BY DAY
Druid 会在 seahorse
之后的日期添加行。
任务完成后,打开新标签页并执行以下查询查看结果:
SELECT * FROM "append_tutorial"
__time | animal | number |
---|---|---|
2024-01-01T01:01:35.000Z | lion | 300 |
2024-01-01T05:01:35.000Z | mongoose | 737 |
2024-01-01T06:01:35.000Z | snake | 1234 |
2024-01-01T07:01:35.000Z | octopus | 115 |
2024-01-02T01:01:35.000Z | opossum | 300 |
2024-01-02T05:01:35.000Z | skunk | 737 |
2024-01-02T06:01:35.000Z | iguana | 1234 |
2024-01-02T07:01:35.000Z | seahorse | 115 |
2024-01-03T01:09:35.000Z | zebra | 233 |
2024-01-04T01:01:35.000Z | rhino | 473 |
2024-01-04T05:01:35.000Z | falcon | 848 |
2024-01-04T06:01:35.000Z | giraffe | 113 |
2024-01-04T07:01:35.000Z | bear | 577 |
了解更多
参阅以下主题获取更多信息:
- 基于 SQL 的摄取参考——MSQ 架构参考
- 基于 SQL 的摄取查询示例——使用 MSQ 任务引擎的示例查询
更新数据
Update data
Apache Druid 通过 segment files 按时间分区来存储数据和索引。
Druid 创建 segment 后,其内容无法被修改。
你可以选择替换整个 segment 的数据,或者在某些情况下,通过 overshadow 覆盖 segment 数据的一部分。
在 Druid 中,使用 time ranges 来指定要更新的数据,而不是像事务数据库那样使用主键或维度。
指定替换时间范围之外的数据不受影响。
你可以利用这一功能执行数据更新、插入和删除,类似于事务数据库的 UPSERT 功能。
本教程演示如何使用 Druid SQL 的 REPLACE 函数结合 OVERWRITE 子句来更新已有数据。
教程涵盖以下使用场景:
- 覆盖全部数据
- 覆盖特定时间范围的记录
- 通过部分 segment overshadowing 更新某一行
所有示例均使用 multi-stage query (MSQ) 任务引擎执行 SQL 语句。
前置条件
在开始本教程之前,请按照 Quickstart (local) 下载 Druid 并在本地启动。无需向 Druid 集群加载任何数据。
你应该熟悉 Druid 的数据查询;如果尚未完成,请先学习 Query data 教程。
加载示例数据
使用 REPLACE 和 EXTERN 函数加载示例数据集。
在 Druid SQL 中,REPLACE 函数可以创建新的 datasource 或更新已有 datasource。
在 Druid web console 中,进入 Query 视图并运行以下查询:
REPLACE INTO "update_tutorial" OVERWRITE ALL
WITH "ext" AS (SELECT *FROM TABLE(EXTERN('{"type":"inline","data":"{\"timestamp\":\"2024-01-01T07:01:35Z\",\"animal\":\"octopus\", \"number\":115}\n{\"timestamp\":\"2024-01-01T05:01:35Z\",\"animal\":\"mongoose\", \"number\":737}\n{\"timestamp\":\"2024-01-01T06:01:35Z\",\"animal\":\"snake\", \"number\":1234}\n{\"timestamp\":\"2024-01-01T01:01:35Z\",\"animal\":\"lion\", \"number\":300}\n{\"timestamp\":\"2024-01-02T07:01:35Z\",\"animal\":\"seahorse\", \"number\":115}\n{\"timestamp\":\"2024-01-02T05:01:35Z\",\"animal\":\"skunk\", \"number\":737}\n{\"timestamp\":\"2024-01-02T06:01:35Z\",\"animal\":\"iguana\", \"number\":1234}\n{\"timestamp\":\"2024-01-02T01:01:35Z\",\"animal\":\"opossum\", \"number\":300}"}','{"type":"json"}')) EXTEND ("timestamp" VARCHAR, "animal" VARCHAR, "number" BIGINT)
)
SELECTTIME_PARSE("timestamp") AS "__time","animal","number"
FROM "ext"
PARTITIONED BY DAY
在生成的 update_tutorial
datasource 中,单行由 __time
、animal
和 number
唯一标识。
查看结果,请打开新标签页并运行以下查询:
SELECT * FROM "update_tutorial"
__time | animal | number |
---|---|---|
2024-01-01T01:01:35.000Z | lion | 300 |
2024-01-01T05:01:35.000Z | mongoose | 737 |
2024-01-01T06:01:35.000Z | snake | 1234 |
2024-01-01T07:01:35.000Z | octopus | 115 |
2024-01-02T01:01:35.000Z | opossum | 300 |
2024-01-02T05:01:35.000Z | skunk | 737 |
2024-01-02T06:01:35.000Z | iguana | 1234 |
2024-01-02T07:01:35.000Z | seahorse | 115 |
结果包含两天共八条动物记录。
覆盖全部数据
你可以使用 REPLACE 函数配合 OVERWRITE ALL 替换整个 datasource,同时丢弃旧数据。
在 web console 中打开新标签页,运行以下查询以覆盖 update_tutorial
datasource 的全部时间戳数据:
REPLACE INTO "update_tutorial" OVERWRITE ALL
WITH "ext" AS (SELECT *
FROM TABLE(EXTERN('{"type":"inline","data":"{\"timestamp\":\"2024-01-02T07:01:35Z\",\"animal\":\"octopus\", \"number\":115}\n{\"timestamp\":\"2024-01-02T05:01:35Z\",\"animal\":\"mongoose\", \"number\":737}\n{\"timestamp\":\"2024-01-02T06:01:35Z\",\"animal\":\"snake\", \"number\":1234}\n{\"timestamp\":\"2024-01-02T01:01:35Z\",\"animal\":\"lion\", \"number\":300}\n{\"timestamp\":\"2024-01-03T07:01:35Z\",\"animal\":\"seahorse\", \"number\":115}\n{\"timestamp\":\"2024-01-03T05:01:35Z\",\"animal\":\"skunk\", \"number\":737}\n{\"timestamp\":\"2024-01-03T06:01:35Z\",\"animal\":\"iguana\", \"number\":1234}\n{\"timestamp\":\"2024-01-03T01:01:35Z\",\"animal\":\"opossum\", \"number\":300}"}','{"type":"json"}')
) EXTEND ("timestamp" VARCHAR, "animal" VARCHAR, "number" BIGINT))
SELECTTIME_PARSE("timestamp") AS "__time","animal","number"
FROM "ext"
PARTITIONED BY DAY
__time | animal | number |
---|---|---|
2024-01-02T01:01:35.000Z | lion | 300 |
2024-01-02T05:01:35.000Z | mongoose | 737 |
2024-01-02T06:01:35.000Z | snake | 1234 |
2024-01-02T07:01:35.000Z | octopus | 115 |
2024-01-03T01:01:35.000Z | opossum | 300 |
2024-01-03T05:01:35.000Z | skunk | 737 |
2024-01-03T06:01:35.000Z | iguana | 1234 |
2024-01-03T07:01:35.000Z | seahorse | 115 |
注意 __time
列的值整体向后推迟一天。
覆盖特定时间范围的记录
你可以使用 REPLACE 函数覆盖 datasource 的特定时间范围。覆盖特定时间范围时,该时间范围必须与 PARTITIONED BY 子句指定的粒度对齐。
在 web console 打开新标签页,运行以下查询以插入新行并更新特定行。注意 OVERWRITE WHERE 子句指示仅更新 2024-01-03 的记录。
REPLACE INTO "update_tutorial"OVERWRITE WHERE "__time" >= TIMESTAMP'2024-01-03 00:00:00' AND "__time" < TIMESTAMP'2024-01-04 00:00:00'
WITH "ext" AS (SELECT *
FROM TABLE(EXTERN('{"type":"inline","data":"{\"timestamp\":\"2024-01-03T01:01:35Z\",\"animal\":\"tiger\", \"number\":300}\n{\"timestamp\":\"2024-01-03T07:01:35Z\",\"animal\":\"seahorse\", \"number\":500}\n{\"timestamp\":\"2024-01-03T05:01:35Z\",\"animal\":\"polecat\", \"number\":626}\n{\"timestamp\":\"2024-01-03T06:01:35Z\",\"animal\":\"iguana\", \"number\":300}\n{\"timestamp\":\"2024-01-03T01:01:35Z\",\"animal\":\"flamingo\", \"number\":999}"}','{"type":"json"}')
) EXTEND ("timestamp" VARCHAR, "animal" VARCHAR, "number" BIGINT))
SELECTTIME_PARSE("timestamp") AS "__time","animal","number"
FROM "ext"
PARTITIONED BY DAY
__time | animal | number |
---|---|---|
2024-01-02T01:01:35.000Z | lion | 300 |
2024-01-02T05:01:35.000Z | mongoose | 737 |
2024-01-02T06:01:35.000Z | snake | 1234 |
2024-01-02T07:01:35.000Z | octopus | 115 |
2024-01-03T01:01:35.000Z | flamingo | 999 |
2024-01-03T01:01:35.000Z | tiger | 300 |
2024-01-03T05:01:35.000Z | polecat | 626 |
2024-01-03T06:01:35.000Z | iguana | 300 |
2024-01-03T07:01:35.000Z | seahorse | 500 |
注意 datasource 的变化:
- 新增一行
flamingo
。 opossum
行已变为tiger
。skunk
行已变为polecat
。iguana
和seahorse
行的数值已不同。
通过部分 segment overshadowing 更新一行
在 Druid 中,你可以通过为整个 segment 或 segment 的某分区叠加较新数据来覆盖旧数据。
该能力称为 overshadowing。
你可以使用部分 overshadowing 通过添加比现有数据粒度更细的新 segment 来更新单行。
这是一个比替换整块时间数据更少见的方法。
以下示例演示如何使用混合 segment 粒度的部分 overshadowing 更新数据。
注意示例要点:
- 查询更新特定
number
行的单条记录。 - 原始 datasource 使用 DAY 粒度 segment。
- 新数据 segment 使用 HOUR 粒度,表示的时间范围小于现有数据。
- OVERWRITE WHERE 与 WHERE TIME_IN_INTERVAL 子句分别指定更新目标位置与更新来源。
- 查询会替换指定区间内的所有内容。如需仅更新该区间内一部分数据,必须在 SELECT 列表中使用 CASE 函数携带所有记录并仅修改所需部分。
REPLACE INTO "update_tutorial"OVERWRITEWHERE "__time" >= TIMESTAMP'2024-01-03 05:00:00' AND "__time" < TIMESTAMP'2024-01-03 06:00:00'
SELECT"__time","animal",CAST(486 AS BIGINT) AS "number"
FROM "update_tutorial"
WHERE TIME_IN_INTERVAL("__time", '2024-01-03T05:01:35Z/PT1S')
PARTITIONED BY FLOOR(__time TO HOUR)
__time | animal | number |
---|---|---|
2024-01-02T01:01:35.000Z | lion | 300 |
2024-01-02T05:01:35.000Z | mongoose | 737 |
2024-01-02T06:01:35.000Z | snake | 1234 |
2024-01-02T07:01:35.000Z | octopus | 115 |
2024-01-03T01:01:35.000Z | flamingo | 999 |
2024-01-03T01:01:35.000Z | tiger | 300 |
2024-01-03T05:01:35.000Z | polecat | 486 |
2024-01-03T06:01:35.000Z | iguana | 300 |
2024-01-03T07:01:35.000Z | seahorse | 500 |
注意 polecat
的 number
已从 626 变为 486。
多次执行部分 segment overshadowing 可能造成 segment 碎片化并影响查询性能,请使用 compaction 进行修正。
了解更多
参见以下主题以获取更多信息:
- Data updates 了解 Druid 数据更新的概览。
- Load files with SQL-based ingestion 生成引用外部托管数据的查询。
- Overwrite data with REPLACE 了解 MSQ 任务引擎如何执行 SQL REPLACE 查询。
风险提示与免责声明
本文内容基于公开信息研究整理,不构成任何形式的投资建议。历史表现不应作为未来收益保证,市场存在不可预见的波动风险。投资者需结合自身财务状况及风险承受能力独立决策,并自行承担交易结果。作者及发布方不对任何依据本文操作导致的损失承担法律责任。市场有风险,投资须谨慎。