返回资讯中心
外部精选
软件工程
#开发者实时流

PostHog 发布 hoglake:面向 PB 级数据的开放表格式

来源标题:PostHog hoglake: Petabyte-scale open table format

PostHog 推出开放表格式 hoglake,支持 PB 级数据规模,元数据以行形式存入 Postgres,数据以 parquet 存放在对象存储中。其目录采用 REST 服务模式,客户端无需直接访问元数据库,提交成本不随目录规模增长。该格式支持快照、模式演进、行级血缘等能力。

Hacker News 最新tosh2026/11/2 · 今天26 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

PB 级开放表格式。快照、schema 演进、数据文件和统计信息都以行形式存于 Postgres;数据以 parquet 存于对象存储。catalog 是一个服务——客户端只通过 REST 契约通信,永远看不到元数据数据库。

目录:是什么 | 快速开始 | 组件 | 一次提交如何工作 | 与 DuckLake 和 Iceberg 的对比 | 性能 | 状态 | 开发 | 文档

Catalog 即服务。所有 catalog SQL 都在服务端,藏在 REST API 之后。一份实现、一个版本、一次部署——而不是让每个客户端各自嵌入的库。

Postgres 作为唯一后端。带 ON DELETE CASCADE 的外键、advisory lock、部分索引和范围删除是承重的设计材料,而不是为了可移植性而牺牲掉的东西。

提交以写入集为界。一次提交就是一个快照,其成本不随 catalog 的规模增长。

页脚随附注册。写入方上传 parquet 并发送页脚统计信息;服务端在提交路径上从不打开数据文件。延迟统计模式则改为异步补全这些信息。

服务端分配行号血统。连续的 row-id 区间,永不复用,受 table_uuid 化身契约约束。compaction 在有序重写过程中保留 row id。

行级删除以删除向量表达。每个数据文件一个活跃向量,增长单调;过期的写入会被以带类型的 409 拒绝,而不是笼统的冲突。

维护由服务端负责。compaction、快照过期、物理清理和按需的不变量扫描作为后台任务在 advisory lock 下运行——而不是客户端过程彼此竞争。

带消费者 offset 的 changefeed。按范围划分的文件与删除向量计划。offset 是 catalog 状态,过期会尊重它们,而不是让读取方陷入困境。

按快照 id 或时间戳进行时间旅行,低于保留下限时返回 410,而不是悄悄给出错误结果。

内置可观测性。Prometheus /metrics、结构化审计日志、用于实例身份与构建信息的 GET /v1/info,以及一个 Web 控制台。

多种接入方式。原生 Trino connector、Python 客户端、DuckDB 扩展,以及设计中的 Iceberg REST facade。

快速开始

整套栈都在容器里——Postgres、MinIO、服务端和控制台:

just up

控制台在 http://localhost:5173 启动,API 在 http://localhost:8080。just down 停止它;just down-volumes 还会丢弃数据。

然后通过 Python 客户端写入一些行:

pip install pyhoglake # 或:uv add pyhoglake

import pyarrow as pa from pyhoglake import HoglakeClient, S3Config client = HoglakeClient( "http://localhost:8080", s3=S3Config( access_key="hoglake", secret_key="hoglake123", endpoint_override="http://localhost:9000", # MinIO;AWS 可省略 region="us-east-1", ), ) catalog = client.create_catalog("demo", "s3://my-bucket/demo/") ns = catalog.create_namespace("analytics") table = ns.create_table( "events", pa.schema([pa.field("id", pa.int64(), nullable=False), pa.field("name", pa.string())]), ) result = table.append(pa.table({"id": [1, 2], "name": ["a", None]})) print(result.snapshot_id)

客户端自己写入 parquet——field id 嵌入 parquet schema——提取页脚统计信息,并在一次提交中注册该文件。读取是仅元数据的规划:table.files() 返回路径和统计信息,数据由你自己获取。schema 演进、时间旅行、changefeed 和维护调用见 pyhoglake/。

路径 是什么 server/ 控制平面:Kotlin、Ktor、JDBI、Flyway server/trino/ PostHog/trino 中原生 Trino connector 的集成测试装置 pyhoglake/ Python 客户端;负责 parquet 写入路径 webui/ 管理控制台:React、Vite、TypeScript。表页面的 partitions 标签页读取维护采样器最后发布的 generation,因此其数字是它所命名快照处的采样,而非实时读取 hedgerow/ 复制守护进程;只追加,单一目标 millrace/ Kafka → hoglake 摄取,带持久化的 SlateDB 暂存;至少一次 duckdb-client/ DuckDB 扩展——通过 REST 契约进行读取、写入、DML、DDL 和时间旅行 bench/ 下方数字背后的基准测试装置

一次提交如何工作

writer hoglake Postgres │ │ │ ├─ 写 parquet ──▶ 对象存储│ │ │ │ │ ├─ POST /commit ────────────────▶│ │ │ 路径、行数、 ├─ 校验 ──────────────▶│ │ 页脚统计、DV ├─ 获取 per-catalog 锁 ─▶│ │ ├─ 冲突扫描 ─────────▶│ │ ├─ 分配快照 ─────▶│ │◀─ 200 {snapshot_id} ───────────┤ 写入行 │ │ 或带类型的 409 │ │

追加永远不会与追加冲突。携带删除的提交会声明它所读取的快照,如果期间发生了冲突性变更,服务端会拒绝它——这是一个指明原因的类型化拒绝,而不是需要模式匹配的消息。序列化点是按 catalog 持有的 advisory lock,仅持有数毫秒。

三种拒绝共享同一种恢复方式:409 ddl_since_read_snapshot(在请求的 read_snapshot 之后,被触及的表上发生了 DDL)、409 table_recreated(expected_table_uuid 所指的化身不再是活跃的那个),以及读取低于 catalog 过期下限的 read_snapshot 时的 410。这些均不可重试——重新读取表并准备新请求,这正是 409 响应体所说的 retry: re-prepare。

列词汇表是封闭的:27 个 wire 名称,由 ColType 枚举、hog_column.col_type CHECK 和 OpenAPI ColumnDef.type 枚举以相同方式强制,ScalarTypeParityTest 从它们的实际文件出发相互断言。边界值按映射后的 Iceberg 类型的 Iceberg 单值序列化存储,绝不按 hoglake 类型存储——正是这一点使 manifest 生成成为机械复制。细节见 docs/iceberg-federation.md §2;这是那张卡片,按 ColType 顺序排列,相邻名称在共享映射时同行。

variant、container、permanent-refusal、promotion 和 transform 规则只声明一次,位于 docs/iceberg-federation.md §2.5-§2.8 及其“Native VARIANT publication”小节。

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →