这是本节的多页打印视图。 .
工具箱
-
1: ZFS 存储管理
-
2: PGMQ 消息队列
-
3: SQLite + Litestream
-
4: TimescaleDB
在核心实例管理之外扩展 pgcli 能力的工具与使用指南。
- ZFS 存储 — 安装 ZFS 并管理 pool、dataset 和快照
- PGMQ 消息队列 — 在 PostgreSQL 内部实现持久化消息队列
- SQLite + Litestream — 管理宿主机 SQLite 数据库并持续备份到 S3
- TimescaleDB — 在 PostgreSQL 上使用时序 hypertable 和 time_bucket 分析
1 - ZFS 存储管理
pg zfs 把一台 Linux 主机变成 ZFS 存储服务器:安装软件包、检查磁盘、创建带冗余的
pool,并管理 dataset 和 snapshot。它直接与内核交互,不读写 pg.yaml,也不依赖任何
PostgreSQL 实例。
命令按“资源 → 动作”组织:pg zfs pool create、pg zfs dataset create、
pg zfs snapshot rollback。没有封装的能力可通过 pg zfs raw 原样透传。
支持的系统
pg zfs install 从 /etc/os-release 识别发行版,并自动准备软件源后完成安装:
| 发行版 | ZFS 来源 | 实测版本 |
|---|---|---|
| Debian 12 (bookworm) | Debian contrib,DKMS |
2.1.11 |
| Debian 13 (trixie) | Debian contrib,DKMS |
2.3.9 |
| Ubuntu 24.04 (noble) | Ubuntu universe |
2.2.2,使用 Ubuntu 内核模块包 |
| Ubuntu 26.04 | Ubuntu universe |
2.4.1 |
| Fedora 44 | OpenZFS release RPM,DKMS | 2.4.4 |
| Rocky Linux 10 | OpenZFS release RPM + EPEL,DKMS | 2.2.11 |
这些是安装器测试时上游仓库提供的版本,实际安装到的版本由仓库决定;
pg zfs status 总会打印已安装的用户态与内核模块版本。
其他发行版只会被识别并给出手动安装指引,不会自动安装。仅支持 Linux。
安装
命令零参数、幂等:
- 检查权限(root,或可用的
sudo -n)。 - 自动启用所需软件源(Debian
contrib、Ubuntuuniverse、 Fedora/Rocky 的 OpenZFS release 仓库)。 - 安装 ZFS、内核头文件/模块和 ZED 守护进程。
- 加载模块并把 ZFS 的 systemd 单元设为开机自启。
- 打印用户态与内核模块版本。
在健康的机器上重复执行是 no-op。安装不提供 --version、--backports、
--force 等开关——需要特定 ZFS 版本时请用发行版包管理器升级,之后交由
pg zfs 管理。
随时查看状态:
RAID 级别与 ZFS 的对应关系
ZFS 没有标准的 RAID2 / RAID3 级别,对应关系如下:
--type |
ZFS 布局 | 冗余 | 类比 |
|---|---|---|---|
stripe(raid0) |
条带 vdev | 无 | RAID0 |
mirror(raid1) |
镜像 | 1 份以上副本 | RAID1 |
raidz1 |
单校验 | 1 块盘 | RAID5 |
raidz2(raid2) |
双校验 | 2 块盘 | RAID6 |
raidz3(raid3) |
三校验 | 3 块盘 | 三校验 RAID |
为方便起见也接受 raid0/raid1/raid2/raid3 别名。
快速开始
pool 挂载在 /tank,dataset 挂载在 /tank/backups。回滚会丢弃快照之后的所有
改动,执行前需要确认。
复用旧 pool 的磁盘
ZFS 会在盘上保留 vdev 分区和标签,export 不会清除它们,因此安全校验会拒绝
这些盘。创建新 pool 前先销毁旧 pool 并清理残留:
如果整盘 labelclear 报错,改清成员分区(/dev/vdd1、/dev/vde1)。旧 pool
已被销毁时跳过第 1 步。
磁盘安全
pg zfs disk list 会列出每块整盘的挂载点、ZFS 归属以及 pgcli 是否认为它可以安全
交给 ZFS 使用。
创建 pool 时拒绝:
- 根盘,或带有已挂载分区的磁盘;
- 分区、loop、ROM、CD-ROM 设备;
- 带有文件系统(
ext4、xfs、swap、LVM、LUKS、mdraid 等)的磁盘; - 已有 ZFS 标签的磁盘;
- 只读磁盘;
- 重复设备(包括通过不同路径指向的同一块盘);
- 已被其他 pool 使用的磁盘。
--force 只跳过确认提示,不会映射为 ZFS 的 -f,也不绕过任何安全检查。
创建 pool
自动布局
位置参数的数量决定布局:
| 盘数 | 布局 |
|---|---|
| 1 | stripe(告警:无冗余) |
| 2 | mirror |
| 3 | raidz1 |
| 4 及以上 | raidz2 |
用 --type 覆盖:
同一 vdev 内请尽量使用容量一致的磁盘(性能也应相近)。ZFS 不会拒绝混合容量, 但 vdev 的可用容量由最小的成员盘决定,大盘多出的空间会被浪费。同一 pool 的不同 vdev 之间可以容量不同。
多个 vdev
使用可重复的 --data 分组,并显式给出 --type。每个分组是一个 vdev,pool 在
这些 vdev 之上做条带:
--type 作用于每个 --data 组内部,而不是 pool 整体:每个组建成该布局的一个
vdev,pool 在这些 vdev 之上条带。所有组共用同一个 --type;pg zfs 不支持
一次创建中混用 mirror、raidz2 等布局,混合拓扑需使用
pg zfs raw zpool create。
pool 容量为各 vdev 可用容量之和;冗余按 vdev 计算,任一 vdev 失去超出其容错能力 的磁盘都会导致整个 pool 不可用。
SLOG、special、cache 与热备
--special 跟随 --type(这里是 mirror)。--log 两块及以上自动 mirror,单块为
stripe 并给出告警。--cache 与 --spare 接受独立的设备列表。
同一块盘只能出现在 data、special、log、cache、spare 中的一处。
建池后也可以用 pool add 追加热备盘:
默认属性
除非指定 --no-defaults:
| 对象 | 属性 | 值 |
|---|---|---|
| pool | ashift |
12 |
| pool | autoexpand |
on |
| pool | autotrim |
所有数据盘均为非旋转盘且支持 discard 时为 on |
| 根 dataset | compression |
zstd |
| 根 dataset | atime |
off |
| 根 dataset | xattr |
sa |
| 根 dataset | acltype |
posixacl |
| 根 dataset | dnodesize |
auto |
pool 属性用 --prop/-p 覆盖(对应 zpool -o),根 dataset 属性用
--fs-prop/-P 覆盖(对应 zpool -O):
-o 是 pgcli 的全局输出参数,所以 pool/dataset 属性使用 -p/-P。在
pg zfs raw 内部,ZFS 原生参数照常可用。
管理 pool
扩容与修复:
attach 针对 mirror vdev。给 raidz vdev 增加磁盘(raidz expansion)需要
OpenZFS 2.3 及以上;Debian 12(2.1)和 Ubuntu 24.04(2.2)不支持。新版本上可用
pg zfs raw zpool attach 操作。
卸载与彻底删除 pool
pg zfs pool export 会卸载所有 dataset 并把 pool 从主机上分离,但数据仍完整
保存在成员盘上:
已 export 的 pool 不会在开机时自动导入。重新接管时先列出可导入的 pool,再导入:
pg zfs pool destroy 会删除 pool、所有 dataset 和全部数据:
destroy 只能作用于已导入的 pool。如果 pool 已 export,需要先导入再销毁:
不加 --force 时这两个命令都会要求确认;EOF 和默认答案均为“否”。非交互自动化
必须显式传 --force。--force 只跳过确认,绝不会映射为 ZFS 的 -f,也不绕过
任何安全校验。export 可逆,destroy 不可逆。
持久化与开机自动导入
pool 和 dataset 默认在重启后自动恢复,无需额外配置:
pg zfs install会启用zfs-load-module、zfs-import-cache、zfs-mount和zfs-zed(单元存在时)。zpool create会把 pool 记录到/etc/zfs/zpool.cache;开机时zfs-import-cache据此导入。- ZFS 不使用
/etc/fstab。每个 dataset 的mountpoint是 ZFS 属性,开机时 由zfs-mount完成挂载。pool 的根 dataset 挂载在/<pool>,子 dataset 挂载在其下(例如/fast/data)。 - pool 状态保存在成员盘上,异常断电或非正常关机后数据仍可导入。
检查配置:
重启后 pg zfs pool list 应能看到该 pool,findmnt /fast 应显示已挂载。
真实硬件上建议用 /dev/disk/by-id/... 等稳定路径创建 pool。/dev/sdX
名称可能在重启后变化,设备找不到时 pool 不会导入。把磁盘移到其他主机前先执行
pg zfs pool export tank,对方即可用 pg zfs pool import tank 接管。
Dataset 与 snapshot
带子 dataset 的 dataset 必须加 --recursive 才能销毁。
回滚
pg zfs snapshot rollback tank/data@snap1 把 dataset 恢复到快照记录的状态,
之后的所有改动都会被丢弃。如果存在更新的快照,ZFS 会拒绝回滚;加 --recursive
会先删除这些更新的快照。依赖该快照的 clone 也会阻止回滚——先删除 clone,或用
pg zfs raw zfs rollback -R(会一并删除依赖的 clone)。非交互运行需加
--force 跳过确认。
克隆
pg zfs dataset clone tank/data@snap1 tank/clone 从快照创建一个新的可写
dataset。未改动的数据块按写时复制共享,不会复制整份数据。用 --prop 设置新
dataset 的属性:
clone 依赖的快照在 clone 存在期间不能销毁。需要删除旧快照时,先用
pg zfs raw zfs promote tank/clone 提升 clone,使其独立于原 dataset。
raw 透传
raw 绕过所有安全校验和确认,是未封装参数的逃生通道——请像以 root 身份直接运行
zpool/zfs 一样谨慎。二进制管道也可以工作,例如:
权限模型
所有会调用 zpool 或 zfs 的命令都会先检查权限:
-
root:直接执行;
-
非 root 且
sudo -n可用:所有 ZFS 命令通过sudo -n执行; -
否则立即失败:
pgcli 从不弹出 sudo 密码提示。pg zfs status 和 pg zfs disk list 在无权限时仍会
输出静态信息,并把 pool 详情标记为不可用。
命令参考
| 命令 | 用途 |
|---|---|
pg zfs install |
安装 ZFS(零参数、幂等) |
pg zfs status |
发行版、软件包、模块与 pool 概览 |
pg zfs disk list [--all] |
整盘、挂载、ZFS 归属与安全性 |
pg zfs pool create NAME DEVICE... |
创建 pool |
pg zfs pool list |
列出 pool |
pg zfs pool status [NAME] [--brief] |
详细状态 |
pg zfs pool add NAME DEVICE... |
增加 vdev |
pg zfs pool attach/detach NAME DEVICE [NEW] |
扩大/缩小 mirror |
pg zfs pool replace NAME OLD NEW |
替换磁盘 |
pg zfs pool offline/online NAME DEVICE |
下线/上线磁盘 |
pg zfs pool export/import [NAME] |
在系统间迁移 pool |
pg zfs pool scrub NAME [--stop] |
校验数据完整性 |
pg zfs pool destroy NAME |
销毁 pool 及全部数据 |
pg zfs dataset create/list/set/clone/destroy |
dataset 生命周期 |
pg zfs snapshot create/list/rollback/destroy |
snapshot 生命周期 |
pg zfs raw zpool/zfs ARGS... |
绕过封装的透传 |
故障排查
pg zfs install 找不到软件包。 检查发行版归档源是否可达、contrib/universe
是否已启用。安装器只修改发行版官方归档源(绝不动第三方源),并在每个被修改的文件旁
留下 *.pgcli.bak。
DKMS 构建失败。 确认当前内核的头文件已安装(Debian/Ubuntu 为
linux-headers-$(uname -r),Fedora/Rocky 为 kernel-devel-$(uname -r))。安装器
会给出构建日志位置;它不会重启或更换内核。内存很小的 VM 请先加 swap。
Secure Boot 已开启。 DKMS 模块需要签名。pgcli 只报告该情况,不会自动登记密钥; 请用 MOK 签名模块,或在测试机上关闭 Secure Boot。
模块未加载。 执行 modprobe zfs 后运行 pg zfs status。安装器会启用
zfs-load-module、zfs-import-cache、zfs-mount 和 zfs-zed,使 pool 开机
自动导入。
磁盘因分区或旧标签被拒绝。 如果确认磁盘空闲,清理残留:
在 OpenZFS 2.2 及更新版本上,若标签位于 ZFS 成员分区内,清理整盘会报
failed to clear label for /dev/vdb。此时改为清理该分区,再清分区表:
绝不要对已导入 pool 的成员盘这样做——pg zfs disk list 会显示所属 pool,安全检查
也会拒绝这类磁盘。
SELinux(Fedora/Rocky)。 ZFS 挂载点使用标准文件上下文。若服务无法访问某个
dataset,请查看 ausearch/journalctl 中的 AVC 拒绝记录,而不是全局关闭 SELinux。
重启后 pool 没有自动导入。 以 root 运行 systemctl status zfs-import-cache
和 zpool import。用 /dev/sdX 名称创建的 pool 会受设备重命名影响;真实硬件上
建议使用稳定路径(/dev/disk/by-id/...)创建 pool。
2 - PGMQ 消息队列
pgmq 把 PostgreSQL 变成持久化消息队列:不需要 Redis、RabbitMQ 或后台进程,
只有 pgmq schema 下的一组表和 SQL 函数。生产者发送 JSON 消息,消费者按可见性
超时读取,消息在被显式删除或归档之前一直保留在队列里。
安装
pgcli 从 Pigsty 仓库安装扩展包,并在实例的默认数据库执行
CREATE EXTENSION IF NOT EXISTS pgmq:
同一实例的其他数据库需要单独创建扩展:
查看扩展版本:
快速开始
通过 pgcli 执行同样的语句:
队列在磁盘上的形态
每个队列都是 pgmq schema 里的一张表:
| 对象 | 用途 |
|---|---|
pgmq.q_<name> |
在队消息 |
pgmq.a_<name> |
pgmq.archive 写入的归档表 |
pgmq.meta |
list_queues 和 metrics 使用的队列表册 |
一条在队消息包含 msg_id、read_ct(投递次数)、enqueued_at、vt
(再次可见时间)和 message jsonb。较新版本还增加了 last_read_at 和
headers。
消息就是表里的行,因此 pgmq.send 参与事务:随所在语句一起提交或回滚。
队列流量也是普通表流量——高频 send/delete 会像其他高写入表一样产生膨胀。
发送消息
send 返回新的 msg_id,send_batch 为每条消息各返回一个 id。批量场景应使用
send_batch,而不是循环调用 send——它只有一次网络往返,且整批原子写入。
消息必须是合法 JSON,存储类型为 jsonb:键会重排、重复键会被去掉。不要把大块
二进制内容放进队列。
较新版本支持 headers jsonb 参数,可携带 trace id、FIFO 分组键等元数据。
读取与确认
read(queue, vt, qty) 最多返回 qty 条可见消息,并在 vt 秒内让它们对其他
消费者不可见。读取不会删除消息,每条都必须显式确认:
pgmq.pop('jobs') 读取并删除一步完成;只有在消费者崩溃时丢一条在途消息可以接受时
才使用它。
长期运行的消费者不要用紧凑轮询:
它最多等待 max_poll_seconds(5 秒),每 poll_interval_ms(100 毫秒)检查一次,
有消息到达就立即返回。
可见性超时与重试
可见性超时是 PGMQ 的投递模型:
read把vt设为now() + timeout,并递增read_ct;- 在
vt之前,其他消费者看不到这条消息; delete或archive将其移除。若消费者崩溃或没有及时确认,消息在vt到期后 重新可见,并被投递给另一个消费者。
因此跨故障场景下投递语义是 at-least-once(至少一次):vt 要大于最坏处理
耗时,消费者要保证幂等。长任务可以在不重新读取的情况下续期:
read_ct 可以直接当重试计数器使用。
归档与死信
delete 是永久删除。archive 把消息移到 pgmq.a_<queue>,便于保留、审计或
重放:
一个简单的死信模式是归档重试次数过多的消息:
没有这类规则时,毒消息会被无限重投。
监控
metrics 返回 queue_length、queue_visible_length、最新/最老消息的年龄以及
total_messages。较旧的扩展版本返回的列更少。
也可以直接查询队列表:
queue_length 持续增长或 oldest_msg_age_sec 不断变大时应当告警:要么消费者
挂了,要么消费速度跟不上。
维护
create_unlogged用崩溃安全性换写入速度,只用于可重新生成的数据。create_partitioned依赖pg_partman扩展。它避免超高吞吐队列把单表撑大; 旧分区会自动丢弃。purge_queue清空但保留队列;drop_queue彻底删除。- 队列名最长 47 个字符。
普通队列在高频 send/delete 下会产生 dead tuple。确认 autovacuum 能跟上;写入量 很大时优先考虑归档代替删除,或使用分区队列。
权限
PGMQ 函数以调用者的权限执行。应用角色需要 schema、函数和队列表的访问权限:
pgmq.create 应以队列所有者身份执行;建表权限通常不应授予应用角色。新建队列需要
单独授权,或预先配置 default privileges。
函数参考
| 函数 | 用途 |
|---|---|
pgmq.create(name) |
创建队列 |
pgmq.create_unlogged(name) |
创建 unlogged 队列 |
pgmq.create_partitioned(name, ...) |
创建分区队列 |
pgmq.send(name, msg [, delay]) |
发送单条消息,返回 msg_id |
pgmq.send_batch(name, msgs jsonb[]) |
原子批量发送 |
pgmq.read(name, vt, qty) |
读取并隐藏消息 |
pgmq.read_with_poll(name, vt, qty, ...) |
长轮询读取 |
pgmq.pop(name [, qty]) |
读取并删除 |
pgmq.delete(name, id) / pgmq.delete(name, ids[]) |
确认并移除消息 |
pgmq.archive(name, id) / pgmq.archive(name, ids[]) |
移入归档表 |
pgmq.set_vt(name, id(s), seconds) |
延长可见性超时 |
pgmq.list_queues() / pgmq.metrics(name) |
查看队列状态 |
pgmq.purge_queue(name) |
删除全部消息,保留队列 |
pgmq.drop_queue(name) |
删除队列及其归档表 |
故障排查
extension "pgmq" is not available。 实例镜像里没有扩展包,或该数据库没有
对应构建。执行 pg extension install pgmq -i <instance>,并在目标数据库中创建
扩展。
消息被重复投递。 处理时间超过 vt,或消费者在 delete 前退出。增大 vt、
在长任务中调用 set_vt,并保证处理逻辑幂等。
队列长度不下降。 消费者失败或太慢。检查 read_ct、应用日志和
metrics().oldest_msg_age_sec。PGMQ 会无限重投失败消息,除非归档或删除它们。
性能随时间下降。 普通队列就是普通表。检查 dead tuple 与 autovacuum,批量
发送和读取;持续高吞吐场景考虑 create_partitioned。
permission denied for schema pgmq。 给应用角色授予 schema 的 USAGE、
函数的 EXECUTE,以及每个队列表的相应表权限。
3 - SQLite + Litestream
pg sqlite 在 Linux 宿主机上管理 SQLite 数据库,并可选地用
Litestream 把 WAL 变更持续复制到 S3 兼容对象存储。
SQLite 直接以宿主机进程运行:不使用容器、不引入 Go SQLite driver,也不依赖 cgo。
所有 SQL 都由 sqlite3 命令行执行;每个数据库对应一个独立的 Litestream 后台进程。
复制是异步的(默认 sync interval 下 RPO 约 1 秒)。它属于持续备份,不是高可用: SQLite 仍然只有一个写入者,故障切换意味着要在别处恢复备份。
目前仅支持 Linux。
工作原理
- 每个受管数据库在
pg.yaml中有一条记录,并在<base>/sqlite/<name>/下有独立 的状态目录。 pg sqlite create会启用journal_mode=WAL,该设置持久化在数据库文件头中。pg sqlite replication enable生成专属的litestream.yml(权限0600),并在 后台启动litestream replicate。Litestream 跟随 WAL,把 LTX 对象上传到指定 bucket。- S3 凭据只写入该
litestream.yml,绝不会进入pg.yaml,任何pg sqlite命令也不会打印它。
安装
命令是幂等的,会安装两个宿主机二进制:
- 如果
sqlite3不在PATH上,则通过发行版包管理器安装(apt-get、dnf、yum、 pacman、zypper 或 apk)。只有这一步可能需要 root;pgcli 使用免密sudo -n,不会交互式索要密码,失败时会给出明确提示。 - Litestream
0.5.17,从 GitHub 下载并校验官方 SHA256。可写时安装到/usr/local/bin,否则安装到~/.local/bin。
常用参数:
Litestream 0.3.x 与生成的配置不兼容;安装器会忽略 PATH 上的旧版本,直接安装
0.5.x。
创建与管理数据库
create 不会截断已有文件:指向一个已存在的 SQLite 数据库时会接管该文件、启用
WAL,并保留原有内容。--path 必须是绝对路径,且只移动数据库文件——配置、日志、
pid 和 Litestream 本地状态始终留在 <base>/sqlite/<name>/ 下。
info 读取 journal_mode、page size/count 和 freelist,默认执行
PRAGMA quick_check。大库可用 --integrity none 跳过检查,或
--integrity full 执行完整 integrity_check。
remove 只注销数据库并停止复制,数据库文件保留。加 --clean-data 才会删除
数据库、WAL 附属文件、Litestream 本地状态和日志(交互确认,--force 跳过)。
远端 S3 备份永远不会被自动删除。
启用 S3 复制
bucket 必须已经存在——Litestream 不会创建 bucket。请先用 mc mb、云控制台或
其他 S3 工具创建好。
本地 MinIO(测试用)
pgcli 可以直接用 MinIO addon 在宿主机上起一个对象存储:
如果 9000 端口已被占用,请改用 pg addon list 中显示的实际端点。然后把复制指向
这个本地存储:
其他 MinIO 或 S3 兼容存储使用相同的 flags,替换成自己的端点和凭据:
AWS S3,使用默认凭据链(命令行不传 key):
| 参数 | 默认值 | 说明 |
|---|---|---|
--s3-bucket |
必填 | 必须已存在 |
--s3-endpoint |
AWS 默认 | 完整 http(s)://host[:port],不能带路径或凭据 |
--s3-path |
sqlite/<name> |
对象 key 前缀;前导 / 会被去掉 |
--s3-region |
us-east-1 |
|
--s3-access-key / --s3-secret-key |
默认凭据链 | 必须同时提供或同时省略 |
--s3-uri-style |
path |
path 或 virtual-host |
--s3-ca-file |
无 | 通过 SSL_CERT_FILE 传给 Litestream,用于私有 CA |
--s3-skip-verify |
false | 关闭 TLS 校验(自签名端点) |
--sync-interval |
1s |
WAL 上传间隔 |
--snapshot-interval |
24h |
全量快照间隔 |
--retention |
24h |
快照保留时间 |
--no-start |
false | 只写配置,不启动 Litestream |
--force |
false | 覆盖已有配置 |
enable 会先确认数据库处于 WAL 模式,解析可用的 0.5.x Litestream(或用
--litestream-binary PATH 指定),以 0600 写入 litestream.yml,清理本地
Litestream 旧状态,启动进程并检查它没有立即退出。若立即退出,会打印日志最后一行
并报错。
在复制运行中,用完全相同的非敏感配置再次执行 enable 是幂等的。配置不同则默认
拒绝;--force 会停止旧进程、重写配置、reset 本地状态并重启。
凭据存放位置
| 路径 | 内容 | 权限 |
|---|---|---|
<base>/pg.yaml |
名称、路径、binary/version、endpoint、bucket、前缀、region、URI style、CA file、skip-verify —— 不含 key | 0644 |
<base>/sqlite/<name>/<name>.db |
数据库文件 | 应用默认 |
<base>/sqlite/<name>/litestream.yml |
完整 S3 配置,含 access/secret key | 0600 |
<base>/sqlite/<name>/litestream.log |
进程日志 | 0644 |
<base>/sqlite/<name>/litestream.pid |
运行中的 pid | 0644 |
<base>/sqlite/<name>/.<db>-litestream/ |
Litestream 本地 LTX 状态 | 应用默认 |
备份或复制 litestream.yml 时要像对待其他凭据文件一样谨慎。
replication disable 会删除它,因此下次 enable 需要重新提供凭据。
复制日常操作
status 结合进程状态与 litestream status -json(本地事务 id 和 WAL 大小),
并显示日志中最近的错误行。即使状态查询失败,仍会展示进程状态并正常退出。
检查远端备份文件
pg mc 是内置 MinIO 客户端的包装器,mc 自己的 flags 必须写在 -- 之后。使用
「本地 MinIO」示例中创建的 alias,可以列出 Litestream 上传到 --s3-path 下的对象:
有数据时类似这样:
0000/ 存放逐事务的 WAL 帧;编号更大的层级(0001/、0009/ 等)是合并、
压缩后的文件。刚 enable 完时前缀为空只说明还没有写入被同步——插入一行数据,
等待至少一个 --sync-interval(默认 1s)后再列一次。replication status
显示的本地事务 id 应与这些文件名中的最大 txid 一致。
disable 保留数据库、本地 LTX 状态和日志,只删除 pg.yaml 中的 replication
块并删除 litestream.yml。远端备份不受影响。
开机自启
Litestream 是独立的宿主机进程,不会在主机重启后自行恢复。把数据库注册到 pgcli 的开机服务:
--name 必填(SQLite 没有默认数据库名)。复制必须已经启用且 litestream.yml
仍然存在——开关命令会检查这两点,否则报错并提示重新 enable。开机时
pg start --autostart 会在对象存储 addon 之后重启 Litestream,因此同机的
MinIO/silo/rustfs/garage/SeaweedFS 已经先开始监听。开机路径只做启动:绝不
重写 litestream.yml,也不修改数据库文件。
pg sqlite replication disable 会自动清除该标志。systemd/launchd 服务细节见
开机自启。
恢复
Litestream 可以恢复最新状态或指定时间点。支持两种模式:
--timestamp 与 --txid 互斥;都不传则恢复最新状态。--timestamp 表示绝对
时间点,请写成带 Z 后缀的 UTC 时间(如 2026-01-01T12:00:00Z),避免恢复
时间点受宿主机时区影响。--target 不能与 --restart 同用;目标已存在时必须加
--force,此时旧的 target 及其 -wal/-shm 会先被移入
<target>.pre-restore-<UTC>/。
时间点恢复演练
在做出想撤销的变更之前,先记录一个事务边界:
LTX 对象名为 <min-txid>-<max-txid>.ltx。按 --txid 恢复会包含该事务及之前
的状态。记录变更前的 txid(或一个安全早于变更的 RFC3339 UTC 时间戳),然后恢复
一份副本并验证:
时间戳方式流程相同:
不确定时先加 --dry-run:它只打印将要使用的事务范围,不写任何文件。恢复到
--target 不会停止复制,因此可以在检查副本的同时继续处理线上流量。建议再恢复
一次最新状态(不传 --timestamp/--txid),确认较新的数据存在;验证无误后,
再用原地恢复把副本正式替换上线。
原地恢复
不传 --target 时会替换配置中的数据库:
流程刻意保守:
- 停止 Litestream(会执行最终 sync);其他打开该数据库的应用必须提前自行停止。
- 当前数据库及 WAL 附属文件移入
<db>.pre-restore-<UTC>/(不加--force时 会先确认)。 - 备份先恢复到数据库同目录下的临时目录,因此恢复失败也不会影响已经保留的旧副本。
- 检查恢复出的数据库,必要时切回 WAL,然后通过同一文件系统内的原子 rename 移动到最终位置。
- 重置 Litestream 本地状态;若 reset 失败,则删除旧的本地状态目录。
- 默认保持复制停止;
--restart会重新启动。
恢复后把复制重启到相同的 --s3-path,可能把恢复出的时间线与远端旧数据混在一起。
优先用新的 --s3-path 重新 enable;使用 --restart 前请确认理解旧时间线。
如果数据库文件完全丢失,原地恢复不需要 --force;但每个数据库的
litestream.yml 必须仍然存在。
WAL 与应用侧设置
CLI 只把 PRAGMA journal_mode=WAL 持久化到数据库文件中。以下设置按连接在应用侧
调整:
注意事项:
- WAL 模式要求本地文件系统,不要把数据库放在 NFS 或 SMB 上。
- 同一时刻仍然只有一个写入者。长写事务会阻塞其他写入者;长读事务会阻止 WAL checkpoint,可能让 WAL 无限增长。
- Litestream 复制的是 WAL,不是应用一致快照。涉及多条语句的不变量请使用事务。
故障排查
litestream exited during startup —— 执行
pg sqlite replication logs <name> -n 50。常见原因是 bucket 不存在、凭据错误,
或宿主机无法访问 endpoint。
no Litestream 0.5.x binary found —— 执行 pg sqlite install。PATH 上的
0.3.x 会被报告但不会使用。
连接 MinIO/RustFS 的 TLS 错误 —— 纯 HTTP 端点要写完整
http://host:port;自签名 HTTPS 用 --s3-ca-file 或 --s3-skip-verify。
database ... uses journal_mode=delete —— 数据库不是由 pg sqlite create
创建的,或被其他工具改过模式。在没有其他连接时执行
PRAGMA journal_mode=WAL;,再重试 enable。
litestream.yml not found —— start、status 和 restore 都依赖
enable 写出的配置;请重新执行 enable(凭据不在 pg.yaml 中)。
限制
- 仅支持 Linux;macOS 的 host-binary 路径未实现。
- Litestream 是异步备份,不是同步复制或高可用。默认 RPO 约 1 秒,
stop/disable时的最终 sync 最长可能耗时 30 秒。 - 每个数据库一个 Litestream 进程、一个 WAL 写入者。
--clean-data和原地恢复具有破坏性;默认会确认,保留的.pre-restore-*目录需要手动清理。
4 - TimescaleDB
TimescaleDB 是面向时序负载的 PostgreSQL 扩展。它把普通表变成 hypertable: 数据按时间自动切分成 chunk,应用仍然使用普通 SQL、索引和事务。
pgcli 安装的是 Pigsty 的 TSL 构建(TimescaleDB 2.31.0)。Hypertable、
time_bucket、chunk 管理、列存/压缩、连续聚合和保留策略均可使用。 TSL 允许免费内部使用,但禁止对外提供时序数据库服务。
使用场景
TimescaleDB 适合以时间为主要访问维度、以追加写入为主的数据:
- IoT 与设备遥测 — 按设备采集读数、查询最新值和按时间窗口聚合。
- 基础设施与应用监控 — CPU、内存、延迟、请求量等指标,用于看板和告警。
- 工业与能源计量 — 高频传感器数据流,保留周期长。
- 金融行情数据 — tick、报价和成交;用
time_bucket和first/last生成 OHLC 类聚合。 - 产品与事件分析 — 用户事件、会话和 API 调用,按最近时间窗口统计。
- 智能家居与楼宇自动化 — 传感器历史数据和状态变化。
典型形态是 (entity_id, time) 的追加型数据:查询过滤最近一段时间,过期数据按
整个 chunk 删除。
不适合没有时间维度的 OLTP、键值/文档存储和大对象存储。TSL 构建包含压缩、 连续聚合和保留策略,但 TSL 不允许对外提供时序数据库服务。
安装
pgcli 会使用 Pigsty 的 TSL 包构建扩展镜像,把 timescaledb 放到
shared_preload_libraries 的首位,重启 PostgreSQL,并在实例的默认数据库执行
CREATE EXTENSION IF NOT EXISTS timescaledb。
同一实例的其他数据库需要单独创建扩展:
查看版本和许可证:
Pigsty 包返回 timescale。
快速开始
1. 创建实例
pg create 写入配置并打印管理员密码;pg start 启动 PostgreSQL 和备份容器。
默认数据库为 tsdb_db。
2. 安装扩展
pgcli 构建扩展镜像,预加载 timescaledb,重启 PostgreSQL,并在 tsdb_db 中
执行 CREATE EXTENSION IF NOT EXISTS timescaledb。
3. 创建 hypertable
连接实例:
创建普通表并转换为 hypertable:
也可以一步创建;此时使用默认 chunk 间隔,之后仍可调整:
chunk 间隔按查询和保留窗口选择;后续调整只影响新 chunk。
4. 写入数据
5. 日常运维
追加数据、查看 chunk 和后台任务、启用自动维护:
常用运维命令:
压缩、保留策略、连续聚合和 chunk 的完整操作见下文对应章节。
InfluxDB 兼容 API:influx2tsdb 插件
如果现有工具只支持 InfluxDB 协议,可用 influx2tsdb 插件 在 TimescaleDB 之上运行一个受管的 无状态代理,提供 InfluxDB 1.x HTTP API(Line Protocol 写入与 InfluxQL 查询):
- 每个 InfluxDB 数据库对应一个 PostgreSQL schema,每个 measurement
对应一个 hypertable,
time为分区列; CREATE / ALTER / DROP RETENTION POLICY会被翻译成 TimescaleDB 自带的add_retention_policy/add_compression_policy任务——不需要pg_cron, 且代理停机期间 chunk 清理照常进行;- 代理本身不存数据,备份、PITR、复制与 HA 仍然完全走 PostgreSQL。
代价是吞吐:请求需要翻译成 SQL,纯时序写入/查询性能低于原生 InfluxDB。代理 没有认证和 TLS,需要 nginx 前置或做网络隔离。参数、数据 模型、保留语义和测试脚本见 插件文档。
常用查询
每条序列的首末值:
每条序列的最新一行:
按指定时区做天级分桶:
补齐时间桶序列中的空档:
time_bucket_gapfill 需要明确的起点和终点:可以作为第二、三个参数传入,或在
WHERE 中给出时间范围。
压缩与列存
较早的 chunk 可以转入列存(columnstore),压缩数据并加速分析查询:
查看压缩状态和空间占用:
保留策略
超出保留窗口的 chunk 会自动删除:
这些任务由 TimescaleDB 自带的后台调度器执行,不需要 pg_cron。
influx2tsdb 插件 使用同一机制:其
CREATE / ALTER / DROP RETENTION POLICY 会被自动翻译成这些任务,代理停机期间
清理照常进行。
连续聚合
预先聚合最近的数据,并由后台任务持续刷新:
像普通表一样查询聚合结果:
策略只自动刷新 start_offset 到 end_offset 之间的近期窗口;回填更早的数据后
需要手动刷新。
管理 chunk
Chunk 间隔
创建时指定间隔,或之后调整(只影响新 chunk):
timestamptz 列的 chunk 边界按 UTC 对齐。间隔要保证单个 chunk 能较轻松地放进
内存;间隔过小会产生大量 chunk,并增加查询规划开销。
查看 chunk
older_than / newer_than 按 chunk 的时间范围过滤,而不是按创建时间。
查看 chunk 大小
拆分 chunk
在 chunk 的时间范围内选一个时间点进行拆分;chunk 名称来自
show_chunks('sensor'):
split_chunk 是 procedure,必须用 CALL,不能用 SELECT。
删除 chunk
drop_chunks 直接删除文件,不产生 dead tuple。不要用大范围 DELETE 清理
历史数据。自动过期优先使用保留策略;只有需要自定义调度时才安装 pg_cron:
手动转换列存
配置好压缩参数后(见上文「压缩与列存」),可以不等策略而手动转换单个 chunk:
这两个 procedure 接收 chunk 名称,而不是 hypertable 名称。
元数据视图
jobs 中可以看到上面创建的列存、保留策略和连续聚合任务,以及内部的 job 历史
任务。
功能可用性
| 功能 | 状态 |
|---|---|
Hypertable、time_bucket、first/last、show_chunks、drop_chunks、hypertable_size、元数据视图 |
✅ Apache |
用 add_dimension / by_hash 做空间分区 |
✅ Apache |
列存与压缩(ALTER TABLE ... SET (timescaledb.compress)、CALL add_columnstore_policy) |
✅ TSL |
连续聚合(CREATE MATERIALIZED VIEW ... WITH (timescaledb.continuous)) |
✅ TSL |
保留策略(add_retention_policy) |
✅ TSL |
time_bucket_gapfill |
✅ TSL |
标记为 ✅ Apache 的功能遵循 Apache-2.0;标记为 ✅ TSL 的功能来自 Timescale License,允许免费内部使用,但禁止对外提供时序数据库服务。
备份与恢复
逻辑备份和 pg export/pg import 正常工作。物理恢复(pg backup/pg restore
使用的 pgBackRest)还需要 TimescaleDB 的恢复钩子:
pg restore 不会自动调用这两个函数,恢复完成后需要手动执行
timescaledb_post_restore()。正式依赖前请先演练一次。
权限
CREATE EXTENSION 需要超级用户;pg exec 以实例管理员身份连接。表的所有者
可以对自己的表执行 create_hypertable 并管理 chunk。应用角色只需要普通的表
权限。
故障排查
extension "timescaledb" is not available。 实例镜像里没有扩展包,或目标
数据库没有创建扩展。执行 pg extension install timescaledb -i <instance>,并在
该数据库中创建扩展。
timescaledb is not preloaded。 扩展已在 shared_preload_libraries 中,但
PostgreSQL 没有重启。pg extension install 会自动处理;可用
SHOW shared_preload_libraries; 检查并重启实例。
函数报许可证错误。 实例镜像中还是 Apache 构建(旧版 pgcli 或手动安装的
PGDG 包)。执行 pg extension install timescaledb -i <instance> --auto-restart
切换到 Pigsty TSL 包。
cannot create a unique index without the column "time"。 hypertable 上的
唯一索引和主键必须包含分区列:
chunk 数量过多。 用 set_chunk_time_interval 调大间隔;已有 chunk 不会
重写。
旧数据没有被清理。 用 add_retention_policy 添加保留策略,或手动执行
drop_chunks;可在 timescaledb_information.jobs 中检查策略任务。
函数参考
| 函数 | 用途 | 状态 |
|---|---|---|
create_hypertable('t', by_range('time')) |
把表转换为 hypertable | ✅ |
by_range('time', INTERVAL '1 day') |
指定时间分区和 chunk 间隔 | ✅ |
set_chunk_time_interval('t', INTERVAL ...) |
调整新 chunk 的间隔 | ✅ |
time_bucket('1 hour', time [, timezone]) |
时间分桶聚合 | ✅ |
first(value, time) / last(value, time) |
分组内首末值 | ✅ |
show_chunks('t' [, older_than/newer_than => ...]) / drop_chunks('t', ...) |
查看、过滤和删除 chunk | ✅ |
hypertable_size('t') / hypertable_detailed_size('t') |
hypertable 总大小及明细 | ✅ |
add_dimension('t', by_hash('col', N)) |
添加空间分区 | ✅ |
add_retention_policy(...) / remove_retention_policy(...) |
自动过期清理 | ✅ TSL |
CALL add_columnstore_policy(...) |
列存/压缩策略 | ✅ TSL |
CALL convert_to_columnstore(chunk) / CALL convert_to_rowstore(chunk) |
手动转换 chunk 存储格式 | ✅ TSL |
hypertable_columnstore_stats('t') / chunk_columnstore_stats('t') |
列存状态与空间统计 | ✅ TSL |
CREATE MATERIALIZED VIEW ... WITH (timescaledb.continuous) |
连续聚合 | ✅ TSL |
add_continuous_aggregate_policy(...) |
连续聚合的自动刷新策略 | ✅ TSL |
time_bucket_gapfill(...) |
补齐缺失的时间桶 | ✅ TSL |