时序数据库存储架构深度对比:InfluxDB、Prometheus、VictoriaMetrics、TimescaleDB

引言

时序数据库(TSDB)是可观测性存储的基石。如果说上一篇是宏观的"存储地图",那本篇就是深入地图中的"核心城区"——四大主流 TSDB 的存储引擎。无论你是刚开始接触 Prometheus 还是正在调研 VictoriaMetrics,理解底层存储引擎的设计哲学,能帮你更高效地用对它们。

新手速查 — 四大 TSDB 一句话定位

数据库定位一句话理解
InfluxDB专用时序数据库最早流行的 TSDB,1.x/2.x 用自研 TSM 引擎,3.0 转型列式存储
Prometheus云原生监控标准自带本地 TSDB,单节点设计,被 Kubernetes 生态广泛采用
VictoriaMetrics高性能替代方案兼容 Prometheus 但更省内存,支持集群,冷数据压缩比极高
TimescaleDBPostgreSQL 时序扩展不想学新查询语言就用 Postgres + 时序优化

如果读到"LSM-Tree"、“Compaction”、“倒排索引"这些词觉得陌生,别担心——本篇就从最核心的存储引擎说起,先搞清楚它要解决什么问题。

InfluxDB

存储引擎演进:从 LSM 到 TSM

InfluxDB 1.x/2.x 的存储引擎核心是 TSM(Time-Structured Merge Tree),它从 LSM-Tree 演化而来,专门针对时序数据优化[4][9]。存储引擎由四个核心组件构成[4]

下图展示了 TSM 存储引擎的四个核心组件及其数据流:

mermaid
flowchart TD
    A[WAL<br/>预写日志] --> B[Cache<br/>内存缓存]
    B --> C[TSM Files<br/>时序合并树文件]
    C --> D[TSI<br/>倒排索引]

    style A fill:#FF9800,color:#fff
    style B fill:#2196F3,color:#fff
    style C fill:#2196F3,color:#fff
    style D fill:#9C27B0,color:#fff

图说明:上图展示了 InfluxDB 1.x/2.x TSM 存储引擎的四大组件。写入路径从左至右依次经过 WAL 预写日志 → 内存 Cache → TSM 文件,TSI 倒排索引为高效的多维度查询提供支持。橙色节点代表写入入口,蓝色节点代表数据存储与处理层,紫色节点代表索引组件。

写入路径与 WAL 机制

写入流程[4]

  1. 写请求追加到 WAL 文件末尾
  2. 通过 fsync() 强制刷盘(保证持久性)
  3. 更新内存 Cache
  4. 磁盘写入成功后返回确认
mermaid
sequenceDiagram
    participant Client as 写入请求
    participant WAL as WAL
    participant Cache as Cache
    participant TSM as TSM Files
    Client->>WAL: 1. 追加写入
    WAL->>WAL: 2. fsync 刷盘
    WAL->>Cache: 3. 更新内存
    Cache-->>Client: 4. 返回确认
    Note over Cache,TSM: 定期 snapshot
    Cache->>TSM: 5. flush 为 TSM 文件

图说明:上图展示了 InfluxDB 的完整写入路径。写入请求先追加到 WAL 并 fsync 刷盘保证持久性,然后更新内存 Cache 后立即返回确认(1-4 步)。后台定期将 Cache 快照刷新为 TSM 文件(第 5 步),实现从热数据到冷数据的异步流转。

Cache 特性[4]:按 key(measurement + tag set + field key)组织,每个 field 独立按时间排序,存储未压缩数据,重启时从 WAL 重建,查询时合并 Cache 数据与 TSM 文件数据。

TSM 文件格式与压缩

TSM 文件以列式格式存储压缩后的序列数据,只存储序列中值的差异(deltas)[4]。Compaction 过程将数据组织为长序列以优化压缩和扫描查询:

TSM 文件层级合并(类似 LSM 的 leveled compaction):数据按 series key 分组再按时间排序,多个 TSM 文件合并为更高层级,合并后 WAL 被截断、Cache 被清空。

TSI(Time Series Index)倒排索引

随着基数增长,早期内存索引在 100-400 万 series 时达到上限[5][6]。TSI 将索引持久化到磁盘,使用 mmap 让 OS 作为 LRU 缓存管理[6]

TSI 架构(基于 LSM-Tree)[5]

mermaid
flowchart TD
    A[Index<br/>单 Shard 完整索引] --> B[Partition<br/>分片分区]
    B --> C[LogFile L0<br/>内存索引+WAL<br/>阈值 5MB]
    B --> D[IndexFile<br/>L1/L2 层级合并<br/>mmap 不可变]
    C -->|compaction| D
    D --> E[SeriesFile<br/>跨库 series key<br/>自增 ID]

    style A fill:#9C27B0,color:#fff
    style B fill:#2196F3,color:#fff
    style C fill:#FF9800,color:#fff
    style D fill:#4CAF50,color:#fff
    style E fill:#9C27B0,color:#fff

图说明:上图展示了 InfluxDB TSI 倒排索引的 LSM-Tree 架构。Index 分片为多个 Partition(蓝色),每个包含 LogFile(橙色,内存索引+5MB WAL 阈值)和 IndexFile(绿色,不可变 mmap 层级合并)。SeriesFile(紫色)为所有跨库 series key 维护自增 ID,LogFile 超过 5MB 阈值后压缩为 L1 IndexFile。

写入流程[5]:Series 添加到 SeriesFile 或查找已有返回自增 series ID → Index 维护 Roaring Bitmap 记录已有 series ID → Series 哈希后发送到对应 Partition → Partition 将 series 写入 LogFile → LogFile 写入磁盘 WAL 并添加到内存索引。

Compaction[5]:LogFile 超过 5MB 压缩为 L1 IndexFile;两个连续 L1 文件合并为 L2 IndexFile。

IndexFile 结构[5]:TagBlocks(维护单个 tag key 的 tag value 索引)、MeasurementBlock(维护 measurement 及其 tag key 索引)、Trailer(偏移信息 + HyperLogLog 基数估计草图)。

读取与查询——TSI 提供可组合的迭代器 API[5]

  • MeasurementSeriesIDIterator() → measurement 对应的所有 series ID
  • TagValueSeriesIDIterator() → tag value 对应的所有 series ID
  • 合并操作:Merge(去重并集)、Intersect(交集)、Difference(差集)

例如 WHERE region != 'us-west' 跨两个 shard 的查询构建为:

text
1
2
3
4
DifferenceSeriesIDIterators(
  MergeSeriesIDIterators(Shard1.MeasurementIterator, Shard2.MeasurementIterator),
  MergeSeriesIDIterators(Shard1.TagValueIterator("region","us-west"), Shard2.TagValueIterator("region","us-west"))
)

InfluxDB 3.0:FDAP 架构(列式存储革命)

InfluxDB 3.0(即 InfluxDB IOx)彻底抛弃了自定义存储格式,转向 FDAP 技术栈[7][8][9]

mermaid
flowchart TD
    A[Flight<br/>gRPC+Arrow 传输] --> B[DataFusion<br/>向量化查询引擎]
    B --> C[Arrow<br/>内存列式格式]
    C --> D[Parquet<br/>磁盘列式存储]

    style A fill:#2196F3,color:#fff
    style B fill:#9C27B0,color:#fff
    style C fill:#4CAF50,color:#fff
    style D fill:#4CAF50,color:#fff

图说明:上图展示了 InfluxDB 3.0 的 FDAP 技术栈四层架构。Flight 提供基于 gRPC 的 Arrow 数组高效网络传输;DataFusion 是 Rust 编写的向量化 SQL 查询引擎,支持投影/过滤下推与自动并行化;Arrow 是内存列式格式,实现跨组件零拷贝共享;Parquet 提供高效的磁盘列式存储格式。蓝色为传输层,紫色为查询引擎,绿色为数据格式层。

核心设计决策与数据[7]

  • Parquet 代替自定义 TSDB 格式,实测整体压缩比约 10x(相比原始数据),InfluxData 官方未公布相对 TSM 的精确对比数字
  • Arrow:内存列式格式,cache 友好,避免序列化开销,跨组件零拷贝共享
  • DataFusion:Rust 编写的向量化 SQL 查询引擎,支持投影/过滤下推、自动并行化
  • Flight:基于 gRPC 的 Arrow 数组网络传输协议,免序列化

架构组件[8][9]

  • Ingester:接收写入,内存中以 Arrow 缓存(立即可查),WAL 保证持久性,定期刷为 Parquet 文件到 S3/GCS/Azure Blob
  • Querier:构建查询计划,查询 Ingester 获取最新未持久化数据,查询 Catalog 获取 Parquet 文件位置
  • Compactor:后台合并小 Parquet 文件为大文件(类似 LSM compaction)
  • 存储计算分离(Lakehouse 模型):无状态计算节点,存储在对象存储上无限扩展

Prometheus

本地 TSDB 存储引擎

Prometheus 内置本地磁盘 TSDB,采用自定义高效格式,平均每样本仅 1-2 字节[10]

Pull 拉取
Prometheus 主动抓取 / 适合长期运行的 services
PrometheusTarget/ Exporterscrape
Prometheus 决定何时抓取
Push 推送
通过 Pushgateway 中转 / 适合批处理任务
BatchJobPushgatewayPrometheuspushscrape
应用决定何时推送,通过 Pushgateway 中转

磁盘文件布局[10]

text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
./data
├── 01BKGV7JBM69T2G1BGBGM6KB12     ← 持久化 Block (2小时)
│   ├── chunks/                     ← chunk 段文件 (每个最大 512MB)
│   │   └── 000001
│   ├── tombstones                  ← 删除标记
│   ├── index                       ← 倒排索引 (label → series 映射)
│   └── meta.json
├── chunks_head/                    ← Head Block 完成的 chunk (mmap)
│   └── 000001
└── wal/                            ← Write-Ahead Log
    ├── 000000002                   ← 128MB 段
    └── checkpoint.00000001

Head Block 与 Persistent Block

  • Head Block:当前接收写入的块,保存在内存中,通过 WAL 保障崩溃恢复[10]
  • Persistent Block:Head Block 满后(2 小时),持久化为不可变 Block,包含 chunks、index、tombstones[10]

Chunk 编码与 Gorilla 压缩

mermaid
flowchart TD
    A["原始数据<br/>120 samples / 2h"] --> B["Gorilla XOR 编码"]
    B --> C["delta-of-delta<br/>时间戳压缩"]
    B --> D["XOR<br/>值压缩"]
    C --> E["密封 Chunk<br/>150-250 bytes"]
    D --> E
    E --> F["不可变<br/>并发安全"]

    style A fill:#FF9800,color:#fff
    style B fill:#9C27B0,color:#fff
    style C fill:#2196F3,color:#fff
    style D fill:#2196F3,color:#fff
    style E fill:#4CAF50,color:#fff
    style F fill:#4CAF50,color:#fff

图说明:上图展示了 Prometheus Chunk 的结构特征。每个 Chunk 包含 120 个样本或 2 小时数据(先到者),采用 Gorilla XOR 编码(delta-of-delta 时间戳 + XOR 值压缩),大小约 150-250 字节。Chunk 密封后不可修改,确保并发读取安全性。

Prometheus chunk 是密封的、不可变的、Gorilla-XOR 编码的数据块,通常 150-250 字节[2][3]

mmap 机制

Prometheus TSDB 积极使用 mmap(内存映射文件)[10][3]:持久化 Block 的 index 文件和 chunk 文件 mmap、Head Block 完成的 chunk(chunks_head/)mmap。优势:OS 管理页面缓存(LRU),零拷贝访问,减少进程内存占用[3]

WAL 机制与远程存储接口

  • WAL 存储在 wal/ 目录,128MB 段,包含尚未压缩的原始数据[10]
  • 至少保留 3 个 WAL 文件,默认启用 WAL 压缩(2.20.0 起可将 WAL 大小减半)
  • 支持乱序写入(tsdb.out_of_order_time_window

Prometheus 通过四种方式集成远程存储[10]:Remote Write(写入远程 URL,snappy 压缩 protobuf over HTTP)、Remote Write Receiver(接收其他客户端样本)、Remote Read(从远程 URL 读回样本)、Remote Read 响应。

关键局限:远程读路径仅获取原始序列数据,所有 PromQL 评估仍在本地完成,存在可扩展性限制[10]。默认保留 15 天,单节点无集群[10]


VictoriaMetrics

集群组件架构

对于时序数据库来说,“集群"解决的核心问题是:单机撑不住了怎么办?VictoriaMetrics 把写入、存储、查询三个环节拆成独立服务,各司其职、独立扩缩容。

VictoriaMetrics 集群采用 Shared-Nothing 架构,由三个独立可扩展的服务组成[11]

VictoriaMetrics 集群数据流:写入与查询管道vminsert写入路由vmstorage 集群(Shared-Nothing)Node ANode BNode CNode Dvmselect查询处理写请求 →一致性哈希← 查询请求并行拉取返回结果vminsert 按一致性哈希将写入分发到 vmstorage 节点vmselect 向所有 vmstorage 节点并行拉取数据Shared-Nothing:节点间互不通信,独立扩展

动画说明:上方动画分阶段展示 VM 集群的数据流。第一阶段(15-35% 周期)展示写请求进入 vminsert,经一致性哈希分配到 vmstorage 节点;第二阶段(35-55%)展示查询请求进入 vmselect,向所有 vmstorage 节点并行拉取;第三阶段(55-75%)展示 vmstorage 返回查询结果;最终阶段展示完整的 Shared-Nothing 架构特征。

下面用两个小图分别展示写入路径和查询路径:

mermaid
flowchart TD
    A[vminsert<br/>写入路由] --> B[vmstorage<br/>Node A]
    A --> C[vmstorage<br/>Node B]
    A --> D[vmstorage<br/>Node C]
    A --> E[vmstorage<br/>Node D]

    style A fill:#2196F3,color:#fff
    style B fill:#4CAF50,color:#fff
    style C fill:#4CAF50,color:#fff
    style D fill:#4CAF50,color:#fff
    style E fill:#4CAF50,color:#fff

图说明:写入路径。vminsert(蓝色)按 metric name 加所有 labels 的一致性哈希将数据分发到 vmstorage 节点(绿色)。

mermaid
flowchart TD
    A[vmselect<br/>查询处理] --> B[vmstorage<br/>Node A]
    A --> C[vmstorage<br/>Node B]
    A --> D[vmstorage<br/>Node C]
    A --> E[vmstorage<br/>Node D]

    style A fill:#2196F3,color:#fff
    style B fill:#4CAF50,color:#fff
    style C fill:#4CAF50,color:#fff
    style D fill:#4CAF50,color:#fff
    style E fill:#4CAF50,color:#fff

图说明:查询路径。vmselect(蓝色)发起查询时从所有 vmstorage 节点(绿色)并行拉取数据。

  • vminsert:按 metric name + 所有 labels 的一致性哈希分发到 vmstorage 节点[11]
  • vmstorage:存储原始数据,节点互不通信、不共享数据
  • vmselect:执行查询,从所有 vmstorage 节点获取所需数据

存储引擎:改进的 MergeTree / LSM

vmstorage 的存储引擎基于类 LSM-Tree 结构,数据经历多级转换[12]

mermaid
flowchart TD
    A[In-memory Part<br/>1-17 MB] --> B[Small Part<br/>磁盘 ≤10 MB]
    B --> C[Big Part<br/>磁盘 ≤1 TB]

    style A fill:#FF9800,color:#fff
    style B fill:#2196F3,color:#fff
    style C fill:#4CAF50,color:#fff

图说明:上图展示了 VictoriaMetrics 存储引擎中三种 LSM Part 的层级流转。In-memory Part(橙色,1-17 MB)是第一次 flush 的产物,立即可查询;Small Part(蓝色,磁盘 ≤10 MB)从内存 flush 或合并产生;Big Part(绿色,磁盘 ≤1 TB)通过合并产生。颜色从暖到冷表示数据从热到冷的生命周期。

三种 LSM Part 类型[12]

Part 类型位置大小范围说明
In-memory part内存1-17 MB第一次 flush 产物,可查询
Small part磁盘(data/small/最大 10 MB从内存 flush 或合并产生
Big part磁盘(data/big/最大 ~1 TB合并产生

Flush 触发条件[12]:大小触发(buffer 达到约 120MB)或时间触发(距上次 flush 超过 2 秒)。最多 60 个 in-memory part,约占系统内存 10%。

Merge 机制与压缩算法

合并不按固定调度运行,基于"因果关系”[12]

Merge Multiplier(合并乘数)[12]:定义输出 part 总大小/最大输入 part 大小,默认值 7.5,单次最多合并 15 个 part。合并时同时执行去重(Deduplication),通过 -dedup.minScrapeInterval 配置去重窗口。

压缩算法[3][12]

  • 热数据路径:Gorilla 编码(delta-of-delta + XOR)
  • 冷数据(导出到 S3 的 Parquet):Gorilla + ZSTD 叠加,压缩比可达 25-35x
  • 与 Prometheus 本地存储相比,内存占用显著降低,单机可处理数千万级时间序列

层级降采样(Downsampling)

新手类比:你不需要记得一周前每一秒的 CPU 利用率是多少——看小时级别的平均值就够了。TSDB 的"层级降采样"就是这个思路:数据越老,保留的分辨率越低,节省存储空间的同时不影响趋势分析。

VictoriaMetrics 没有内置的自动降采样(需要搭配 vmctl 等外部工具),但理解这个概念对后续理解 TSDB 冷热分层至关重要:

TSDB 层级降采样:数据分辨率随时间降低原始数据(15s 间隔)Compaction5 分钟聚合(avg/min/max)20 点→120 点→120 点→1Compaction1 小时聚合(12×5m → avg)12 点→112 点→1Compaction1 天聚合(24×1h → avg)24 小时→1高精度趋势长期归档15 秒原始 → 5 分钟聚合 → 1 小时聚合 → 1 天聚合存储密度:从 15 秒一个点到 1 天一个点,降低 5760 倍

动画说明:上方动画展示 TSDB 层级降采样的四个阶段。原始数据(橙色,15 秒间隔)经过多次 compaction 逐步合并为更大时间窗口的聚合数据——5 分钟聚合(avg/min/max, 每 20 点合 1 点)→ 1 小时聚合(12 个 5m 合 1 个)→ 1 天聚合(24 个 1h 合 1 个)。存储密度随着分辨率降低而指数级提升——从 15 秒一个数据点到 1 天一个点,存储需求降低 5760 倍。

性能特点

指标数据
单机写入可达百万级数据点/秒[11]
内存效率官方称低 7x(VM blog),第三方基准实测约 1.7x[3]
压缩比热数据 ~12x(Gorilla),冷数据 25-35x[3]
推荐集群阈值写入率 > 100 万点/秒[11]

流式聚合:VictoriaMetrics 提供了内置的流式聚合(Stream Aggregation)功能,可在写入端对指标进行实时降采样和聚合,减少存储量与查询负载。深入实践请参阅 Stream Metrics 系列


TimescaleDB

核心定位:PostgreSQL 扩展

TimescaleDB 是 PostgreSQL 扩展,在 Postgres 坚实的事务引擎、复制和生态之上叠加时序优化[3][15]

Hypertable 与 Chunk 架构

mermaid
flowchart TD
    A[Hypertable<br/>逻辑表] --> B[Chunk 1<br/>T0-T1]
    A --> C[Chunk 2<br/>T1-T2]
    A --> D[Chunk 3<br/>T2-T3]
    A --> E[Chunk N<br/>Tn-1-Tn]

    style A fill:#9C27B0,color:#fff
    style B fill:#2196F3,color:#fff
    style C fill:#2196F3,color:#fff
    style D fill:#2196F3,color:#fff
    style E fill:#2196F3,color:#fff

图说明:上图展示了 TimescaleDB 的 Hypertable 逻辑架构。Hypertable(紫色)是对用户透明的逻辑表,自动按时间范围分区为多个 Chunk(蓝色),每个 Chunk 底层是标准的 PostgreSQL heap 表。INSERT 操作根据时间戳自动路由到对应的 Chunk,SELECT 查询通过 Chunk 裁剪(Chunk Exclusion)跳过不相关的时间分区。

  • Hypertable:面向用户的逻辑表,自动按时间分区[15]
  • Chunk:底层是标准 Postgres heap 表,受 MVCC 和锁定约束[3][15]

压缩策略:列式存储

压缩算法(列级别)[15]:Dictionary Encoding(字典编码)、Delta Encoding(增量编码)、Gorilla Compression(浮点数 XOR 压缩)、Run-Length Encoding(行程长度编码)。

压缩效果[14][15]:chunk 压缩后存储减少 最高 98%,列式格式支持快速扫描和聚合,可透明查询列式数据无需解压。

Hypercore(TimescaleDB 2.18, 2025)[17]:统一 rowstore/columnstore 引擎,同一 hypertable 同时持有热行 + 压缩冷列,基于 chunk age 自动转换。

连续聚合(Continuous Aggregates)

连续聚合是增量更新的物化视图,避免每次查询重新计算聚合[14][15]

mermaid
flowchart TD
    A[Materialization Engine<br/>聚合引擎] --> B[Materialization Hypertable<br/>存储聚合结果]
    C[Invalidation Engine<br/>失效检测] --> A
    D[Query Engine<br/>查询引擎] --> B
    style A fill:#2196F3,color:#fff
    style B fill:#4CAF50,color:#fff
    style C fill:#FF9800,color:#fff
    style D fill:#9C27B0,color:#fff

图说明:上图展示了 TimescaleDB 连续聚合的四组件架构。Invalidation Engine(橙色)检测原始 Hypertable 的数据变更并通知 Materialization Engine(蓝色)执行增量聚合计算,结果写入 Materialization Hypertable(绿色)。Query Engine(紫色)透明地查询预聚合结果。

关键机制[14]:增量更新(只处理新增/变更数据)、物化阈值(滞后时间点)、两阶段事务、失效引擎跟踪数据变更。支持层级连续聚合(秒级→小时级→日级)和实时聚合(预聚合数据与最新原始数据合并)[14]

mermaid
sequenceDiagram
    participant Raw as 原始 Hypertable
    participant IE as Invalidation Engine
    participant ME as Materialization Engine
    participant Mat as 物化 Hypertable
    Raw->>IE: 数据变更通知
    IE->>IE: 计算失效区间
    IE->>ME: 触发增量刷新
    ME->>Raw: 读取新增/变更数据
    ME->>ME: 执行聚合计算
    ME->>Mat: 写入物化结果

图说明:上图展示了 TimescaleDB 连续聚合的增量刷新流程。原始 Hypertable 的数据变更通知 Invalidation Engine 计算失效区间,触发 Materialization Engine 读取新数据、执行聚合计算并写入物化 Hypertable。整个过程对用户透明且增量式执行。

与原生 TSDB 的差异

维度TimescaleDB原生 TSDB
存储引擎PostgreSQL heap + 列式压缩专用 TSM/LSM/Gorilla
事务语义完整 ACID(继承 Postgres)通常无 ACID
查询完整 Postgres SQL专用查询语言
压缩列式压缩(90-98%)Gorilla 12x+
连续聚合原生支持(增量物化视图)无(需外部工具)

TSDB 横向对比

底层数据结构对比

项目磁盘文件格式内存结构索引类型
InfluxDB 1.x/2.xTSM 文件(列式,delta 编码)Cache(未压缩)TSI(LSM-based 倒排,Roaring Bitmap,mmap)
InfluxDB 3.0Parquet(列式)Arrow(列式)运行时从 Parquet 元数据重建
Prometheuschunks/(段 512MB)+ indexHead Block(内存)倒排索引(block 内 label→series→chunk)
VictoriaMetricsLSM parts(in-memory/small/big)raw-row shards → in-memory partsmergeset 索引引擎
TimescaleDBPostgres heap table + 列式压缩 chunkPostgres shared_buffersB-tree(Postgres 原生)

写入路径与压缩算法对比

项目WAL 机制时间戳压缩值压缩平均字节/样本
InfluxDBappend + fsyncdelta 编码delta 编码(列式)~2-3 bytes
Prometheus128MB 段,snappy 压缩delta-of-delta (Gorilla)XOR (Gorilla)1-2 bytes
VictoriaMetrics内存 part flush 到磁盘delta-of-delta (Gorilla 改进)XOR + ZSTD(冷)~1-2 bytes(热),25-35x(冷)
TimescaleDBPostgres WALdelta encodingGorilla + RLE + Dictionary90-98% 压缩

集群/分布式架构对比

项目架构模式分片方式复制多租户
InfluxDB Enterprise数据节点+Meta 节点按 shardRP 副本数有限
InfluxDB 3.0存储计算分离(Lakehouse)Parquet 文件在对象存储对象存储冗余独立集群
Prometheus单节点(无集群)N/AN/AN/A
VictoriaMetricsShared-Nothing一致性哈希(metric+labels)replicationFactor 顺序传递accountID:projectID
TimescaleDBPostgres 流复制/Citus手动/第三方Postgres 复制DB 级别隔离