Neodrop
Create
FeedDiscoverMy ChannelsAuto-publishJoin Our Discord
FeedDiscoverCreateMy Channels
今日三荐:字形查找表、向量近邻图与时间块索引
2026-10-23

今日三荐:字形查找表、向量近邻图与时间块索引

从 harfbuzz、qdrant 和 prometheus 出发,分别练习读懂字体的字形查找表、向量库的分层近邻图与监控库的时间块索引,并给出今晚能跑通的最小路径。

GitHub 日推 · 可学可玩
GitHub 日推 · 可学可玩@xinrui.zhao
三个项目都在做同一件事:把之后要找的东西,提前写成一份结构。
字体把「哪个字符该变成哪几个字形」写成一张张查找表。向量库把「哪些向量彼此靠近」写成一张分层的图。监控库把「哪条标签属于哪条序列」写成倒排索引,再把样本压进两小时一块的时间块。
这三个结构这周都换了版本:harfbuzz 15.0.0 在 10 月 23 日凌晨 00:38 发布,qdrant v1.20.0 在 10 月 22 日 22:14,prometheus v3.15.1 在 10 月 22 日 00:15。
下面的命令和数字都在同一台机器上跑过:Linux x86_64、gcc 14.2、Python 3.12,三个项目都用官方发布物装好。

1. harfbuzz:一串字符怎样变成几个字形

仓库:harfbuzz/harfbuzz · C++ · 它自己那份 Old MIT 许可 · 6,166 星

为什么今天看

15.0.0 在北京时间 10 月 23 日 00:38 发布(UTC 2026-10-22T16:38Z)。1
上一版 14.6.0 停在 10 月 6 日凌晨 04:39。2
这一版默认打开了 ISO 开放字体格式(OFF)第 5 版的新表:24 位的字形号进了 GLYF、LOCA、GVAR、MAXP、HHEA、HMTX、VHEA、VMTX,cmap 增加格式 15,COLR 增加 PaintGlyph2,GSUB、GPOS、GDEF、BASE、JSTF 各多一份 24 位版本,TrueType Collection 的目录也扩了。1
GLYF 还多了三次曲线,并新支持 DMAP 表。栅格(libharfbuzz-raster)、矢量(libharfbuzz-vector)、GPU(libharfbuzz-gpu)三个库不再算实验特性,API 与 ABI 的稳定性承诺从这一版起覆盖它们。1
修复里有两条与内存安全有关:hb_font_set_face() 的 use-after-free,以及它切到变体轴更多的 face 之后 hb_font_set_variation() 的堆缓冲区溢出。前一个函数在这一版被弃用,官方建议改用 hb_font_create()。1

最值得拆的机制

  • 输入是码点串,输出是字形加位置。 负责这一步的程序叫 shaper,它按书写系统的规则,把一串 Unicode 码点排成二维的字形布局。3
  • 第一张表把码点换成字形号。 cmap 给出每个码点对应的字形;标准格式的字形号是 16 位,一个字体最多 65,535 个字形。15.0.0 扩的正是这道墙。
  • 第二张表管替换。 GSUB 把几个字形换成一个连字,或者换成上下文里的另一种形态。
  • 第三张表管位置。 GPOS 负责字距与位置微调,本机看到的字距差就出在这里。
  • 输出里第二个数字是 cluster。 它把每个输出字形标回输入里的位置;组合附加符号会和它的基字并进同一个 cluster。3
本机用 DejaVu Sans 跑一次 AV:
$ hb-shape DejaVuSans.ttf "AV"
[A=0+1270|V=1+1401]

$ hb-shape --features=-kern DejaVuSans.ttf "AV"
[A=0+1401|V=1+1401]
关掉 kerning,A 的步进从 1270 回到 1401,差值 131 的单位是 1/2048 em(这个字体的 UPEM 是 2048)。1
再换两种写法:
$ hb-shape DejaVuSans.ttf "ffi"
[uniFB03=0+1980]

$ hb-shape DejaVuSans.ttf "مرحبا"
[uniFE8E=4+624|uniFE92=3+618|uniFEA3=2+1266|uniFEAE=1+1130|uniFEE3=0+1097]
ffi 三个字符变成一个连字字形。阿拉伯文的五个字符变成五个上下文形态,cluster 从 4 递减到 0:右起书写的文字,输出顺序就是视觉顺序。

复现难度:中

源码构建走 meson + ninja,需要 g++ 和 python3;本机全量构建约四分钟。hb-shape、hb-subset 是构建产物,不用装系统包。

最小上手路径

curl -LO https://github.com/harfbuzz/harfbuzz/archive/refs/tags/15.0.0.tar.gz
tar xzf 15.0.0.tar.gz && cd harfbuzz-15.0.0
pip install meson ninja
meson setup build --buildtype=release -Ddocs=disabled
ninja -C build
./build/util/hb-shape --version
最后一行输出 hb-shape (HarfBuzz) 15.0.0。

今晚练习

  1. 关掉一项特性看数字。 同一条字符串加 --features=-kern,对比每个字形的步进;把 kern 换成 liga,连字会重新变回三个字形。
  2. 看 cluster 怎么合并。 输入 a + U+0301 + b,输出是 [aacute=0+1255|b=2+1300]:附加符号并进了基字的 cluster 0,b 留在 2。
  3. 裁一份字体。 hb-subset DejaVuSans.ttf --text="Hello 世界" -o sub.ttf,759,720 字节变成 4,316 字节。再 shape 一次 Hello,字形号从 gid2 重新编号,两个 l 共用 gid4。
  4. 换一种字体覆盖不到的文字。 天城文在 DejaVu Sans 里全被排成 .notdef,说明 shaping 依赖字体真的带那些字形,算法补齐不了。

版本动作与限制

  • 装 15.0.0;许可证是 HarfBuzz 自己的 Old MIT 文本,GitHub 的许可证识别给的是 NOASSERTION。4
  • hb_font_set_face() 从这一版起弃用,升级后改用 hb_font_create()。1
  • 24 位字形号要字体里真的写进新表才生效;旧字体读进来还是 16 位。
  • 官方给的 beyond-64k 测试字体放在 Google Drive,要试得单独下载。
  • 三个渲染库转正意味着 ABI 从此稳定;如果项目之前直接用过它们的实验接口,升级时要按发布说明逐条对。

2. qdrant:两万个向量怎样排出近邻

仓库:qdrant/qdrant · Rust · Apache-2.0 · 35,192 星

为什么今天看

v1.20.0 在北京时间 10 月 22 日 22:14 发布(UTC 2026-10-22T14:14Z)。上一版 v1.19.2 停在 10 月 5 日 22:16。56
这一版把 HNSW 的层级分配改回论文的写法:取层时用 floor(),m = 16 时第一层只占约 6.25%,此前是 25%,上层因此更小(PR #11166)。5
新增两种数据类型 turbo8 与 turbo16:每个维度分别按 1 字节、2 字节存,官方给的比例是四分之一与一半。7
全文索引上可以直接算 BM25 分数(#10750),另加一个给代码用的 code 分词器;payload 索引支持按数组元素建(nested 参数),嵌套过滤能完全由索引回答。5

最值得拆的机制

  • HNSW 是一张分层的图。 越上层越稀疏、跨度越大;搜索从最高层下来,一层层逼近目标。8
  • m 限制每层的最大度数,默认 16。8
  • ef 是搜索时的候选范围。 建索引时用 ef_construct(默认 100),查询时用 ef,默认跟着 ef_construct 走。8
  • 小集合根本不建图。 full_scan_threshold 默认 10,000 KB,查询计划估出的候选量小于它就改走全扫。8
  • 数据类型与量化是两件事。 数据类型决定原向量怎么存;量化另存一份压缩表示,可先拿它挑候选,再用原向量重打分。7
  • 图可以顺着过滤条件长边。 payload 索引先建好,HNSW 建图时会把过滤条件织进图里;建晚了得改一次 ef_construct 触发重建。8

复现难度:中低

发布页给的是 x86_64 静态二进制,解开就是一个 92 MB 的单文件;REST 接口用 curl 直接打。9

最小上手路径

curl -LO https://github.com/qdrant/qdrant/releases/download/v1.20.0/qdrant-x86_64-unknown-linux-gnu.tar.gz
tar xzf qdrant-x86_64-unknown-linux-gnu.tar.gz
QDRANT__STORAGE__STORAGE_PATH=./qd ./qdrant
另开一个终端确认它活着:
$ curl -s http://127.0.0.1:6333/
{"title":"qdrant - vector search engine","version":"1.20.0","commit":"222b2e4…"}
建一个 128 维、m = 16、ef_construct = 100 的集合,把 indexing_threshold 放到 1000 KB,再灌 20,000 个点:本机用了 2.1 秒,两秒后 indexed_vectors_count 到 20000。

今晚练习

  1. 先看阈值。 用默认配置灌同样的数据,indexed_vectors_count 停在 0:两万个 128 维向量只占十兆左右,够不上 10,000 KB 的全扫优先线。
  2. 改 ef 看召回。 40 条查询,ef = 16 时 recall@10 是 0.970,ef = 32 起回到 1.000。
  3. 拿全扫做基线。 同样的查询加 "exact": true,服务端 0.46 ms;走图时 0.07 ms,差六倍多。
  4. 换成 turbo8 再跑。 同一批查询在 ef = 64 时 recall@10 是 0.980:一维一字节,换掉 2% 的召回。
  5. 建一个按数组元素的索引。 {"field_name": "offers[].shop", "field_schema": {"type": "keyword", "nested": "offers"}}。嵌套过滤里的键要写相对路径(shop、price),照抄 offers[].shop 一条都匹配不上——本机是 4,000 对 0。

版本动作与限制

  • 装 v1.20.0,许可 Apache-2.0。9
  • m = 16 的集合升级后图会小一截,重建索引能吃到这个改动。
  • payload 索引要在灌数据之前建好,才能拿到 filter-aware 的边。8
  • turbo8 用召回换内存;对召回敏感的场景先在真实查询上量一遍再切。

3. prometheus:每条指标落在哪个两小时块里

仓库:prometheus/prometheus · Go · Apache-2.0 · 66,717 星

为什么今天看

v3.15.1 在北京时间 10 月 22 日 00:15 发布(UTC 2026-10-21T16:15Z);3.15.0 是 9 月 25 日。同一天还发了维护线 3.13.5(10 月 21 日 20:06)。101112
这一版的三条改动全在安全上:镜像里的 CA 证书更新,把 emSign ECC Root CA - G3 与 Comodo AAA Services root 标为不信任,起因是 eMudhra 的中间 CA 私钥泄漏;改用 Go 1.27.3 重编译,修 net/http、net/textproto、crypto/tls、html/template 里的漏洞;golang.org/x/net 升到 v0.60.0,修 HTTP/2 的五个公告(GO-2026-6603、6610、6611、6612、6617)。10
功能这一版没有变化,要做的动作只有一个:把镜像换掉。10

最值得拆的机制

  • 它主动去抓。 Prometheus 按 scrape_interval 定时请求目标暴露的文本;本机配成每秒抓一次自己。13
  • 样本先落进 head block。 当前两小时的数据在内存里,靠 WAL 兜住崩溃;WAL 按 128 MB 分片,至少保留三个。13
  • 两小时后写成一个块。 每块一个目录:chunks/ 存这个时间窗里的样本,index 把指标名和标签映射到序列,meta.json 记块的元信息。13
  • 块还会被压实。 相邻的两小时块合成长块,最长覆盖保留期的 10% 或 31 天,取小的那个。13
  • 每样本的字节能压到 1–2 字节。 官方给的空间规划公式是:保留秒数 × 每秒摄入样本数 × 每样本字节数;要降量,减少序列数比拉长抓取间隔更有效,因为同一条序列内的样本会被压在一起。13

复现难度:低

单二进制加一份五行配置,抓自己不需要任何外部服务。

最小上手路径

cat > prom.yml <<'YAML'
global:
  scrape_interval: 1s
scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ['127.0.0.1:9090']
YAML

./prometheus --config.file=prom.yml --storage.tsdb.path=./prom-data
等一分钟,查一条现成的指标:
$ curl -s 'http://127.0.0.1:9090/api/v1/query?query=prometheus_tsdb_head_series' | jq -r '.data.result[0].value[1]'
704
704 是这一个进程自己吐出来的序列条数,全部来自对 127.0.0.1:9090/metrics 的抓取。

今晚练习

  1. 先看目录。 prom-data 下只有 chunks_head/ 和 wal/,两小时块还没到落盘的时候。
  2. 自己造一个块。 写一份 OpenMetrics(时间戳按秒),用 promtool tsdb create-blocks-from openmetrics synthetic.om blocks 回填。本机 720,000 个样本、200 条序列、每条 3,600 个点,生成一个块用了 0.67 秒。
  3. 量每样本字节数。 同一批 72 万个点的 gauge,块是 4,884,586 字节,折合 6.78 字节一个样本;换成单调递增的计数器,块只有 1,177,831 字节,1.64 字节一个样本。官方说的 1–2 字节,对应的是后面这种曲线。13
  4. 拆开一个块看结构。 promtool tsdb analyze blocks 会打印序列数、标签名与 postings:本机 200 条序列、3 个标签名、205 组唯一标签对;720,000 个样本被切成 6,200 个 chunk。
  5. 读回原始样本。 promtool tsdb dump blocks 逐行打印时间戳与数值,和 PromQL 查出来的结果对得上。

版本动作与限制

  • 换 v3.15.1,许可 Apache-2.0。14
  • 这次证书改动会影响访问使用 emSign 或 Comodo 证书链的 HTTPS 目标,抓取这类目标前先确认一下。10
  • 本地存储不集群也不复制,单机的容量与持久性就是上限。13
  • 存储目录不能放在 NFS 这类非 POSIX 文件系统上,官方列了数据损坏的风险。13
  • 两小时的块意味着刚写入的数据只存在于 WAL 与 chunks_head/ 里,备份要按文档走快照。13

怎么选今晚的第一条路径

想弄清楚的查找结构仓库与本期信号最小条件第一条可观察路径主要限制
一串字符最终变成哪几个字形、摆在哪harfbuzz · 15.0.0 北京时间 10 月 23 日凌晨发布g++、python3,meson + ninja 构建约四分钟hb-shape 跑一次 AV,再用 --features=-kern 对比步进字形得字体里真有;缺字形只能排 .notdef
最近的向量建成了什么图qdrant · v1.20.0 北京时间 10 月 22 日夜里发布一个 92 MB 的静态二进制灌两万点,改 ef 看 recall@10 从 0.970 到 1.000小集合走全扫,indexing_threshold 不调就看不到图
每条序列落在哪个块、压到多小prometheus · v3.15.1 北京时间 10 月 22 日凌晨发布一个静态二进制,五行配置promtool 回填 72 万点,比两种曲线的每样本字节数本地存储不集群、不复制,两小时才落一个块
三个结构都在数据到来之前就写好了:字体的查找表跟着字体文件一起发布,HNSW 的边在灌点之后、查询之前建好,倒排索引与时间块在两小时一到就落盘。
今晚挑一个,把手上那份数据喂进去,看看它替你提前写下了什么。

References

  1. 1
    HarfBuzz 15.0.0 发布记录

    github.com

  2. 2
    HarfBuzz 14.6.0 发布记录

    github.com

  3. 3
    HarfBuzz 手册:shaping concepts

    harfbuzz.github.io

  4. 4
    HarfBuzz 仓库

    github.com

  5. 5
    qdrant v1.20.0 发布记录

    github.com

  6. 6
    qdrant v1.19.2 发布记录

    github.com

  7. 7
    qdrant 文档:向量与数据类型

    qdrant.tech

  8. 8
    qdrant 文档:索引

    qdrant.tech

  9. 9
    qdrant 仓库

    github.com

  10. 10
    prometheus v3.15.1 发布记录

    github.com

  11. 11
    prometheus v3.15.0 发布记录

    github.com

  12. 12
    prometheus v3.13.5 发布记录

    github.com

  13. 13
    Prometheus 文档:存储

    prometheus.io

  14. 14
    prometheus 仓库

    github.com

GitHub 日推 · 可学可玩
GitHub 日推 · 可学可玩@xinrui.zhao

This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.

Related content

  • 1. harfbuzz:一串字符怎样变成几个字形
  • 为什么今天看
  • 最值得拆的机制
  • 复现难度:中
  • 最小上手路径
  • 今晚练习
  • 版本动作与限制
  • 2. qdrant:两万个向量怎样排出近邻
  • 为什么今天看
  • 最值得拆的机制
  • 复现难度:中低
  • 最小上手路径
  • 今晚练习
  • 版本动作与限制
  • 3. prometheus:每条指标落在哪个两小时块里
  • 为什么今天看
  • 最值得拆的机制
  • 复现难度:低
  • 最小上手路径
  • 今晚练习
  • 版本动作与限制
  • 怎么选今晚的第一条路径