PostHog 如何配置 S3 查询缓存降低 Redis 存储成本?
【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog
PostHog 的查询结果缓存(query cache)会把每条结果 zstd 压缩后存入 Redis。对于压缩后仍然很大的结果(临时查询、API 大结果集),它们会长期占据 Redis 内存。PostHog 提供了一条 S3 分流路径:压缩后达到QUERY_CACHE_S3_MIN_COMPRESSED_BYTES(默认 128KB)的缓存条目会上传为 S3 对象,Redis 中只保留一条小的指针记录。这样大结果从 Redis 挪走,而仪表板小结果仍走亚毫秒级的 Redis 读取。本文基于 S3 query cache setup 与 posthog/query_cache/storage.py、posthog/settings/object_storage.py,说明完成这条路径所需的配置、灰度开关、生命周期规则和验证方式。
大结果如何被分流到 S3
写入流程(store_result→schedule_upload_for_pointer,见 storage.py):
- 结果先序列化为 JSON,超过 512 字节(
COMPRESSION_FLOOR_BYTES)时做一次 zstd 压缩。同一个压缩帧同时用作三样东西:判断是否走 S3 的路由依据、Redis 内联值、S3 上传体。 - 若压缩帧长度 ≥
QUERY_CACHE_S3_MIN_COMPRESSED_BYTES,且该 team 所属 organization 的灰度开关允许,条目在后台线程上传到 S3,成功后把 Redis 中的内联值替换为指针记录(指针携带 bucket、key 和last_refresh)。 - 任何失败(S3 报错、上传线程池饱和)都会回落到“条目继续留在 Redis 内联”的状态,查询本身不会因此失败。
只有 zstd 帧才可能走 S3:低于 512 字节的结果存原始字节,天然不进入 S3 路径;关掉压缩开关USE_REDIS_COMPRESSION(posthog/settings/data_stores.py,默认True)同样会让条目离开 S3 路由。
需要配置的三个环境变量
这三个设置是进程启动时读取的普通环境变量(定义见 posthog/settings/object_storage.py):
| 环境变量 | 默认值 | 含义 |
|---|---|---|
QUERY_CACHE_S3_BUCKET | 回退到OBJECT_STORAGE_BUCKET | 缓存 blob 所在 bucket,Cloud 上为专用 bucketposthog-query-cache-<region>-<env> |
OBJECT_STORAGE_S3_QUERY_CACHE_FOLDER | query_cache | bucket 内的 key 前缀 |
QUERY_CACHE_S3_MIN_COMPRESSED_BYTES | 131072(128KB) | 路由到 S3 的最小 zstd 压缩后大小 |
对象键形如{folder}/{team_id}/{cache_key}/{upload_id},每次上传生成一个新的upload_id,避免同一查询并发重算时互相覆盖;对象带归属标签cache_type=query_data和team_id=<id>(标签只用于归属,不代表过期)。
# 按需覆盖(文档说明 Cloud 运行默认值,生产覆盖需先通过部署 chart 注入,与其他应用设置一致) QUERY_CACHE_S3_BUCKET=<你的缓存专用或共享 bucket> OBJECT_STORAGE_S3_QUERY_CACHE_FOLDER=query_cache QUERY_CACHE_S3_MIN_COMPRESSED_BYTES=131072QUERY_CACHE_S3_MIN_COMPRESSED_BYTES的阈值针对的是压缩后字节——那才是条目在 Redis 里的真实成本。按 object_storage.py 中的注释,查询结果大约压缩 3-15 倍,128KB 阈值对应约 0.4-2MB 的序列化 JSON:仪表板图块几乎到不了这个量级,留在 Redis;字节大头的临时查询和 API 结果则被移出集群。
灰度开关 query-cache-s3-writes
分流由 organization 组上的多变量特性开关query-cache-s3-writes控制,它只限制写入路径,读取路径从不评估开关、只跟随已存储的记录形态。三种状态:
- 未开启(off):所有结果照旧内联存 Redis。
shadow:缓存条目同时写 S3 和 Redis,用于验证写入路径;没有任何读取走 S3 副本,Redis 仍是权威。on:Redis 存指针、条目存 S3,读取时从 S3 取 blob。
评估是本地求值(only_evaluate_locally=True),只有shadow和on两个已知变体会激活 S3 写入;布尔开关、未知变体或求值失败都会 fail-closed 回到内联 Redis 路径。开关按 organization 维度开启,因此可以按团队灰度推进。
必须配套:S3 生命周期规则
S3 生命周期规则是这条路径的垃圾回收兜底,删掉的是主动删除(Celery 延迟删除)漏掉的对象:TTL 自然过期的指针、shadow 模式的上传、回滚的 team、失败的删除,以及无法触达 Celery broker 的进程留下的写入。规则只有一条:
对象在CACHED_RESULTS_TTL_DAYS(7)天后过期。
按 bucket 类型分两种做法(见 S3 query cache setup):
- 专用 bucket(Cloud 的
posthog-query-cache-<region>-<env>只服务这个缓存):规则可以作用于整个 bucket。Cloud 侧由 posthog-cloud-infra 的terraform/modules/s3/main.tf(enable_query_cache_lifecycle)管理。 - 共享 bucket:必须把规则限定在
OBJECT_STORAGE_S3_QUERY_CACHE_FOLDER前缀(默认query_cache/)上。因为QUERY_CACHE_S3_BUCKET未设置时回退到共享的OBJECT_STORAGE_BUCKET,全 bucket 的过期规则会连 exports、media uploads、error-tracking source maps 一起删掉。
还有一个顺序要求:如果未来把CACHED_RESULTS_TTL_DAYS调大,必须先调大 bucket 生命周期规则。否则 S3 会在 Redis 指针仍然存活时就删掉 blob,大缓存条目会提前悄悄失效。调小是安全的——blob 只会比指针多活几天再被 GC。
过期与删除语义
- 过期由 Redis 指针的 TTL 决定,与 S3 无关。附着于 insight/dashboard 的条目存活
CACHED_RESULTS_TTL(CACHED_RESULTS_TTL_DAYS= 7 天,定义在 posthog/settings/schedules.py);insight/dashboard 之外的程序化写入走更短的CACHED_RESULTS_PROGRAMMATIC_TTL(默认 24 小时)。指针过期或被驱逐后条目即消失,无论 S3 对象是否还在。 - 一旦没有指针引用,blob 会被尽快删除:替换或驱逐指针条目会入队一个延迟
BLOB_DELETE_DELAY_SECONDS(60 秒)的尽力而为 Celery 删除,留出窗口让刚读到指针的读方完成 S3 读取;而一次指针交换失败的上传会立刻删掉自己的 blob,因为它从未进入 Redis。
验证配置是否生效
文档给出的可观察点如下:
- Redis 侧形态。条目被分流后,Redis 中该缓存键的值以
S3_POINTER_MAGIC(PHQCS3\x00)开头的指针记录替代了原来的 zstd 帧;未分流的条目仍是 zstd 帧或原始字节。posthog/query_cache/test/test_storage.py 中的test_on_mode_large_result_round_trips_via_pointer演示了这条判定:写入大结果后 Redis 持有指针、S3 有 1 个对象,且lookup()读回的as_full_response()与原始响应一致。 - Prometheus 指标(见 storage.py 中的指标定义):
posthog_query_cache_s3_write_total{mode, outcome}:写入模式与结果;posthog_query_cache_s3_write_bytes_total{mode}:上传的压缩字节数,是 bucket 增长的直接信号;posthog_query_cache_s3_read_total{outcome}/posthog_query_cache_s3_read_duration_seconds:指针解析结果(hit、missing、error、blob_corrupt、pointer_corrupt、storage_disabled)与耗时。 读侧 outcome 为hit说明“指针 → S3 blob”链路已经实际服务流量。
- shadow 模式的行为。
test_shadow_mode_uploads_but_redis_stays_authoritative表明 shadow 模式下 S3 有对象、但 Redis 不出现指针——这正是 shadow 的预期形态。
限制与边界
- 故障退化是内联,不是失败:
write_blob失败返回 None,条目保留在 Redis 内联;指针解析失败(corrupt/missing)读作 miss 并重算,重算的写入会覆盖坏条目。 - 上传并发受限:S3 上传走 2 线程池、最多 8 个排队 blob,槽位耗尽时直接跳过上传(记
saturated),条目留在内联。 - 读取路径不看开关:回滚
on→ 已写入的指针条目仍会走 S3 读取直到其自然过期;反过来,若关掉对象存储(OBJECT_STORAGE_ENABLED=False),读侧会把指针读记为storage_disabled并当作 miss。 - 版本混合:旧版本 pod 读到不认识的格式会记一次读错误、删除条目并重算,滚动部署期间每条条目最多多一次重算。
完成专用(或正确限定前缀的共享)bucket、7 天生命周期规则与query-cache-s3-writes开关后,先让目标组织跑shadow观察写入指标,再切on,用 Redis 指针形态与posthog_query_cache_s3_read_total{outcome="hit"}确认大结果已实际离开 Redis。
【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考