[TSDB] OpenGemini 综述:原理与架构

0 序: 续接《OpenGemini 安装指南》/《OpenGemini 运维指南》

1 概述:OpenGemini

OpenGemini

  • OpenGemini是一款面向物联网、车联网、工业互联网、运维监控等领域的,由华为发起的、基于 Go-Lang 语言开发的开源、分布式、时序数据库

相比同样是高性能的开源时序数据库 Top1 的 InfluxDB,OpenGemini 有如下优势:

  • 兼容 InfluxDB 的大部分特性
  • 架构分布式(节点横向扩展)

InfluxDB 的集群版,需采购商业付费版,而 开源的 OpenGemini 天然支持。

  • 读写分离

ts-store / ts-sql / ts-meta 进程

三大特点

OpenGemini是一款存储与分析并重的高性能时序数据库,具有的显著特点:

  • 开源

OpenGemini 采用的开源License是 Apache 2.0,对商业友好,伙伴和开发者可以基于openGemini发布自己的商业版本,也可以基于openGemini搭建运维监控系统,还可以基于openGemini开发监控类产品和服务、构建车联网、物联网以及工业物联网平台等。

  • 高性能

OpenGemini 从孵化到开源,长期背靠华为云SRE运维监控业务,在产品打磨的过程中造就了openGemini卓越的读写性能和高效的数据分析能力。

  • 分布式

单机版数据库始终受计算资源限制,无法获得更高的吞吐量和性能。
因此,openGemini从诞生一刻起就设计了分布式集群架构,具备良好的可扩展性和灵活性。

专注海量遥测数据存储分析场景

  • 近年来,随着云计算、AI、5G、物联网等众多新技术的发展和普及,数字化转型如火如荼,在车联网、制造业、物流、电力、物联网、工业互联网、运维监控等领域的数据量出现猛增。

例如大型车企一天采集的车辆数据就在PB级;TOP级云厂商每天采集的运维数据超过数十TB。

  • 面对如此海量的遥测数据,openGemini通过对上述场景中数据和业务特点深入了解,提出针对性的设计和技术优化方案,实现了集群高并发、高扩展、低时延、低成本的时序数据库系统。

image

  • 目前,openGemini已正式在华为云工业物联平台中商业化落地,同时也在支撑整个华为云的运维监控业务,在全网部署有约25套集群,最大集群规模为70节点,日均处理20TB数据,写TPS 4000万条/秒,读QPS 5万/秒。

  • 在openGemini开源的数个月里,和社区取得联系并正式接入业务进行测试和适配的已知企业有46家。后起之星火,大有燎原的态势。

6大能力凸显openGemini差异化竞争力

  • 性能优势:在openGemini差异化竞争力中,高性能是最重要的一项。

openGemini 相比开源 InfluxDB,简单查询场景提升 2 倍多,中等查询场景提升 5 倍多,复杂查询场景下,openGemini 依然可以快速响应,然而 InfluxDB 则出现 OOM 无法工作。
此外,openGemini 新研发的高基数引擎,支持时间线无上限,进一步扩大了应用范围。
需要了解与其他同类产品的性能对比,可以在官网找到联系方式进行索要。

image

除此之外,openGemini在数据存储和数据分析方面推出一系列实用功能,以此构建更多差异化竞争力,主要功能如下:

  • 流式聚合:流式聚合是一种前置聚合方式,一边写数据、一边对数据进行降采样.

目的是解决传统降采样方法从磁盘读取大量历史数据进行计算,造成I/O放大严重的问题。

  • 多级降采样:对于存量的历史数据,传统降采样方式会保留历史数据明细。

在某些场景下,历史数据明细并不重要,只需保留数据特征即可,多级降采样功能可以实现对历史数据明细的特征提取,并原地替换历史数据明细,可进一步降低50%的存储成本。

  • 日志检索:日志数据是一种特殊的时序数据,多数时序数据库支持日志存储,但仅仅是存储日志数据时还远远不够,日志检索和分析才是存储日志的最终目的。

主流针对日志的处理多使用ELK技术栈,但面对海量日志时,ES也变得很吃力。
openGemini采用动态分词方法,在内核实现了全文索引,且具有内存资源占用少,检索效率高的优点,欢迎大家试用和反馈。

  • 异常检测和预测:openGemini针对时序数据的最终应用开发了基于AI的数据分析框架,可实现对时序数据的异常检测和预测,可检测13种常见的异常场景,具有检测速度快、准确性高、流批一体的优点,让数据就近处理,提高数据分析效率。

  • 高基数引擎:高基数会带来索引膨胀,从而引起内存资源消耗过高读写性能降低,长期以来一直困扰着时序数据库的发展。

openGemini从AP系统中寻找到解决办法,研发了全新的高基数引擎HSCE,可支持时间线无上限。
目前核心能力已具备,正在完善高基数引擎下的各种聚合方法(计划2023年9月可完成)。

image

核心能力加持,场景应用更宽广

  • 除上述差异化能力之外,openGemini的核心能力还包括: 完全兼容InfluxDB 1.x APIs算子(函数)和数据行协议,可作为 Promethus 和 openTelemetry 的后端存储,支持数据可靠性(计划2023年9月份推出)、物化视图、数据分区分片(支持指定分区键)、数据保留策略等。

强大组件提升运维管理能力 (ts-monitor)

  • 为提升openGemini的运维效率,社区开发了ts-monitor组件,专门采集节点和内核指标,可搭配Grafana实现对openGemini运行状态的全面监控。

例如CPU和内存利用率、写入带宽、写时延、写并发、QPS等指标可以通过可视化界面一目了然。

image

拥抱生态,助力应用开发

  • 由于openGemini对InfluxDB的兼容。

因此,应用于InfluxDB的数据接入工具、SDK、数据洞察工具、大数据分析工具等都能直接应用在openGemini之上。

  • 操作系统方面,openGemini目前已经对主流Linux系统、X86和ARM64的CPU架构支持,下个版本上可支持MAC和Windows;
  • 云原生方面,openGemini支持Docker、K8s、KubeEdge等平台的部署,为方便在K8s部署,社区创建了openGemini-operator项目;
  • 数据迁移方面,提供了InfluxDB向openGemini的数据迁移工具,ES迁移数据到openGemini的工具正在开发中,预计8月份可提供;
  • 管理工具方面,数据导出已支持,备份恢复和GUI管理工具正在社区开发中,9月份可以和大家见面。

总结起来,openGemini支持多种主流开发语言和操作系统平台、与InfluxDB的第三方工具无缝衔接、支持多形态的部署及应用。

image

openGemini 优势

Q:时序数据库相比传统数据库有哪些核心特性,什么情况下有必要选择openGemini?

  • 相比传统数据库,openGemini的核心特性有:针对时序业务特点和时序数据特点做了垂直优化,在架构上:采用MPP架构,支持大规模横向扩展,满足更大的业务负载要求。在数据存储上:提供数据批量写入、时间范围分片、冷热分级存储、列式存储、数据压缩、LSM等能力,解决海量数据持续写入性能问题;在数据分析方面:提供数据预聚合、滑窗聚合、降采样、流式聚合、时序异常检测/预测、日志检索等功能,解决数据分析效率问题。总的来说,openGemini具备高性能、高扩展、低存储成本等优势。

  • 什么情况下有必要选择openGemini?前提是处理时序业务的情况下。

首先:如果使用了关系数据库存储时序数据,即使还未出现性能问题,也建议换到openGemini,因为迟早会出现性能问题。
其次:当你使用其他时序数据库时,比如InfluxDB、openTSDB等,如果已经出现性能问题,可以考虑openGemini。如果预期业务会有大的增长,接入的时间线/设备数量大幅增多,可以考虑openGemini。
再次:如果使用了NoSQL数据库,出现查询语言表达弱、存储成本较高、计算和内存资源长期消耗大,这种情况有必要选择使用openGemini。

Q:openGemini的成本如何?比如,相比其他时序数据库是否更具有成本优势。

  • 测试结果来看,TSBS生产的测试数据,共计25亿条,纯文本800GB,压缩后88GB,写入到openGemini后只有14GB,数据压缩比在60:1左右。

从实际生产环境来看,华为云SRE业务中,压缩比是15:1。
相比其他时序数据库(比如TD),相同数据集,openGemini数据压缩率更高,最终数据量比TD还少70%-200%不等。

Q:openGemini是否支持数据的加密和安全性控制?

比如可以对数据进行加密以保证数据的安全性。

  • openGemini支持数据传输加密,可以在配置文件中打开https,配置安全证书即可。

节点之间数据传输也支持数据加密。
另外,openGemini支持用户权限控制,并且普通用户只能看到自己创建的库和表。
参考文档:

Q:openGemini相比其他时序数据库有什么具体的差异化竞争力?

  • 大的方面有两个:性能和功能

性能方面,当前在Devops场景下,性能处于领先优势。
6大功能,提高差异化竞争力,包括高基数引擎(解决了高基数问题),基于AI的异常检测和预测(解决数据分析问题),多级降采样(进一步降低存储成本),日志存储于检索,流计算等。

Q:时序数据库灾备和恢复有什么解决方案吗

  • 进程级的灾备可以通过数据多副本看门狗实时监测进程状态并拉起进程的办法解决。
  • 跨机房可以通过数据双写、数据订阅的方式解决。
  • 跨地域的情况,通常不要求数据实时同步,可以定期通过全量/增量备份的方式解决。

Q:针对不同时序业务场景,openGemini扩展性能怎么样?是否支持组件单独扩展?

  • openGemini在华为云SRE业务中共部署有25套集群,最大集群规模是70节点,openGemini实际可以支持100+节点规模。支持openGemini的组件单独扩展。

Q:openGemini是否支持数据的分区和分片?例如,可以将数据分散存储在多个节点或服务器上以提高性能和可扩展性。

  • openGemini支持数据的分区和分片,默认按时间线分区,也可以按指定分区键分区。

默认按时间分片,创建database时设定分片的时间范围。
参考文档:

Q:如果发生故障,有哪些协议可以避免数据不一致的问题?

  • 首先,时序数据库一般没有数据更新的操作,所以并发写入不会造成数据不一致的情况。
  • 即使有更新操作,也是做历史数据的修正,不太可能出现并发修正同一条数据的情况。
  • 其次,raft协议主要用于主从节点数据同步,应用非常广泛,但它仍无法避免数据不一致问题,一旦出现问题,还需要人工干预

openGemini 未来发展

openGemini的发展规划和未来展望如何,计划是怎么样的向那个方向发展?openGemini未来的发展规划主要分为三个方面:

  • 性能方面:在运维监控和物联网场景上持续进行内核性能优化,很多优化的点可以做。

  • 功能方面:功能需求的来源有两个方面,一个是社区用户,另一个是社区伙伴。

未来还将在日志数据存储和检索方面新增更多的功能,还会支持调用链数据存储,打造可观测性领域统一的数据存储底座。
在物联网方面,将会联合伙伴开发更适合工业场景的功能。

  • 生态方面:社区会在生态工具上持续投入,打通数据采集-存储-分析全链路上的主流生态。

包括但不限于数据迁移工具、数据接入中间件、大数据生态集成工具、AI分析平台、可视化、报表、数据湖、数据集市等。

2 工作原理与架构

集群架构

  • openGemini整体上由ts-sql、ts-meta、ts-stores三个组件组成

  • ts-sql

对外提供统一的读写接口。在数据写入方面,校验接收到的数据格式,再根据时间线名称进行hash打散,转发数据到对应的ts-store节点进行存储;在数据查询方面,根据请求生成分布式查询计划,分发各子查询计划到每个ts-store节点,最后汇总数据并返回Client。

ts-sql是无状态的,可以根据业务负载进行横向扩展。

  • ts-meta

管理数据库系统中的数据库、表、数据分区、数据保留策略、集群等元数据信息。

  • ts-store

数据存储和查询。采用类LSM Tree结构,数据追加写入;数据查询时,执行子查询计划,从倒排索引中检索查询涉及的时间线,数据读取后,根据查询条件过滤数据,再返回数据到ts-sql。

同样可以根据业务负载进行ts-store节点的横向扩展,暂不支持缩容。

  • ts-server : 单机版 open gemini

多用于做监控库

WAL文件 vs. TSSP文件

OpenGemini是一款面向物联网、车联网、工业互联网、运维监控等领域的开源分布式时序数据库。

在openGemini中,WALWrite-Ahead Logging)文件和TSSPTime Series Storage Protocol)文件扮演着重要的角色,以下是关于它们的用途及关系的详细解释:

WAL文件

用途

  • WAL文件主要用于记录数据库中的所有写操作,确保数据的一致性持久性
  • 在数据写入内存的同时,这些操作会被记录到WAL文件中。
  • 如果数据库系统发生故障或崩溃,重启后可以通过重新执行WAL文件中的操作来恢复数据,从而保证数据的可靠性

TSSP文件

用途

  • TSSP文件是openGemini用于存储时序数据的主要文件格式。
  • 在数据写入openGemini后,系统会按照自己定义的数据格式重新组织数据,并压缩存储到后缀名为tssp的数据文件中。
  • 这些文件不仅包含了实际的数据值,还可能包含数据的元数据(如时间戳、标签等)以及数据的压缩和编码信息。
  • TSSP文件的设计旨在优化时序数据的存储和查询性能。

WAL文件与TSSP文件的关系

  • 数据写入流程:在openGemini中,数据首先被写入内存,并同时记录到WAL文件中。随后,这些数据会经过一系列的处理和压缩,最终存储到TSSP文件中。

因此,WAL文件可以看作是数据写入过程中的一个临时记录,而TSSP文件才是数据的最终存储形式

  • 故障恢复:在数据库系统发生故障或崩溃时,WAL文件可以用于恢复数据。通过重新执行WAL文件中的写操作,可以确保内存中的数据与TSSP文件中的数据保持一致,从而避免数据丢失或不一致的情况。

  • 性能优化

  • 虽然WAL文件在数据写入过程中增加了额外的写操作,但这一机制对于确保数据的可靠性和持久性至关重要;
  • 同时,通过优化TSSP文件的存储和查询性能,OpenGemini 可以在保证数据可靠性的同时,提供高效的数据处理能力。

综上所述,WAL文件和TSSP文件在openGemini中各自扮演着重要的角色,它们共同确保了数据的可靠性、持久性和高效处理能力。

OpenGemini 数据存储目录结构

数据存储目录结构

  • opengemini v1.2.0 - ts-store 数据存储目录结构
/data/gemini-data/ts-store-8401/wal                    := 顶层层级结构   := {ts-store.toml:data:store-wal-dir}/wal/
/data/gemini-data/ts-store-8401/data/bdp_dwd/1/autogen/ := 顶层层级结构   := {ts-store.toml:data:store-data-dir}/data/{database}/{EngineType}/{RetentionPolicy}/
	/2867_1769068800000000000_1769097600000000000_311   := Shard 目录结构 := {ShardId}_{ShardStartTime}_{ShardEndTime}_{IndexId}
		/compact_log                                    := Shard 内部组件之压缩日志目录 := /compact_log
		/tssp                                           := Shard 内部组件之数据存储根目录
			/dwd_device_signals_ri_0000                 := {Measurement}_{MeasurementHash分隔}
		20M    00000001-0003-00000000.tssp              := TSSP 文件      := {FileId}-{Level}-{MergeCount}.tssp
		2.8M    00000081-0003-00000000.tssp
		924K    00000101-0003-00000000.tssp
		220K    00000181-0001-00000000.tssp
		220K    00000189-0001-00000000.tssp
		220K    00000191-0001-00000000.tssp
		196K    00000199-0000-00000000.tssp
		196K    0000019a-0000-00000000.tssp
		196K    0000019b-0000-00000000.tssp
		196K    0000019c-0000-00000000.tssp
		196K    0000019d-0000-00000000.tssp
		196K    0000019e-0000-00000000.tssp
		196K    0000019f-0000-00000000.tssp

				/out-of-order                           := 乱序数据隔离目录
	/2865_1769040000000000000_1769068800000000000_309
	/2863_1769011200000000000_1769040000000000000_307
	/2861_1768982400000000000_1769011200000000000_305
    /... 
  • openGemini 维护两类不可变的 TSSP 文件:
  • 有序文件:按时间排序且可高效查询的数据
  • 无序(乱序)文件:延迟到达且在写入过程中无法直接合并到有序文件中的数据
  • 系统持续执行两种类型的后台操作:
  • 合并:将乱序文件与有序文件合并,以恢复时间顺序。
  • 压缩:将多个小文件合并成同一级别的大文件,以减少文件数量。
  • 推荐文献

顶层层级结构

  • 顶层层级结构
  • {EngineType} = 引擎类型
  • 1 代表的是 openGemini 默认的列式存储引擎
  • {RetentionPolicy} = 保留策略名称
  • autogen: 保留策略名称 (Retention Policy)。默认通常为 autogen。

Shard 目录结构

  • Shard 目录结构 : 进入 Shard 目录后,你会看到核心的数据组织
  • ShardId = 全局唯一的物理分片标识
  • 样例值 = 2867
  • {ShardStartTime} = Shard 开始时间 (纳秒)
  • 样例值 = 1769068800000000000
  • {ShardEndTime} = Shard 结束时间 (纳秒)
  • 样例值 = 1769097600000000000

注:通过转换可知,这2个时间戳相差 8 小时,说明你的 Shard Duration 设置为 8h

  • {IndexId} = 该 Shard 关联的倒排索引 ID
  • openGemini 为了提高效率,多个 Shard 可能会共用同一个索引文件(IndexGroup)。
  • 样例值 = 311

Shard 内部组件之压缩日志目录

  • Shard 内部组件之压缩日志目录
  • compact_log: 压缩日志。记录该 Shard 正在进行或已完成的合并(Compaction)操作,用于在进程崩溃后恢复任务状态,保证原子性。

Shard 内部组件之数据存储根目录(Time Series Storage Pod)

  • Shard 内部组件之数据存储根目录
  • tssp : Time Series Storage Pod。这是真正的列式数据存储根目录。
  • {Measurement}_{MeasurementHash分隔}

样例值 : dwd_device_signals_ri_0000 : Measurement 目录。

openGemini 将表(Measurement)的数据物理隔离。
后缀 _0000 是为了防止某些系统文件限制而做的 hash 分隔或溢出处理。

TSSP 文件

  • TSSP 文件 := {FileId}-{Level}-{MergeCount}.tssp
  • 推荐文献
部分 含义 解读
FileID 文件序号 如 0000018f,是一个递增的十六进制数。
Level 压缩层级 核心指标。0000 表示刚落盘的新文件。数值越大(如 0003),说明该文件是经过多次 Compaction 合并而成的“大文件”,查询性能通常更好。
MergeCount 合并计数 记录该文件参与合并的次数。

分析上述 TSSP 文件列表:

  • 有些文件是 0003 层级:说明这些是历史积累的、已经合并过的成熟数据。
  • 大量 0000 层级的文件(如 0000018a 到 00000190):说明近期该表有频繁的写入尚未触发合并,或者正在生成新的小文件。
  • 乱序数据隔离目录 := 'out-of-order'

这就是我们上一轮讨论的 “乱序隔离区”。
位置:它存在于每个 Measurement 目录下。
作用:当新到的 Point 时间戳早于该 Shard 已有的最大时间时,数据会落入这个 out-of-order 文件夹中,生成对应的 .tssp 文件。
状态:如果你看到这个目录下有文件,说明该表当前存在未处理的乱序数据,等待后台 Compaction 进程将其与外层目录的顺序文件合并。

  • 简单小结

从提供的目录结构看:

  • 写入活跃:0000 层级文件较多,说明有持续的数据流入。
  • 分片清晰:Shard 按照 8 小时滚动,符合中大型业务场景的配置。
  • 乱序监控:你可以定期查看 out-of-order 目录。如果里面的文件长时间不消失且体积巨大,说明系统 Compaction 能力不足 或 乱序程度极高。

ts-store 写数据的过程

  • openGemini 的 ts-store 是其核心存储引擎,负责时序数据的持久化、索引和查询执行。其写数据过程遵循了经典的时序数据库优化路径(类似于 LSM 树结构),但在高基数索引写性能上做了深度优化。

以下是 ts-store 处理写请求的详细全过程:

1. 接入与初步分发 (ts-sql 阶段)

在数据到达 ts-store 之前,ts-sql 节点会先进行预处理:

  • 格式校验:验证行协议(Line Protocol)是否合法。
  • 分片计算:根据数据的时间戳确定所属的 Shard(时间分区),并根据 Series Key(Measurement + Tags)进行 Hash,确定数据该发往哪个 ts-store 节点。
  • 批量发送:为了提高效率,ts-sql 会将数据封装成内部的二进制 Block 流,通过长连接发送给对应的 ts-store

2. ts-store 内部写流程

ts-store 接收到写请求后,内部会经历以下关键步骤:

① 序列号分配与反序列化 (Unmarshal)

  • ts-store 接收到二进制流后,会根据配置的并发数(通常对应 CPU 核数)进行调度。
  • 执行 Unmarshal,将二进制数据转换为内部的行(Rows)结构。

② 索引映射 (Indexing & Series ID)

这是处理“高基数”问题的核心。

  • Series Key 转 ID:系统检查该时间线是否已存在。如果不存在,则在倒排索引(Inverted Index)中创建新的条目,并分配一个唯一的 Series ID
  • 索引缓存:为了加速,openGemini 使用了高效的索引缓存机制,减少磁盘 I/O。

③ 写入预写日志 (WAL - Write Ahead Log)

  • 持久化保证:在数据进入内存之前,必须先写入 WAL。WAL 是顺序追加写入磁盘的,保证了即使系统宕机,内存中的数据也能通过日志恢复。
  • 写入优化:openGemini 的 WAL 采用批量写入和高效的编码方式,尽量降低磁盘 I/O 对响应时延的影响。

④ 写入内存缓冲区 (MemTable)

  • 数据暂存:数据被写入活跃的 MemTable 中。
  • 组织形式:在内存中,数据通常按 Series ID 和时间戳进行排序。这样可以确保后续落盘时,数据是局部有序的,极大提升查询效率。

3. 数据落盘 (Flush)

当 MemTable 达到一定阈值(如大小或时间间隔)时,会触发落盘流程:

  1. 变为 Immutable:当前的 MemTable 变为“只读”状态,同时开启一个新的 MemTable 接收新数据。
  2. 转换为 TSM 文件:后台线程将只读 MemTable 中的数据进行压缩、编码,并转换成 TSM (Time Series Merge) 格式。 (tssp文件)
  3. 列式存储:TSM 采用列式存储,不同类型的数据(Float, Integer, String 等)使用专门的压缩算法(如 Gorilla, Delta-encoding),压缩率通常可达 10:1 以上。

4. 后台维护 (Compaction)

数据落盘后,ts-store 还会进行持续的后台优化:

  • 合并小文件:将多个小的 TSM 文件合并成大的 TSM 文件,减少文件句柄占用和查询时的扫描范围。
  • 处理过期与删除:在合并过程中,彻底剔除已超过 Retention Policy(保留策略)或被标记删除的数据。

openGemini 写路径的性能亮点

特性 说明
写并发优化 通过 Shard 分区和多线程调度,充分利用多核 CPU 能力。
高基数引擎 针对千万级甚至亿级时间线设计,优化了索引存储和检索。
列式压缩 显著减少存储空间,同时提升 I/O 吞吐量。

如果你对 openGemini 的具体配置感兴趣,可研究如何优化 openGemini.conf 中的 [data][index] 配置以获得更高的写入吞吐。

数据多副本机制

  • 参考文献

这是一个实验特性,当前解决了多副本的稳定性问题,但还存在性能问题暂未解决

数据多副本机制

  • openGemini的数据副本能力,可以满足工业、能源、物联网、运维监控等领域对数据可靠性的要求。
  • 当前版本的数据副本主要基于raft协议实现,在大量数据密集写入的场景下,副本能力写性能有一定的影响。
  • 后续版本会不断迭代,进一步优化数据副本分布,并推出新的数据副本协议
  • 解决写性能损伤的问题(当前版本多副本写性能劣化比较严重)
  • 支持2数据副本,当前主要受raft协议限制,必须3个数据副本起

image

适用范围与局限性

  • 特别说明
  • 单机版本(ts-server)不支持数据副本
  • 当前为【DB级】数据副本,不支持【表级】数据副本
  • 集群中ts-store节点数量必须是副本数量的倍数。

换句话说,如果是3副本,ts-store节点数量至少是3个,可以是6个或者9个

  • v1.2.0及以下版本的集群不能平滑升级到多副本集群,需要做数据迁移。

社区提供数据导入导出工具以支撑数据迁移

  • 建议多副本集群ts-store节点规格更大一些。

因为副本数据同样会消耗存储和计算资源。尤其是出现故障时,其中一些节点因为需要业务接管,从而导致的压力会比原来更大,要为突发情况预留一些空间

配置/使用

  • 修改配置文件 openGemini.conf
[common]
ha-policy = "replication"

[meta]
ptnum-pernode = 2 # 每个节点的PT数量,推荐设置为2
  • 创建数据库,指定副本数量为3,目前副本数仅支持大于0的奇数。
CREATE DATABASE db0 WITH REPLICATION 3

PT是单词Partition的缩写,是openGemini数据管理的一个逻辑概念。
举个例子,假设有100台设备数据,如果数据库下面仅有一个PT,那么数据全部存到该PT下。如果有2个PT,那么每个PT会各自管理50台设备数据。
PT之间是可以并发处理数据,理论上如果资源充足的情况下,多PT性能会更优。

DB、PT、RP、SHARD、MEASUREMENT之间的关系

image

副本数据分布

示例1: ptnum-pernode = 1,ts-store 3个

image

每个节点仅有一个PT,数据会写入PT leader节点,其他两个节点只能作为被动的数据同步节点,无法分担业务压力,类似一主两从。

示例2: ptnum-pernode = 2,ts-store 3个

image

每个节点仅有2个PT,3个节点可以组成2个raft组,每个组一个pt leader,落在不同节点上,可以分担业务压力。

示例3: ptnum-pernode = 3,ts-store 3个

image

每个节点有3个PT,3个节点可以组成3个raft组,每个组一个pt leader,落在不同节点上,可以分担业务压力。由于每个节点都有2个副本PT会同步写入数据,因此会占用部分计算和存储资源。建议适当扩大节点规格。

示例4: ptnum-pernode = 3,ts-store 4个

image

每个节点有3个PT,4个节点依然只能组成3个raft组,剩余ts-store-4节点由于无法找到更多节点与其组成raft组,因此该节点上的PT是离线的,无法工作。

示例5: ptnum-pernode = 3,ts-store 6个

image

每个节点有3个PT,6个节点组成6个raft组,数据按时间线hash打散分布在节点上。

读写策略与节点故障

  • 每个副本组会选出一个【主pt】,读写请求只会路由到 leader pt上,通过ts-meta来统一管理所有副本组主pt的选举使其在节点间尽量均匀分布。

如下图所示,若ts-store-1节点故障,则原来raft group 1在ts-store-2上的pt follower会被选为新主,承接业务数据。

image

多AZ部署进阶

  • 前提条件是: 集群跨AZ部署。修改配置文件openGemini.conf
[data]
availability-zone = "az1"

[meta]
rep-dis-policy = 1
  • 配置文件修改后启动,配置项 availability-zone 标识ts-store节点所属AZ,rep-dis-policy标识副本pt的分布策略,默认为0表示会尽量在节点间均匀分布,1表示会尽量在AZ间均匀分布,AZ间均衡分布是比节点间均匀分布更严格的分布策略。

端口规划与用途

占用端口的分布与用途 | 图源: https://zhuanlan.zhihu.com/p/582321361

组件 端口 说明
ts-sql 8086 端口可变更,openGemini对外提供服务的统一入口
6061 不可变更,若被其他程序占用,则pprof功能不可用
ts-meta 8092 端口可变更,ts-meta与ts-sql、ts-store之间正常业务交互使用的端口
8091 端口可变更,ts-meta的运维接口
8088 端口可变更,选举通信使用,三个ts-meta组成一个复制集,复制集之间通过raft协议进行选举
8010 端口可变更,ts-store(新)加入集群时使用
ts-store 8400 端口可变更,ts-sql通过该端口将数据写入ts-store
8401 端口可变更,ts-sql通过该端口查询ts-store的数据
8011 端口可变更,ts-meta监测ts-store心跳使用
6060 不可变更,若被其他程序占用,则pprof功能不可用

伪集群中各组件的端口分布 | 图源: https://zhuanlan.zhihu.com/p/582321361

客户端

OpenGemini ts-cli

  • 推荐文献

HTTP API(Curl/...)

  • 设置curl请求的变量
# OPENGEMINI_HOST=192.168.101.102
# OPENGEMINI_USERNAME=admin
# OPENGEMINI_PASSWORD=xxxxxx
  • 创建用户
curl -i -XPOST "http://$OPENGEMINI_HOST:8086/query" -k --insecure --data-urlencode "q=CREATE USER admin WITH PASSWORD 'xxxx' WITH ALL PRIVILEGES"
  • 创建库
# curl -POST http://$OPENGEMINI_HOST:8086/query --data-urlencode "q=CREATE DATABASE bdp_test"

# curl -POST http://$OPENGEMINI_HOST:8086/query -u $OPENGEMINI_USERNAME:$OPENGEMINI_PASSWORD  --data-urlencode "q=CREATE DATABASE bdp_test"
{"results":[{"statement_id":0}]}
  • 查询数据库
# curl -POST http://$OPENGEMINI_HOST:8086/query -u $OPENGEMINI_USERNAME:$OPENGEMINI_PASSWORD --data-urlencode "db=bdp_test"  --data-urlencode "q=show databases"
{"results":[{"statement_id":0,"series":[{"name":"databases","columns":["name"],"values":[["bdp_test2"]]}]}]}
  • 删除库
# curl -POST http://$OPENGEMINI_HOST:8086/query -u $OPENGEMINI_USERNAME:$OPENGEMINI_PASSWORD --data-urlencode "q=DROP DATABASE bdp_test2" 
{"results":[{"statement_id":0}]}
  • 建表
# curl -POST http://$OPENGEMINI_HOST:8086/query -u $OPENGEMINI_USERNAME:$OPENGEMINI_PASSWORD --data-urlencode "db=bdp_test"  --data-urlencode "q=create MEASUREMENT dwd_device_signal_ri_d" 
{"results":[{"statement_id":0}]}
  • 查表 on 指定库
# curl -POST http://$OPENGEMINI_HOST:8086/query -u $OPENGEMINI_USERNAME:$OPENGEMINI_PASSWORD --data-urlencode "db=bdp_test"  --data-urlencode "q=SHOW MEASUREMENTS on bdp_test" 
{"results":[{"statement_id":0,"series":[{"name":"measurements","columns":["name"],"values":[["dwd_device_signal_ri_d"]]}]}]}
  • 删表
# curl -v -POST http://$OPENGEMINI_HOST:8086/query -u $OPENGEMINI_USERNAME:$OPENGEMINI_PASSWORD --data-urlencode "db=bdp_test"  --data-urlencode "q=drop MEASUREMENT dwd_device_signal_ri_d" 
{"results":[{"statement_id":0,"error":"measurement not found"}]}
  # 注:表不存在时,删除失败
{"results":[{"statement_id":0}]}
  # 注:表存在时,删除成功
  • 新增数据
# curl -i -XPOST http://$OPENGEMINI_HOST:8086/write?db=bdp_test -u $OPENGEMINI_USERNAME:$OPENGEMINI_PASSWORD --data-binary 'dwd_device_signal_ri_d,host=device-001,region=us-west sg1=0.64,sg2=9.9,sg3=44 1735216290000000'

//插入1条 field 为: {"Name": "1#sg_xxx", "Type": "float"} 的 point
# curl -i -XPOST -u $OPENGEMINI_USERNAME:$OPENGEMINI_PASSWORD "http://$OPENGEMINI_HOST:8086/write?db=dwd&precision=ms" --data-binary 'dwd_xxx_device_signal_ri,deiceId=A001 1#sg_xxx=-1.1 1736701200000'
HTTP/1.1 204 No Content
Content-Type: application/json
Request-Id: cebb8e14-d1a4-11ef-8cd2-fa163e5ba0b7
X-Geminidb-Build: OSS
X-Geminidb-Version: v1.2.0
X-Request-Id: cebb8e14-d1a4-11ef-8cd2-fa163e5ba0b7
Date: Mon, 13 Jan 2025 11:52:04 GMT

  • 上传文件数据集
  • 创建文件 : cpu_data.txt
cpu_load_short,host=server02 value=0.67 
cpu_load_short,host=server02,region=us-west value=0.55 1422568543702900257 
cpu_load_short,direction=in,host=server01,region=us-west value=2.0 1422568543702900257 
  • 上传文件
curl -i -XPOST http://$OPENGEMINI_HOST:8086/write?db=bdp_test --data-binary @cpu_data.txt 
  • 查询数据
# curl -POST http://$OPENGEMINI_HOST:8086/query -u $OPENGEMINI_USERNAME:$OPENGEMINI_PASSWORD --data-urlencode "db=bdp_test" --data-urlencode "q=select * from dwd_device_signal_ri_d"
{"results":[{"statement_id":0,"series":[{"name":"dwd_device_signal_ri_d","columns":["time","host","region","sg1","sg2","sg3"],"values":[["1970-01-21T02:00:16.29Z","device-001","us-west",0.64,9.9,44]]}]}]} 

# curl -POST http://$OPENGEMINI_HOST:8086/query?db=bdp_test -u $OPENGEMINI_USERNAME:$OPENGEMINI_PASSWORD --data-urlencode "q=select * from bdp_test.autogen.dwd_device_signal_ri_d"
{"results":[{"statement_id":0,"series":[{"name":"dwd_device_signal_ri_d","columns":["time","host","region","sg1","sg2","sg3"],"values":[["1970-01-21T02:00:16.29Z","device-001","us-west",0.64,9.9,44]]}]}]}

Java for OpenGemini(JDBC)

<dependency>
    <groupId>io.opengemini</groupId>
    <artifactId>opengemini-client</artifactId>
    <version>${latest.version}</version>
</dependency>

https://blog.csdn.net/qq_36004521/article/details/80101608

<!-- 引入influxdb依赖 --> 
<dependency> 
    <groupId>org.influxdb</groupId> 
    <artifactId>influxdb-java</artifactId> 
    <version>2.22</version> 
</dependency> 
<dependency>  
    <groupId>com.influxdb</groupId>  
    <artifactId>influxdb-client-java</artifactId>  
    <version>6.3.0</version>  
</dependency>

InfluxDBStudio(第三方开源)

Chronograf

Chronograf is an open-source web application written in Go and React.js that provides the tools to visualize your monitoring data and easily create alerting and automation rules.

3 Opengemini 静态结构(目录)及运行态(进程)

运行态/进程

  • vmw-b
[root@vmw-b ~]# ps -ef | grep -i ts-
root        621      1  0 12月22 ?      00:00:01 /usr/libexec/accounts-daemon
root       1841      1  3 14:54 ?        00:00:02 bin/ts-meta --config=conf/ts-meta.toml
root       2521      1  0 14:54 ?        00:00:00 bin/ts-store --config=conf/ts-store.toml
root       2838      1  0 14:54 ?        00:00:00 bin/ts-sql --config=conf/ts-sql.toml
root       3661      1  0 14:55 ?        00:00:00 bin/ts-monitor --config=conf/ts-monitor.toml
root       8919    307  0 14:56 pts/2    00:00:00 grep --color=auto -i ts-

[root@vmw-b ~]# netstat -lntp | grep -i ts-
tcp        0      0 192.168.101.102:8011    0.0.0.0:*               LISTEN      2521/bin/ts-store   
tcp        0      0 192.168.101.102:6060    0.0.0.0:*               LISTEN      2521/bin/ts-store   
tcp        0      0 192.168.101.102:6061    0.0.0.0:*               LISTEN      2838/bin/ts-sql     
tcp        0      0 192.168.101.102:8400    0.0.0.0:*               LISTEN      2521/bin/ts-store   
tcp        0      0 192.168.101.102:8401    0.0.0.0:*               LISTEN      2521/bin/ts-store   
tcp        0      0 127.0.0.1:6066          0.0.0.0:*               LISTEN      3661/bin/ts-monitor 
tcp        0      0 192.168.101.102:8086    0.0.0.0:*               LISTEN      2838/bin/ts-sql     
tcp        0      0 192.168.101.102:8088    0.0.0.0:*               LISTEN      1841/bin/ts-meta    
tcp        0      0 192.168.101.102:8091    0.0.0.0:*               LISTEN      1841/bin/ts-meta    
tcp        0      0 192.168.101.102:8092    0.0.0.0:*               LISTEN      1841/bin/ts-meta    
tcp        0      0 192.168.101.102:8010    0.0.0.0:*               LISTEN      1841/bin/ts-meta    

[root@vmw-b ~]# pwdx 1841 2521 2838
1841: /usr/local/opengemini/gemini-deploy/ts-meta-8091
2521: /usr/local/opengemini/gemini-deploy/ts-store-8401
2838: /usr/local/opengemini/gemini-deploy/ts-sql-8086
  • vmw-c
[root@vmw-c ~]# ps -ef | grep -i ts-
root        581      1  0 04:07 ?        00:00:01 /usr/libexec/accounts-daemon
root        939  76609  0 15:00 pts/1    00:00:00 grep --color=auto -i ts-
root     117790      1  1 14:54 ?        00:00:05 bin/ts-meta --config=conf/ts-meta.toml
root     117998      1  0 14:54 ?        00:00:02 bin/ts-store --config=conf/ts-store.toml
root     118234      1  0 14:54 ?        00:00:00 bin/ts-sql --config=conf/ts-sql.toml
root     118793      1  0 14:55 ?        00:00:01 bin/ts-monitor --config=conf/ts-monitor.toml

[root@vmw-c ~]# netstat -lntp | grep -i ts-
tcp        0      0 192.168.101.103:8400    0.0.0.0:*               LISTEN      117998/bin/ts-store 
tcp        0      0 192.168.101.103:8401    0.0.0.0:*               LISTEN      117998/bin/ts-store 
tcp        0      0 127.0.0.1:6066          0.0.0.0:*               LISTEN      118793/bin/ts-monit 
tcp        0      0 192.168.101.103:8086    0.0.0.0:*               LISTEN      118234/bin/ts-sql   
tcp        0      0 192.168.101.103:8088    0.0.0.0:*               LISTEN      117790/bin/ts-meta  
tcp        0      0 192.168.101.103:8091    0.0.0.0:*               LISTEN      117790/bin/ts-meta  
tcp        0      0 192.168.101.103:8092    0.0.0.0:*               LISTEN      117790/bin/ts-meta  
tcp        0      0 192.168.101.103:8010    0.0.0.0:*               LISTEN      117790/bin/ts-meta  
tcp        0      0 192.168.101.103:8011    0.0.0.0:*               LISTEN      117998/bin/ts-store 
tcp        0      0 192.168.101.103:6060    0.0.0.0:*               LISTEN      117998/bin/ts-store 
tcp        0      0 192.168.101.103:6061    0.0.0.0:*               LISTEN      118234/bin/ts-sql  
 
[root@vmw-c ~]# pwdx 117790 117998 118234 118793
117790: /usr/local/opengemini/gemini-deploy/ts-meta-8091
117998: /usr/local/opengemini/gemini-deploy/ts-store-8401
118234: /usr/local/opengemini/gemini-deploy/ts-sql-8086
118793: /usr/local/opengemini/gemini-deploy/ts-monitor
  • vmw-d
[root@vmw-d ~]# ps -ef | grep -i ts-
root        652      1  0 12月21 ?      00:00:03 /usr/libexec/accounts-daemon
root     117865      1  1 14:54 ?        00:00:06 bin/ts-meta --config=conf/ts-meta.toml
root     118043      1  0 14:54 ?        00:00:03 bin/ts-store --config=conf/ts-store.toml
root     118167      1  0 14:54 ?        00:00:01 bin/ts-sql --config=conf/ts-sql.toml
root     118509      1  0 14:55 ?        00:00:01 bin/ts-monitor --config=conf/ts-monitor.toml
root     126943 130546  0 15:01 pts/1    00:00:00 grep --color=auto -i ts-

[root@vmw-d ~]# netstat -lntp | grep -i ts-
tcp        0      0 192.168.101.104:8010    0.0.0.0:*               LISTEN      117865/bin/ts-meta  
tcp        0      0 192.168.101.104:8011    0.0.0.0:*               LISTEN      118043/bin/ts-store 
tcp        0      0 192.168.101.104:6060    0.0.0.0:*               LISTEN      118043/bin/ts-store 
tcp        0      0 192.168.101.104:6061    0.0.0.0:*               LISTEN      118167/bin/ts-sql   
tcp        0      0 192.168.101.104:8400    0.0.0.0:*               LISTEN      118043/bin/ts-store 
tcp        0      0 192.168.101.104:8401    0.0.0.0:*               LISTEN      118043/bin/ts-store 
tcp        0      0 127.0.0.1:6066          0.0.0.0:*               LISTEN      118509/bin/ts-monit 
tcp        0      0 192.168.101.104:8086    0.0.0.0:*               LISTEN      118167/bin/ts-sql   
tcp        0      0 192.168.101.104:8088    0.0.0.0:*               LISTEN      117865/bin/ts-meta  
tcp        0      0 192.168.101.104:8091    0.0.0.0:*               LISTEN      117865/bin/ts-meta  
tcp        0      0 192.168.101.104:8092    0.0.0.0:*               LISTEN      117865/bin/ts-meta  

[root@vmw-d ~]# pwdx 117865 118043 118167 118509
117865: /usr/local/opengemini/gemini-deploy/ts-meta-8091
118043: /usr/local/opengemini/gemini-deploy/ts-store-8401
118167: /usr/local/opengemini/gemini-deploy/ts-sql-8086
118509: /usr/local/opengemini/gemini-deploy/ts-monitor
  • vmw-e
[root@vmw-e ~]# ps -ef | grep -i ts-
root        582      1  0 12月21 ?      00:00:03 /usr/libexec/accounts-daemon
root      19858      1  0 14:54 ?        00:00:03 bin/ts-server --config=conf/ts-server.toml
root      20170      1  0 14:55 ?        00:00:00 bin/ts-monitor --config=conf/ts-monitor.toml
root      20564  20344  0 15:03 pts/1    00:00:00 grep --color=auto -i ts-

[root@vmw-e ~]# ps -ef | grep -i grafana
root       2528      1  0 19:48 ?        00:00:07 bin/grafana-7.5.17/bin/grafana-server --homepath=/usr/local/opengemini/gemini-deploy/grafana-3000/bin/grafana-7.5.17 --config=/usr/local/opengemini/gemini-deploy/grafana-3000/conf/grafana.ini
root      89035  88722  0 21:01 pts/2    00:00:00 grep --color=auto -i grafana

[root@vmw-e ~]#  netstat -lntp | grep -i ts-
tcp        0      0 192.168.101.105:8186    0.0.0.0:*               LISTEN      19858/bin/ts-server 
tcp        0      0 192.168.101.105:8410    0.0.0.0:*               LISTEN      19858/bin/ts-server 
tcp        0      0 192.168.101.105:8411    0.0.0.0:*               LISTEN      19858/bin/ts-server 
tcp        0      0 192.168.101.105:8188    0.0.0.0:*               LISTEN      19858/bin/ts-server 
tcp        0      0 192.168.101.105:8191    0.0.0.0:*               LISTEN      19858/bin/ts-server 
tcp        0      0 192.168.101.105:8192    0.0.0.0:*               LISTEN      19858/bin/ts-server 
tcp        0      0 192.168.101.105:6060    0.0.0.0:*               LISTEN      19858/bin/ts-server 
tcp        0      0 192.168.101.105:6061    0.0.0.0:*               LISTEN      19858/bin/ts-server 
tcp        0      0 127.0.0.1:6066          0.0.0.0:*               LISTEN      20170/bin/ts-monito 

[root@vmw-e ~]# pwdx 19858 20170
19858: /usr/local/opengemini/gemini-deploy/ts-server-8186
20170: /usr/local/opengemini/gemini-deploy/ts-monitor

[root@vmw-e ~]# pwdx 2528
2528: /usr/local/opengemini/gemini-deploy/grafana-3000

静态结构(目录结构)

注:默认仅展示目标目录的前3层

[vmw-b] /root/.gemix

 cluster-info
 download
 logs
  gemix-cluster-debug-2024-12-19-15-11-04.log
  ...
  gemix-cluster-debug-2024-12-20-21-21-29.log
  ...
  gemix-cluster-debug-2024-12-23-10-25-44.log
  gemix-cluster-debug-2024-12-23-14-35-18.log
  gemix-cluster-debug-2024-12-23-14-38-12.log
 storage
  cluster
   clusters
    gemini-test
   packages
    checksums.txt
    grafana-7.5.17.linux-amd64.tar.gz
    grafana-7.5.17.linux-amd64.tar.gz.bak
    grafana-enterprise-7.5.17.linux-amd64.tar.gz
    openGemini-1.2.0-linux-amd64.tar.gz
    openGemini-1.2.0-linux-amd64.tar.gz.bak
8 dictories,36 files

[vmw-b] /opt/go-projects/

 opengemini/
  bin/
   gemix/
  pkg/
   mod/
    cache
    github.com
    golang.org
    gopkg.in
    go.uber.org
   sumdb/
    sum.golang.org

[vmw-b] /usr/local/opengemini/

 gemini-deploy/
  ts-meta-8091/
   bin/
    ts-meta
   conf/
    ts-meta.toml
   scripts/
    run_ts-meta.sh
  ts-monitor/
   bin/
    monitor.lock
    ts-monitor
   conf/
    ts-monitor.toml
   scripts/
    run_ts-monitor.sh
  ts-sql-8086/
   bin/
    ts-sql
   conf/
    ts-sql.toml
   scripts/
    run_ts-sql.sh
  ts-store-8401/
   bin/
    ts-store
   conf/
    ts-store.toml
   scripts/
    run_ts-store.sh
 gemini-log/
  logs/
   ts-meta-8091/
    gossip.log
    meta_extra.log
    meta.log
    metric
    raft.log
   ts-monitor/
    monitor.error.log
    monitor_extra.log
    monitor.log
   ts-sql-8086/
    metric
    sql_extra.log
    sql.log
   ts-store-8401/
    gossip.log
    metric
    store_extra.log
    store.log
 gemix-cluster-install.log

[vmw-b] /data/gemini-data/

 data
 meta
  raft.db
  snapshots/

[vmw-e] /root/test

[root@vmw-e ~]# ll /root/test/usr/bin
总用量 30416
-rwx------. 1 root root 31142400 12月 20 19:36 ts-cli
[root@vmw-e ~]# ll /root/test/usr/bin/ts-cli
-rwx------. 1 root root 31142400 12月 20 19:36 /root/test/usr/bin/ts-cli

注:ts-cli 脚本

ln -s /root/test/usr/bin/ts-cli /usr/local/bin/ts-cli

# 帮助手册
ts-cli -h

ts-cli -host 192.168.101.102 -port 8086
[root@vmw-e ~]# ll /root/test/etc/
总用量 24
-rw-r--r--. 1 1001 docker  1704 2月  29 2024 monitor.conf
-rw-r--r--. 1 1001 docker 11087 2月  29 2024 openGemini.conf
-rw-r--r--. 1 1001 docker  1692 2月  29 2024 openGemini.singlenode.conf
-rw-r--r--. 1 1001 docker  3680 2月  29 2024 weakpasswd.properties

[vmw-e] /usr/local/opengemini/

 gemini-deploy
  grafana-3000
   bin
    grafana-7.5.17
   conf
    grafana.ini
   dashboards
    error_logs.json
    inspection.json
    overview.json
    performance_read.json
    performance_write.json
    process_restarted.json
   data
    grafana.db
    png
   log
    grafana.log
   plugins
   provisioning
    dashboards
    datasources
   scripts
    run_grafana.sh
  ts-monitor
   bin
    monitor.lock
    ts-monitor
   conf
    ts-monitor.toml
   scripts
    run_ts-monitor.sh
  ts-server-8186
   bin
    ts-server
   conf
    ts-server.toml
   data
    meta
   scripts
    run_ts-server.sh
 gemini-log
  logs
   ts-monitor
    monitor.error.log
    monitor_extra.log
    monitor.log
   ts-server-8186
    raft.log
    server_extra.log
    single.error.log
    single.log

Z FAQ for OpenGemini

时间乱序专题研究

Q: 写数据的过程中,对时间乱序Point的判定与处理过程是?

在 openGemini 中,处理时间乱序(Out-of-Order, OOO)数据是保证系统鲁棒性的关键。时序数据库通常期望数据按时间顺序到达,但由于网络延迟、设备离线重传或时钟漂移,乱序点不可避免。

openGemini 处理乱序数据的过程可以分为 “判定”“缓冲排序”“存储隔离与合并” 三个阶段:


1. 乱序点的判定 (Detection)

判定主要发生在数据进入 Shard 的过程中。

  • 判定基准:每个 Shard 内的每条时间线(Series)都会维护一个 MaxTimestamp(当前已接收到的最大时间戳)。

  • 判定逻辑

  • 当新到达的数据点时间戳 ,该点被视为顺序点

  • 当新到达的数据点时间戳 ,该点被判定为乱序点

  • 窗口限制:openGemini 通常会配置一个乱序容忍窗口(Out-of-order window)。如果数据点太旧(例如早于当前时间 7 天,视 Retention Policy 而定),可能会直接被拒绝以防止过度干扰历史文件。


2. 内存中的处理 (In-Memory Handling)

在内存层(MemTable),openGemini 并不担心乱序,因为它通过数据结构自动解决了排序问题。

  • 自动排序:MemTable 内部通常采用类似 SkipList(跳表)B-Tree 的结构。当数据插入内存时,会根据 SeriesID + Timestamp 进行排序。
  • 结果:无论数据在网络中是按什么顺序来的,在【内存缓冲区】里,它们最终都是按时间先后顺序整齐排列的。

3. 落盘时的特殊处理 (Storage Isolation)

这是 openGemini 性能优化的核心。为了防止乱序数据破坏已有 TSM 文件的顺序性,它采用了顺序与乱序分离的策略。

① 产生 OOO 文件 (Out-of-Order Files)
  • 冷热分离:如果一个 MemTable 中包含大量比磁盘上已有数据还旧的点,在 Flush(落盘)时,openGemini 会识别出这些重叠区域。
  • 隔离存储:为了避免频繁改写已经压缩好的老 TSM 文件(避免写放大),openGemini 会将【这部分乱序数据】写入专门的 OOO 文件(或者标记为乱序块)。
② 乱序判定与分片
  • 如果乱序数据的时间范围跨度很大,它会被写入对应的历史 Shard。
  • 系统会记录哪些文件是“乱序”的,在查询时,引擎会同时扫描顺序文件和这些 OOO 文件,并在内存中进行 Merge Sort(归并排序),确保返回给用户的结果依然是有序且正确的。

4. 后台合并 (Background Compaction)

乱序数据最终在 Compaction(压缩/合并) 阶段得到彻底解决。

  1. Level 合并:后台线程会定期扫描磁盘上的小文件和 OOO 文件。
  2. 归并重写:Compaction 进程会将顺序 TSM 文件与 OOO 文件读入内存,按照时间戳重新进行归并,剔除重复点(如果有的话,通常以最新到达的为准)。
  3. 生成新文件:生成一个新的、完全有序且高度压缩的正式 TSM 文件,并删除旧的乱序文件。

总结:处理流程图示

阶段 处理方式 目的
写入瞬间 对比 标记是否为乱序。
内存暂存 跳表/树结构插入排序 确保内存中的 block 内部有序。
落盘 Flush 写入 OOO 临时区域 保护已有的顺序文件,降低 I/O 开销。
后台合并 归并排序 + 重写 TSM 最终消除乱序,恢复全局最优存储。

您可以进一步了解:
这种处理方式确保了写入性能(不需要实时修改磁盘文件),但也意味着如果乱序非常严重,会增加查询时的归并负担

Q: OOO (Out-of-Order) 文件,是1个tag对应一个文件,还是?

  • 不是的,OOO(Out-of-Order)文件绝对不是“一个 Tag 对应一个文件”
  • 在 openGemini 中,如果为每一个 Tag(或每一条时间线)创建一个独立文件,当面对千万级甚至亿级时间线(高基数场景)时,操作系统会因为打开过多的文件句柄File Descriptors)而崩溃,磁盘 I/O 效率也会极度低下。

OOO 文件的组织方式遵循以下逻辑:

1. 以 Shard 为基本单位

OOO 文件是属于 Shard(分片) 级别的。

  • 逻辑层级Database -> Retention Policy -> Shard -> [TSM/OOO Files]
  • 在一个特定的时间分区(Shard)内,所有属于该时间段的乱序数据,无论来自哪个 Tag,在落盘时都会被聚合到 少数几个 OOO 文件中。

2. OOO 文件的内部构造

虽然物理上是一个文件,但它内部是高度结构化的:

  • 按 Series ID 分块:文件内部会将数据划分为多个 Block(块)。每个 Block 对应一个 Series ID(即特定的 Measurement + Tags 组合)。
  • Series 索引:OOO 文件尾部(或单独的索引文件)会记录每个 Series ID 在文件中的偏移量。
  • 列式存储:即使是乱序文件,内部依然采用列式压缩存储。

3. 为什么不按 Tag 分文件?

openGemini 这样设计是为了平衡 写入保护查询效率

  • 聚合写入:将多个不同 Tag 的乱序点一次性顺序写入磁盘,可以最大限度利用磁盘带宽。
  • 避免文件碎片:减少小文件的产生。
  • 查询映射:查询时,通过内存中的索引快速定位到这个 OOO 文件里属于特定 Tag 的数据块,进行实时归并。

4. 数据的“归宿”:Compaction

OOO 文件只是临时状态

  • 当后台的 Compaction(压缩合并) 线程启动时,它会将这些 OOO 文件与对应 Shard 下的标准 TSM 文件读取出来。
  • 系统会在内存中对它们进行“大合唱”:按 Series ID 和时间戳重新排序,剔除重复值。
  • 最终,乱序数据会消失,合并进一个全新的、完全有序的 TSM 文件 中。

总结

维度 情况
物理表现 一个 Shard 下可能存在少量几个 OOO 文件,里面塞满了成千上万个 Tag 的数据。
逻辑索引 每个 Tag 在文件里有自己的“领地”(Block),通过 Series ID 检索。
存在周期 暂时的。它们是乱序数据的“隔离区”,最终会被合并进主文件。

您可以进一步了解:
如果您发现 ts-store 的数据目录下 OOO 文件数量过多(比如一个 Shard 下有几十个),这通常意味着系统来不及进行乱序合并。

可研究下查看 乱序文件监控指标 的方法,或者通过配置参数(如 compact-full-write-cold-duration)来加速合并。

Q: 由于不同tag的设备数据都存放在1个shard,是否存在当设备A的数据晚于设备B的数据到达时,引发时间乱序现象,进而存储到 /out-of-order 下?

  • 答案是:会引发乱序现象,但判定标准并非简单的“谁后到”,而是取决于该数据是否“迟到”于已持久化的时间窗口。

在 openGemini 中,不同 Tag 的设备(即不同的 Series)虽然共享一个 Shard,但系统对乱序的判定逻辑是结合了 Series 级别 和 Shard 存储状态 的。

  • 场景 1:数据都在内存中(不计入 /out-of-order)假设设备 B 先发送了 T=100 的数据,设备 A 随后发送了 T=90 的数据。
  • 状态:此时两个设备的数据都还在 ts-store 的 MemTable(内存缓冲区) 中,尚未落盘。
  • 处理:内存中的跳表(SkipList)会自动按照时间戳将 T=90 排在 T=100 之前。
  • 结论:这种情况不会产生乱序文件。对于内存来说,这只是正常的插入排序
  • 场景 2:跨越了落盘(产生 /out-of-order)这是最常见引发 /out-of-order 的情况。

设备 B 发送 T=100。系统达到 Flush 条件,将包含 T=100 的内存数据写入了磁盘,生成了一个正式的 .tssp 文件。
此时系统记录了该 Shard 的持久化时间进度。
随后,设备 A 的 T=90 数据才到达。
判定:系统发现 T=90 比已经存入磁盘的某些数据还要旧。为了不修改已经写死的(Immutable)正式文件,它必须把 T=90 放入隔离区。
结论:T=90 会被写入该 Measurement 目录下的 /out-of-order 文件夹中。

  • 场景 3:设备 A 自身的“时间倒流”如果设备 A 先发了 T=100,然后又发了 T=90(例如设备离线重传、时钟回拨)。

判定:openGemini 会记录每个 Series ID 的最新时间戳 \(T_{max\_series}\)。如果 \(T_{new} < T_{max\_series}\),这就是典型的乱序点。
结论:这必然会进入乱序处理逻辑。

  • 为什么不同设备会互相影响?

小伙子,你观察得很准,问题的核心在于 Shard 的时间跨度。

由于一个 Shard 覆盖了一个固定的时间段(例如你目录中的 8 小时),ts-store 在落盘时是批量处理该 Shard 下所有 Series 的。
如果设备 B 频率很高,不停地触发落盘,就会把磁盘上的“时间水位线”不断推高。
此时如果设备 A 传输慢了(由于网络或 Tag 自身的上报频率低),它稍微“迟到”一点,就可能发现水位线已经过了。
底层逻辑: openGemini 追求的是写入吞吐量。它宁愿把迟到的数据丢进 /out-of-order 以后再通过后台合并,也不愿意为了等“慢设备”而阻塞内存或频繁重写文件。

Q:怎么判断你的系统乱序严重不严重?

  • 你可以观察 /out-of-order 目录:
  • 文件很小且经常消失:这是健康的。说明系统在及时合并。
  • 文件很大且越积越多:说明你的设备上报数据时间跨度太大,或者磁盘 I/O 压力大导致后台合并(Compaction)跟不上。

建议: 如果你发现大量设备数据因为“迟到”进入了乱序目录,可以尝试调整 openGemini.conf 中的:

compact-full-write-cold-duration:缩短这个时间,让乱序文件更早地合并到主文件中。

Q:在应用程序端而言,如何判别、减少OpenGemini的乱序数据文件?

在 OpenGemini 中,乱序数据(Out-of-Order, OOO)是指写入的数据时间戳早于该时间线(Series)已落盘的最大时间戳。乱序数据会导致系统频繁触发 Compaction(合并),增加 CPU 和 IO 开销,甚至导致查询性能大幅波动。

以下是从应用程序端判别减少乱序数据的方法:


一、 如何判别乱序数据?

由于 OpenGemini 在底层会将顺序数据和乱序数据分开存放(分别存储在 .tssp.ooo 后缀的文件中),你可以通过以下方式判别:

1. 监控系统指标(最直观)

OpenGemini 暴露了丰富的监控指标,你可以通过 Prometheus 或内置统计信息查看:

  • ooo_file_count:查看当前分片(Shard)下乱序文件的数量。如果该值持续走高,说明写入端存在大量乱序。
  • compact_ooo_duration:乱序文件合并的耗时。如果这个指标很高,说明乱序数据正在拖累系统性能。
  • write_drop_out_of_order:如果配置了丢弃乱序数据,可以观察此指标看有多少数据被拦截。
2. 数据库内部查询

你可以通过 SHOW 命令查看分片详情(虽然主要看物理分布,但能辅助判断):

SHOW SHARDS

如果发现某些 Shard 的文件数量远超其他 Shard,且写入延迟明显增加,通常是乱序导致的。

3. 应用逻辑自检

在应用程序写入逻辑中,针对每个 Measurement + Tag Set(即时间线),记录上一次发送的时间戳 。

  • 判定逻辑:如果当前待发送数据的时间戳 ,则该条数据即为乱序。
  • 统计频率:在应用端打点统计乱序发生的频率,比在数据库端排查更及时。

二、 如何减少乱序数据?

减少乱序的核心思想是“在数据进入数据库前完成排序或分流”

1. 保证“单时间线”有序(最有效)

OpenGemini 的性能瓶颈不在于跨时间线的乱序,而在于同一条时间线内的时间交叉。

  • Kafka 分区策略:如果你使用消息队列,建议以 Tag Key(如 device_id)作为 Partition Key。
  • 效果:这样可以保证同一台设备的数据始终由同一个消费者按序处理并写入,从而实现单时间线的绝对有序。
2. 客户端批量局部排序

在执行 Batch Write 之前,在内存中对该批次数据进行一次排序:

  • 操作:按 Timestamp 升序排列。
  • 原理:虽然不同批次间可能仍有重叠,但至少保证了单次写入请求内的数据是完全有序的,极大减轻了数据库在 Memtable 阶段的处理压力。
3. 避免并发批次的时间重叠

如果你的应用采用多线程并发写入:

  • 策略:尽量让不同的线程负责不同的“时间段”或不同的“设备集合”。
  • 风险点:线程 A 发送 10:00-10:05 的数据,线程 B 发送 10:03-10:08 的数据。由于网络波动,B 可能先到达,导致 A 的数据变成乱序。
  • 优化:在应用层引入微小的延迟等待缓冲区(如 500ms),积攒一定量数据排序后再统一发送。
4. 合理设置时间戳精度
  • 场景:如果多个采集器精度不一(有的秒级,有的毫秒级),汇总时极易产生乱序。
  • 优化:在应用端统一规整时间戳精度,并剔除由于设备时钟回拨产生的非法未来时间或过旧时间。

三、 总结建议

措施 难度 效果 说明
Kafka 按 Tag 分区 极高 工业场景最佳实践,保证单设备有序。
内存局部排序 简单且能有效降低 CPU 抖动。
应用层时钟同步 防止因设备时间偏差导致的逻辑乱序。
调整分片时长 通过 ShardGroupDuration 减少大范围跨分片乱序。

如何使用 openGemini 提高性能
这段视频详细介绍了 openGemini 的架构和性能优化方向,涵盖了如何通过合理的配置和数据模型设计来提升大规模时序数据的读写效率,有助于你更深入地理解乱序数据对系统的影响及优化方案。

Q: 乱序数据的影响评估?

维度 影响程度 说明
写入性能 极低 乱序写入依然是顺序追加到 OOO 文件,写入速度不受损。
查询性能 中等 查询时需要对正式文件和 OOO 文件做归并排序Merge Sort),会增加 CPU内存开销。
磁盘空间 暂时增加 OOO 文件由于没有经过深度合并,压缩率略低,但 Compaction 后会恢复。

处理乱序数据(Out-of-Order, OOO)是时序数据库的“家常便饭”,但处理不当确实会拖累系统。针对 openGemini 的架构,我们从内存、CPU 和数据安全性三个维度深度拆解乱序带来的影响:


1. 对内存(Memory)的影响

乱序对内存的压力主要体现在写入缓冲查询合并两个阶段。

  • 写入阶段(MemTable 膨胀):

  • 为了处理乱序,内存中的 MemTable 需要维护有序结构(如跳表或平衡树)。如果乱序点非常分散(跨度巨大),会导致内存中需要同时维持多个 Shard 的活跃 MemTable

  • 风险点:乱序数据可能频繁触发“冷快照(Snapshot)”,如果内存回收速度跟不上新到的乱序数据,容易引发 OOM (Out of Memory)

  • 查询阶段(缓存压力):

  • 查询时,系统需要同时加载顺序文件(TSM)和乱序文件(OOO)的索引到内存。

  • 多路归并缓存:由于需要实时合并 OOO 数据,查询引擎需要申请额外的 Buffer 空间来暂存待合并的数据块,乱序文件越多,查询时的内存开销成倍增加。


2. 对 CPU 的影响

乱序是 CPU 的“性能杀手”,主要体现在后台合并实时排序

  • 后台合并(Compaction)—— 最大消耗:

  • ts-store 需要定期将 /out-of-order 目录下的文件与主文件合并。这个过程涉及:解压 -> 归并排序 -> 重新编码 -> 压缩

  • 当乱序严重时,Compaction 线程会长时间占用高额 CPU,可能导致正常的写入请求处理变慢。

  • 查询时的逻辑运算:

  • 如果后台合并还没完成,用户发起查询,CPU 必须在读取时进行“在线排序”。

  • 计算开销:( 是乱序文件的个数)。如果乱序文件过多, 增大,查询延迟(Latency)会明显上升。


3. 对宕机数据丢失(Reliability)的影响

结论:在开启 WAL(预写日志)的情况下,乱序不会导致数据丢失,但会延长恢复时间。

  • WAL 保护机制:

  • 无论是顺序还是乱序,数据在进入内存前都会先写入 WAL。只要磁盘不坏,即便 ts-store 进程崩溃,数据也能从 WAL 中恢复。

  • 恢复时长(MTTR)的影响:

  • 恢复变慢:宕机重启后,系统需要回放(Replay)WAL 日志。如果乱序导致内存中的数据频繁切分或碎片化,WAL 的索引重建过程会比纯顺序数据更复杂,导致服务“热身”时间变长。

  • 一致性风险:

  • 在极端的“覆盖写”场景下(同时间戳、同标签、不同值),乱序到达可能引发版本冲突。openGemini 通常以“最后到达的为准(Last-write-wins)”,这种逻辑在宕机恢复后依然保持一致,但可能不是业务预期的值。


总结:影响程度矩阵

维度 影响程度 核心表现
内存 (Memory) 中等 索引条目增多,查询缓冲开销增大。
CPU (CPU) 后台合并(Compaction)消耗大量算力,查询延迟增加。
数据丢失 (Loss) 极低 有 WAL 保护,基本无丢失风险。
恢复速度 (Recovery) 中等 WAL 回放逻辑变复杂,重启后进入可用状态变慢。

Q: 如何优化乱序数据?

如果你的 /out-of-order 文件积压严重,建议检查以下配置:

  1. 加大 Compaction 并发:在 openGemini.conf 中适当调大压缩线程池,加快乱序文件的消化速度。
  2. **调整 max-past-nanoseconds**:如果业务允许,丢弃掉那些“迟到”太久的数据(例如 7 天前的乱序数据),直接从源头减轻系统负担。
  3. 增加内存分配:给 ts-store 预留更多 Headroom,防止【查询乱序数据】时发生 OOM。

您可以进一步了解:
现在的生产环境中,是否存在 CPU 占用率随着乱序文件增多而升高 的现象?可通过查看 openGemini 的核心监控面板来验证这一点。

Q: opengemini 因内存不足而频繁宕机,且已开启了 wal 预写机制,为何仍存在丢数据的偶发现象?可能是什么原因?

  • 开启了 WAL(预写日志)机制却依然在 OOM(内存溢出)宕机时丢数据,这在分布式时序数据库中是一个非常经典且棘手的问题。
  • 即便 WAL 逻辑是完备的,在极端的内存压力操作系统干预下,数据持久化链路仍存在多个“【易碎点】”。以下是深度分析可能的原因:

1. “应用层写”与“内核刷盘”的时间差 (Fsync 延迟)

这是最常见的原因。WAL 的写入过程通常分为三步:

  1. Application Buffer: 数据进入 openGemini 进程内部的缓冲区。
  2. OS Page Cache: 调用 write() 系统调用,数据进入操作系统的页面缓存。
  3. Disk: 调用 fsync(),操作系统将数据真正强制刷入物理硬盘。

问题所在:为了追求极致性能,openGemini(及多数数据库)通常不会对每一条数据都执行 fsync。如果配置了 wal-sync-interval(同步间隔),在两次同步之间发生的 OOM 宕机,会导致那些已经成功写入 OS Cache 但尚未落盘的数据因【系统重启】而丢失。
注:虽然进程崩溃(OOM Killer)后内核通常会继续刷盘,但如果 OOM 导致了系统级死锁或内核崩溃(Kernel Panic),这部分数据必丢。


2. 内存挤压导致的“原子性失效”

当内存严重不足时,系统会进入一种极不稳定的状态:

  • 申请内存失败:数据可能在尝试构建 WAL 记录时,因为无法申请到临时的内存对象而失败。如果错误处理逻辑在极端压力下出现异常,可能导致数据既没有写入 WAL,也没有成功返回错误给上游。
  • WAL 文件头损坏:如果在写入 WAL 文件元数据(如文件头、Length 字段)的过程中发生崩溃,重启后的恢复程序(Recovery)可能会认为该 WAL 文件已损坏,为了保证数据库能正常启动,系统可能会跳过或截断这个损坏的文件。

3. 重启恢复时的“文件完整性校验”策略

openGemini 在重启时会扫描 WAL 目录并回放数据。

  • .broken 隔离机制:如果 WAL 文件在宕机瞬间只写了一半,导致校验和(Checksum)不匹配,openGemini 的策略往往是“优先保证可用性”。它可能会将损坏的 WAL 块重命名为 .broken 或直接舍弃,以确保 ts-store 能顺利启动。
  • 恢复超时:如果 OOM 频繁发生,产生的 WAL 文件可能过多。在某些配置下,如果恢复时间过长触发了进程监控的超时杀掉,可能会导致恢复流程中断,甚至引发二次损坏。

4. 上游 ACK 确认的“假象”

这是一个分布式协同问题。

  • 写入路径ts-sql -> ts-store (Write to MemTable & WAL) -> Ack to ts-sql
  • 潜在风险:如果 ts-store 在内存极其紧张的情况下,采用了异步写 WAL 的模式(或者在内存池耗尽时处理逻辑出现竞争),可能在数据真正进入磁盘安全区之前,就给 ts-sql 返回了成功。此时一旦宕机,上游认为数据已安全,实则不然。

5. 磁盘 I/O 饥饿引发的写入超时

OOM 发生前,系统往往伴随着剧烈的 Swap(内存交换)

  • 当系统开始大量使用虚拟内存时,磁盘 I/O 会被 Swap 占满。
  • 此时,WAL 的写入线程可能因为 I/O 等待而超时或挂起。如果 openGemini 内部对 WAL 写入有超时保护,且在超时后未进行严格的重试或回滚,数据就会在内存中“蒸发”。

建议排查与优化步骤

动作 操作建议
检查日志 搜索 ts-store 启动日志中的 RECOVERY 关键字,观察是否有 checksum errorskip wal file
调整同步频率 openGemini.conf 中缩短 wal-sync-interval,权衡性能与安全性。
内存阈值保护 调低 node-mutable-size-limit,在内存耗尽前提前触发压力回绝(Reject),而不是等着被 OOM Killer 杀死。
监控 IOPS 观察宕机前的磁盘 I/O 延迟。如果 wal-write-latency 飙升,说明磁盘已成为瓶颈。

下一步动作:
您可以检查 ts-store 的配置文件中 [data] 模块下的 wal-sync-interval 参数是多少吗?此外,检查一下数据目录下是否存在以 .broken 结尾的文件,这能直接证实是否存在因文件损坏导致的丢数。

Q:OpenGemini 小文件、乱序文件的触发机制、合并过程?合并过程中是否涉及跨节点传输文件?

在 openGemini 中,小文件与乱序文件的处理是其高性能存储引擎(基于 LSM-Tree 变体)的核心机制。以下是针对您问题的详细解答:

1. 触发机制 (Trigger Mechanism)

  • openGemini 通过分层合并(Compaction)来管理文件,小文件和乱序文件的产生及触发合并的机制略有不同:
  • 小文件的产生与触发:

  • 产生: 当内存中的 Memtable 达到阈值(如大小或存活时间)时,会溢写(Flush)到磁盘。如果写入频率高但单次批次小,就会产生大量小文件(TSSP 文件)。

  • 触发: 存储引擎会定期扫描各个 Level 的文件数量或总大小。当某一层的 文件数量达到预设阈值(如默认 10-15 个)或 总空间占用达到阈值 时,后台调度任务会触发合并。

  • 乱序文件 (Out-of-Order) 的产生与触发:

  • 产生: 当新写入的数据时间戳小于已持久化文件中的最大时间戳时,这些数据会被标记为“乱序”并刷写到专门的 乱序区 (Level 0)

  • 触发: openGemini 采用一种乱序优先合并策略。乱序文件会先在乱序区内部进行合并(L0 -> L0),以减小乱序数据规模。当乱序文件的大小或数量达到设定阈值后,才会触发与有序区(Level 1 及以上)文件的合并。

2. 合并过程 (Compaction Process)

合并的核心是将多个乱序或小的 TSSP 文件重新整理为更大、更有序的文件,过程如下:

  1. 文件筛选: 调度器根据优先级(乱序文件优先、层级文件数、数据冷热等)选择待合并的文件集合。
  2. 流式读取与归并: * 早期版本可能加载到内存,但在 v1.0 之后,openGemini 优化为 【流式合并】
  • 从多个源文件中读取数据块(Block),按 SeriesID + Timestamp 进行归并排序(Merge Sort)。
# ts-store.toml

[data]
# 开启流式合并(默认: 非流式合并)
compaction-method = 1
  1. 冲突处理与去重: 在合并过程中,如果存在相同时间戳的数据,会根据版本号预设规则进行【去重】或【覆盖】。
  2. 生成新文件: 排序后的数据被压缩并写入新的 TSSP 文件。
  3. 元数据更新: 新文件写入完成后,原子性地更新 Meta 节点中的元数据,并将【旧文件】标记为【删除】。

3. 文件合并过程中,是否涉及跨节点传输文件?

  • 结论:合并过程通常不涉及跨节点传输文件。

原因如下:

  • 本地化合并原则: openGemini 的数据是按照 Shard(分片)存储在具体的 ts-store 节点上的。一个 Shard 内部的文件合并只在该节点本地 CPU 和磁盘上完成。

  • 计算下推: 为了避免巨大的【网络开销】,合并任务由持有该数据的 ts-store 节点自主完成,不需要将数据拉取到 ts-sql 节点或其他存储节点。

  • 唯一的例外情况:

  • 负载均衡/扩缩容: 当集群进行节点扩容或由于磁盘不均衡触发 Rebalance 时,Shard 会在节点间迁移,这涉及文件的【跨节点传输】,但这属于【数据迁移】,而非合并过程

  • 多副本同步: 如果开启了多副本(Replica),主副本完成合并后,从副本通常会独立进行相同的合并动作,或者通过同步协议传输合并后的差异,但这更多是副本间的一致性维护。

总结建议

如果您在生产中遇到大量小文件,建议:

  • 客户端侧: 尽量通过批量写入(Batch Size 推荐 5,000-10,000 行)减少 Flush 频率。
  • 服务端侧: 检查 openGemini.conf 中的 [storage-engine] 相关配置,如 compact-full-write-cold-duration(冷数据判定时间)以优化合并时机。

Q: OpenGemini 经常出现其中一个节点的【内存使用率】比其他节点高(但【磁盘使用率】又是均衡的),可能是什么原因?

  • 在 openGemini 集群中,出现“磁盘均衡但内存不均衡”的情况是比较典型的读写负载不均数据分布长尾的表现。

由于磁盘使用率均衡,说明数据分片(Shard)在各节点上的分布是均匀的;但内存负载高,说明某个节点正在承担更多的活跃计算索引压力。以下是可能的原因分析:


1. 热点查询或大查询 (Query Hotspots)

这是最常见的原因。openGemini 的查询引擎在执行过程中会占用大量内存用于数据缓存、排序和聚合。

  • 原因: 如果某些频繁访问的热点数据(如最近 1 小时的监控指标)所在的 Shard 恰好分布在某个特定的 ts-store 节点上,该节点的内存会因为频繁的 Data Cache 和查询算子执行而显著升高。
  • 表现: 该节点的 CPU 使用率通常也会伴随性升高。
  • 排查: 检查是否存在针对特定 Tag 或时间范围的密集查询。

2. 高基数序列分布不均 (High Cardinality)

openGemini 会在内存中维护 倒排索引 (Inverted Index)

  • 原因: 虽然各节点存储的数据量(磁盘占用)差不多,但如果某个节点分到的 Shard 中包含了更多的 唯一序列 (Series Key),例如某个节点的设备 ID 种类远多于其他节点,那么该节点的索引内存占用会大得多。
  • 指标: 观察 Series 数量在各节点间的分布。

3. 合并任务 (Compaction) 堆积

如前文所述,Compaction 是在本地完成的。

  • 原因: 某些节点可能因为写入的数据乱序程度更高,或者该节点承载的文件层级更复杂,导致触发了更频繁或更大规模的 Compaction 任务。合并过程中的数据读取缓冲区会占用大量内存。
  • 表现: 该节点磁盘 I/O 也会变得活跃,且内存使用量呈周期性波动。

4. 缓存淘汰策略不一致 (Cache Residency)

  • 原因: openGemini 内部有多种缓存(如 Meta Cache, Block Cache)。如果某个节点承载了更多的历史查询任务,它可能会在内存中缓存更多的 Data Block。即使磁盘数据是均衡的,内存中“活着”的数据量却不同。

5. 架构视角:计算与存储的分离错觉

在 openGemini 中,数据的逻辑分布与物理压力如下表所示:

维度 均衡情况 影响因素
磁盘 (Disk) 均衡 Shard 的数量和大小决定。
内存 (Memory) 可能不均衡 取决于 查询频率索引规模 (Cardinality)Compaction 负载

排查建议与解决步骤

1. 查看各节点 Series 数量:

使用 show series cardinality on <database> 查看序列基数,确认是否存在某些 Shard 导致的索引过大。

curl -G http://xx.xx.xx.xx:8086/query -u {username}:'{password}' --data-urlencode "q=show series cardinality on bdp_dwd"

{"results":[{"statement_id":0,"series":[{"columns":["startTime","endTime","count"],"values":[["2025-05-12T00:00:00Z","2025-05-19T00:00:00Z",13637]]},{"columns":["startTime","endTime","count"],"values":[["2025-12-22T00:00:00Z","2025-12-29T00:00:00Z",63861]]},{"columns":["startTime","endTime","count"],"values":[["2025-12-29T00:00:00Z","2026-01-05T00:00:00Z",61271]]},{"columns":["startTime","endTime","count"],"values":[["2026-01-05T00:00:00Z","2026-01-12T00:00:00Z",67006]]},{"columns":["startTime","endTime","count"],"values":[["2026-01-12T00:00:00Z","2026-01-19T00:00:00Z",65477]]},{"columns":["startTime","endTime","count"],"values":[["2026-01-19T00:00:00Z","2026-01-26T00:00:00Z",63398]]}]}]}

从提供的 show series cardinality 结果来看,bdp 数据库的单周序列数(Series Cardinality)大约在 6 万到 7 万 之间。
从 openGemini 的性能阈值来看,这个基数非常小(通常 openGemini 可以轻松处理数千万甚至亿级的基数)。因此,内存倾斜的原因大概率不在【索引本身】的大小,而在【内存的使用效率】或【瞬时负载】。

结合 series 数据分布,可锁定以下3个最高频的原因:

从你提供的 show series cardinality 结果来看,bdp 数据库的单周序列数(Series Cardinality)大约在 6 万到 7 万 之间。

从 openGemini 的性能阈值来看,这个基数非常小(通常 openGemini 可以轻松处理数千万甚至亿级的基数)。因此,内存倾斜的原因大概率不在索引本身的大小,而在内存的使用效率或瞬时负载

结合你的数据分布,我为你锁定以下三个最高频的原因:


1. 乱序写入(Out-of-Order)引发的 Memtable 堆积

观察你的数据,最新的时间戳在 2026-01-22 左右。

  • 现象: 如果你的业务存在大量“补课”数据(例如补传 2025 年的数据),这些数据会被送往旧的 Shard。
  • 原因: openGemini 为了处理乱序,会在内存中为这些旧 Shard 重新开辟 Memtable。由于磁盘是均衡的,但如果某个节点承载了更多“跨度大、乱序多”的写入任务,它会维护更多的活跃 Memtable,导致内存显著高于其他节点。
  • 验证: 检查高负载节点的 write_out_of_order 监控指标。
2. Shard Group 的 Leader/查询倾斜

openGemini 的数据按 Shard 存储,但查询请求往往集中在最新的 Shard Group

  • 原因: 虽然磁盘文件是均衡的,但如果某个节点的 Shard 承载了大部分的近期查询,该节点会缓存大量的 Data Block 到内存中。
  • 缓存差异: openGemini 的 Block Cache 是按需加载的。如果某个节点被频繁请求,其缓存命中和留存会远高于其他节点,造成内存占用不均。
3. 未触发的强制垃圾回收(Go GC 延迟)

openGemini 是基于 Go 语言开发的,内存管理受 Go GC 机制影响。

  • 原因: Go 的 GC 触发取决于 GOGC 变量(默认 100)。如果某个节点刚刚经历了一次大查询或大合并,内存虽然在逻辑上已经释放,但在物理上可能还没还给操作系统。
  • 排查: 查看该节点的 heap_sys(系统申请的内存)与 heap_alloc(实际使用的内存)之间的差值。

针对性的排查与优化建议
维度 排查命令 / 操作 优化方向
查询热点 show queries 观察是否有长查询或大聚合任务卡在高内存节点。
合并负载 检查日志中是否有频繁的 Full Compaction 调整 compact-full-write-cold-duration 避免频繁合并。
内存限制 检查配置文件 [data] 模块 确保 cache-max-memory-size 设为了物理内存的 60%-80%,防止 OOM。
强制 GC 在高内存节点观察 sys 内存 如果 alloc 很低但 rss 很高,说明是系统回收慢,通常不影响业务。

一个值得关注的点

你的结果显示 2025-05-12 这一周只有 13,637 个序列,而近期(12月-1月)翻了 5 倍 左右。

这说明你的业务规模在近期有显著增长。 如果这种增长是不均匀的(例如某些新的设备或 App 实例只落在特定的物理节点上),那么最近 4-5 周产生的查询压力会集中在这些节点。

接下来的建议:
你可以对比一下各节点的 CPU 使用率。如果内存高的节点 CPU 也高,那是查询/合并引起的;如果 CPU 很低但内存高,那是 Cache/Index 留存或 GC 延迟 引起的。

需要我帮你看看如何调整 openGemini 的 Cache 参数来平抑这种内存波动吗?

2. 监控活跃查询:

通过监控工具(如 Grafana 模板或内置指标)查看各 ts-store 节点的 query_executor 相关指标,确认是否存在查询请求倾斜。

3. 调整内存参数:
  • 检查 openGemini.conf 中的 [data] 模块下的 cache-max-memory-size,确保每个节点都有合理的上限保护。
  • 适当减小 index-cache-limit 以强制释放不常用的索引内存。
4. 优化写入:

如果内存升高伴随大量乱序写入,尝试在客户端通过一致性哈希等方式,让相同 Tag 的数据更均匀地分布或更顺序地写入。

您目前集群的 Series(序列)总规模大概在什么量级?如果过亿且内存波动剧烈,我们可能需要重点关注索引优化。

Q:时序数据库是否支持与批处理系统的集成??

  • 可以与批处理系统集成,比如AI模型训练、报表生成、Spark/Flink数据分析等。

目前支持Spark和Flink,其他的比如AI分析框架或者报表工具暂时不能直接支持,需要开发对应的Connector或者基于SDK开发功能模块

Q:openGemini是否自动支持特定时间间隔的汇总?

  • 支持,如果仅仅对某个时间范围内的数据(目标数据量不是很大)按特定时间间隔汇总,可以直接使用select语句或者select into语句。
  • 如果目标数据量太大,建议使用openGemini将要推出的continue query(连续查询)功能;如果不需要保留历史数据明细,可以使用openGemini提供的多级降采样功能

Q:如何优化时序数据库的存储和查询,以提高数据的处理效率?

    1. 如果可以接受部分数据丢失,可以设置不写WAL;
    1. 尽可能批量写;
    1. 增加openGemini的缓存占比;
    1. Tag名字尽量简短;可以指定分区键,减少网络扇出度。

Q:openGemini对于CPU 资源有哪些要求?

  • 部署节点的CPU至少4核起步

Q: 如何在应用程序中使用openGemini时序数据库进行事务处理和并发控制?

  • openGemini不支持事务,数据一旦成功写入便不能回滚

Q: OpenGemini的应用开发流程是怎样的,下载sdk、配置文件、创建连接、创建表?比如,开发者如何利用openGemini进行数据的读写操作。

  • openGemini的开发流程是:
  • 下载openGemini二进制或者源码编译,修改配置文件并启动openGemini单机或者集群节点、创建库和表、下载SDK、基于SDK开发读写数据功能模块进行数据读写。
  • 各语言的SDK下载地址可以参考社区主目录下README中给出的链接。
  • 每种语言的SDK项目中都提供了DEMO

Q:企业如何建设时序数据库基准测试和标准?

  • 基准测试有3个要素:数据、负载和度量体系。

简单来说,做时序数据库基准测试和标准,需要考虑测试的数据模型是什么样,测试模型和负载如何设计,设计哪些性能度量指标。
企业要做这方面的基准测试,需要注意标准的公正、公平和客观性,避免出现基准之争。
建议和开源社区以及典型企业共同来做,这是一件非常有意义的事情。openGemini表示支持。

Q:openGemini是否有详细的文档和教程或者案例?比如,是否有官方文档和教程以帮助开发者快速上手和解决问题。

目前还欠缺的内容包括内置函数和新特性的功能、示例介绍。
暂时可以先参考InfluxDB的文档: https://docs.influxdata.com/influxdb/v1.8/ ,所有算子用法是兼容的。

  • openGemini自身运维监控参考:

https://mp.weixin.qq.com/s/hbb3CnVWCqBulIvgNU_bIw

Q:openGemini时序数据库是否支持数据可视化和报表生成?支持接入Grafana

  • openGemini支持Grafana进行数据可视化展示,但不能生成表表,需要有额外的开发工作。

Q:openGemini是否支持多种操作系统和平台?比如,可以在docker、x86,鸿蒙、麒麟、Linux、Windows等不同操作系统上运行。

  • 支持docker、K8s、KubeEdge、x86、ARM64、Linux(openEuler、Ubuntu、Centos、Redhat等),windows将于下个版本支持。

暂时还没有适配国产的其他操作系统,比如麒麟、鸿蒙。

Q:openGemini是否支持多种存储介质和存储引擎?

  • 目前支持两种存储引擎(默认存储引擎高基数存储引擎),openGemini采用Go语言开发,操作系统和Go语言标准库屏蔽了底层存储介质,不管是SSDHDD,对openGemini来说是无感知的。

Q:openGemini时序数据库能否用于数据仓库和数据湖的集成?

  • 可以用于集成,主要存储时序类型的指标、日志等数据。

但目前没有开发集成工具来实现。

Q:写入数据页时发送断电数据文件和数据页是否会导致数据损坏?

  • openGemini写数据时,会先写WAL文件,再写缓存,缓存满了再刷磁盘文件存储

如果数据在写WAL文件时断电,数据写失败,这部分数据会丢失,写WAL成功后,再断电,数据不会丢失,通过WAL回放会重新找回数据。

Q:openGemini 时序数据库处理和分析结构化、非结构化和半结构化数据的方式有哪些不同?

  • 结构化数据支持友好,半结构化数据需要进行数据各式转换后才能存储在openGemini中,比如XML、JSON, 或者把整个XML或者JSON内容按照string类型存储也可以,暂不支持对非结构化数据的处理。

具体使用场景,可以联系 OpenGemini 布道师-向宇 进行交流,这方面需求如果很普遍,社区可以考虑新增支持半结构化数据的数据类型。

Q:openGemini如何保证实时数据服务。提供数据的实时值的查询、修改?

  • openGemini 并不是严格的实时系统,所有设备(时间线)的数据,写第一条数据时,由于要创建倒排索引,需要在写入成功后等待1s后查到,随后的数据可以即写即查

没有单独的更命令,如果需要更新,可以重写,确保时间与待修改数据的时间保持一致

Q:openGemini时序数据库在华为云SRE中如何进行进行故障排除和性能调试?

  • 使用openGemini自身提供的监控手段,在SRE中为openGemini集群搭建了监控面板,监控指标包括磁盘存储空间、CPU和内存利用率,写入带宽、查询时延、查询QPS、WAL时延等等,根据指标面板分析发现潜在问题,再进行问题排查。

举个例子,某一次SRE给openGemini做版本升级后,通过面板发现写入性能存在明显的毛刺。如下是排除和解决的过程:

  • 求证业务是否平稳,是否因为业务流量原因导致,询问后排除。
  • 查看内核其他指标面板是否正常,发现写memtable时延波动明显,写WAL以及流控令牌获取均有不等的毛刺,推测是GC导致。
  • 求证,使用pprof抓取火焰图,重点分析火焰图上颜色较深的部分,通过内存池化或者减少这部分函数内的变量数量和内存申请次数进行优化,优化后再一次抓取火焰图,发现有明显的改善,毛刺消失了。

Q:openGemini数据容量取决于服务器存储的容量吗?在大容量下是否能保持较高的数据读取性能?

  • openGemini中,数据按时间范围进行分区,理论上没有数据容量上限,取决于服务器存储的容量大小,openGemini的数据读取性能取决于目标数据量规模,规模越大,读取数据的时间相对更长,比如历史数据有1PB,但是每次查询1天内的数据,性能同样是很高效的。

Q:openGemini适用于哪些具体的场景?比如,适用于物联网、金融行业或其他需要处理大量时序数据的场景。

  • 适用于物联网、车联网、运维监控、电力、能源、制造业、物流、智能建筑、金融等存在大量遥测数据存储和分析的场景

Q:openGemini时序数据库如何优化数据插入和存储问题?

  • 数据插入优化建议:
  • 尽量采用批量写入,减少网络交互次数
  • 集群模式下,数据写入压力尽量分散到不同的ts-sql
  • 如果内存资源充足,可以开启Hot模式(对应配置文件:shard-tier= hot)
  • 如果内存资源充足,可以适当调大缓存的占比,默认 10%(imm-table-max-memory-percentage)

Q:openGemini兼容现有哪些时序数据库工具链?

  • 数据可视化工具:Grafana、Chronograf等
  • 大数据分析工具:Flink、Spark等
  • SDK驱动:兼容已有的C/C++, Java, Python, Javascript, Rust, Go等SDK
  • 其他:支持InfluxDB的带界面的客户端

Q:openGemini支持信创操作系统吗?目前在行业内成熟的应用或者解决方案有哪些?

  • 信创的操作系统中仅支持openEuler。目前openGemini整理的行业内成熟的解决方案有:
  • 基于openGemini + EMQ + Kuiper + grafana的物联网数据可视化解决方案
  • openGemini + ngix的集群负载均衡解决方案
  • openGemini作为prometheus后端存储的解决方案
  • openGemini + KubeEdge的边缘计算解决方案
  • 等等,还有更多待挖掘

Q:如何评估和比较不同时序数据库的性能和功能?主要依据哪些项目测试和指标?

  • 性能和功能评估这块,先说性能:
  • 需要了解自己的业务,比如需要存储什么数据,有哪些字段,1分钟或者1秒钟需要写入的数据量大约是多大,单条数据长度,并发量多大,未来预期增长水平,能接受的最大查询时延,自己能提供的机器规格是多大,购买硬件设备的预算是多少等等。
  • 依据上述类容,使用现有的测试工具(比如TSBS)进行多种数据库进行性能对比,同时还要自己写一些测试程序来完成和业务相关的性能和功能测试,包括稳定性
  • 测试指标通常是写入平均速度、查询平均时延两大类,部分业务可能关注查询的P95、P99性能数据。
  • 功能方面:了解自己业务痛点和现有的查询语句,对比其他时序数据库提供的能力。

Q:openGemini 单机和分布式的语法都是一样的吗?openGemini能否根据硬件情况自动调优

  • 单机集群的语法是一样的。

openGemini还不能根据硬件情况自动调整参数优化,这需要大量的应用案例学习。
而且这件事情很复杂,仅仅根据硬件情况只能优化一小分部参数,比如缓存、并发数量等。
硬件上运行的应用有可能读多写少,有可能写多读少,也有可能读写差不多,有可能时间线很少,但数据量很大,也有可能时间线很多,单条时间线数据量很少,这些情况有需要重新调整缓存和并发,以及其他参数。这也是一个值得研究的问题。

Q:新手小白如何参与openGemini开源项目贡献?

  • openGemini的贡献可分为代码非代码贡献

  • 非代码贡献包括文档以及翻译、活动策划、社区运营、社区大使、技术文章分享、源码分享等。

  • 代码贡献包括内核功能特性开发、Bug修复、测试用例编写、周边工具开发、生态集成工具开发等。

  • 至于如何参与项目贡献,可以先联系社区,告知你的擅长领域和方向,根据你的情况,社区会提供相关的开发、运营、文档编写等方面的指导。

目前社区有很多事情可以做,但还没有全部都以issue的方式发布出来。新手小白,可以从贡献文档、修复简单Bug、简单的小功能做起,也可以参与到社区运营、活动策划中。

Y 推荐文献

  • opengemini

X 参考文献

  • 华为云
  • OpenGemini

git clone https://github.com/openGemini/openGemini.git
cd openGemini
python3 build.py --clean

单机版的构建结果:

> ls build
> ts-cli ts-meta ts-monitor ts-server ts-sql ts-store

ts-server 是独立运行版

posted @ 2024-12-23 16:23  千千寰宇  阅读(276)  评论(0)    收藏  举报