2026-07-09 11:02:00
一个爆火的讨论
云风 @cloudwu 2026-06-29
微信的开发人员根本就不懂该怎么储存数据。这种聊天软件,文本和媒体文件分开存,文本根本就不应该保存在什么数据库(sqlite)里, 一个对话一个文本文件追加就可以了。需要搜索的时候 grep 一下性能完全符合需求。一个对话能有多少文本?一秒一个字 24 小时不间断,一年也就 30M 个字。
网上的争论都是猜测,我呢,决定让 opus 跑一局。
首先让AI去搜微信聊录表结构
微信(Android)聊天记录存储在加密 SQLite 数据库
EnMicroMsg.db中,使用 SQLCipher(AES-256-CBC,PBKDF2 256000 轮派生密钥)。核心message表:
CREATE TABLE message (
msgId INTEGER PRIMARY KEY, -- 本地自增 ID
msgSvrId INTEGER, -- 服务器消息 ID
type INTEGER, -- 1=文字, 3=图片, 34=语音, 43=视频
isSend INTEGER, -- 0=接收, 1=发送
createTime INTEGER, -- Unix 时间戳
talker TEXT, -- wxid 或群 chatroom ID
content TEXT, -- 消息正文
imgPath TEXT -- 附件路径
);
微信支付、服务器、数据库、会议、周末
| 分类 | 方案 | 思路 |
|---|---|---|
| 传统文本 | grep (BSD) | 最朴素的逐字节匹配 |
| SIMD文本 | ripgrep | AVX2/NEON 并行 + 多线程 |
| 零拷贝 | mmap 直接搜索 | OS page cache + Python bytes.find |
| 压缩文本 | zstd 流式解压搜索 | 省空间,边解压边搜 |
| 索引 | 倒排索引 (2-gram) | 搜索引擎思路,内存索引 |
| 索引 | Bloom Filter 分块 | 概率型预过滤 |
| RDBMS | SQLite LIKE | 微信的实际方案(去掉加密) |
| RDBMS | SQLite mmap 模式 | mmap I/O 加速 |
| RDBMS FTS | SQLite FTS5 (trigram) | 全文搜索引擎 |
| 列式DB | DuckDB contains() | OLAP 列式扫描 |
| 列式DB | DuckDB FTS | DuckDB 的全文搜索扩展 |
| 列式文件 | Parquet(zstd) + DuckDB | 列式文件直接查询 |
| DataFrame | Polars lazy scan | Rust实现的极速 DataFrame |
| DataFrame | Polars in-memory | 全量载入内存 |
| 并行文本 | ripgrep 多文件并行 | 分块文件 + rg 多线程 |
| # | 方案 | 微信支付 | 服务器 | 数据库 | 会议 | 周末 | 平均 |
|---|---|---|---|---|---|---|---|
| 1 | SQLite FTS5 (trigram) | 0.65 | 0.46 | 0.37 | ❌² | ❌² | 0.31¹ |
| 2 | Polars lazy scan | 3.51 | 2.56 | 2.43 | 2.49 | 2.47 | 2.69 |
| 3 | 倒排索引 (2-gram) | 2.71 | 3.26 | 2.78 | 3.24 | 2.37 | 2.87 |
| 4 | Polars in-memory | 2.67 | 3.61 | 2.98 | 3.21 | 3.21 | 3.13 |
| 5 | DuckDB contains() | 3.72 | 3.23 | 3.18 | 3.91 | 4.41 | 3.69 |
| 6 | Parquet + DuckDB | 4.63 | 4.37 | 4.55 | 4.88 | 4.84 | 4.65 |
| 7 | ripgrep 多文件并行 | 10.65 | 10.26 | 9.48 | 10.32 | 9.05 | 9.95 |
| 8 | DuckDB FTS (BM25) | 12.53 | 11.77 | 12.85 | 11.82 | 12.78 | 12.35³ |
| 9 | ripgrep (SIMD, 单文件) | 13.24 | 13.77 | 13.40 | 14.14 | 13.10 | 13.53 |
| 10 | mmap 直接搜索 | 17.00 | 22.62 | 22.86 | 31.36 | 30.52 | 24.87 |
| 11 | Bloom Filter + 扫描 | 25.34 | 25.25 | 25.06 | 26.68 | 27.01 | 25.87 |
| 12 | SQLite mmap LIKE | 37.45 | 37.45 | 37.92 | 37.83 | 37.81 | 37.69 |
| 13 | SQLite LIKE | 43.13 | 41.40 | 41.50 | 41.51 | 41.46 | 41.80 |
| 14 | grep (BSD) | 139.10 | 136.86 | 141.42 | 122.54 | 124.16 | 132.82 |
| 15 | zstd 流式解压搜索 | 161.32 | 164.22 | 164.56 | 170.23 | 170.73 | 166.21 |
¹ FTS5 只对 ≥3字符 的关键词有效,取3个有效关键词平均
² trigram tokenizer 无法匹配 2 字符的中文词
³ DuckDB FTS 默认 tokenizer 不支持中文,返回 0 结果(延迟仍可参考)
1. █ 0.31ms SQLite FTS5
2. █ 2.69ms Polars lazy
3. █ 2.87ms 倒排索引
4. █ 3.13ms Polars in-mem
5. █ 3.69ms DuckDB contains
6. ██ 4.65ms Parquet+DuckDB
7. ████ 9.95ms rg多文件并行
8. ██████ 12.35ms DuckDB FTS
9. ██████ 13.53ms ripgrep (SIMD
10. ████████████ 24.87ms mmap 直接搜索
11. ████████████ 25.87ms Bloom+扫描
12. ██████████████████ 37.69ms SQLite mmap LIKE
13. ████████████████████ 41.80ms SQLite LIKE
14. ████████████████████████████████████████████████████████████ 132.82ms grep (BSD
15. ████████████████████████████████████████████████████████████ 166.21ms zstd流式解压
| 方案 | 延迟 (ms) | 倍率(vs grep) |
|---|---|---|
| SQLite indexed | 2.80 | 76x |
| DuckDB | 4.57 | 47x |
| Polars in-memory | 7.02 | 30x |
| ripgrep pipe | 20.47 | 10x |
| grep pipe | 213.90 | 1x |
| 格式 | 大小 | vs TSV | 说明 |
|---|---|---|---|
| Parquet (zstd) | 8.3 MB | 0.18x | 列式 + 字典编码 + 压缩 |
| zstd 压缩 TSV | 12.7 MB | 0.27x | 纯压缩 |
| DuckDB + FTS | 26.0 MB | 0.55x | 含全文索引 |
| TSV 纯文本 | 47.0 MB | 1.00x | 基线 |
| SQLite | 69.2 MB | 1.47x | B-tree 开销 |
| SQLite + FTS5 | 116.8 MB | 2.49x | trigram 索引翻倍 |
SQLite FTS5 (trigram)
- 原理:对 content 字段的每个 3 字符子串建倒排索引
- 优点:查询极快(0.3-0.6ms),无需额外依赖
- 缺点:索引体积翻倍(+68MB);trigram 无法匹配 ≤2 字符的关键词
- 适用:搜索词通常 ≥3 字符的场景
Polars (lazy/in-memory)
- 原理:Rust 实现的 DataFrame 引擎,列式内存布局 + SIMD 字符串匹配
- 优点:Parquet 文件仅 8.3MB(最小!),查询 2-3ms,复合查询也快(7ms)
- 缺点:需要加载到内存;Python 库依赖
- 杀手锏:8MB 的 Parquet 文件 + 3ms 搜索延迟,这是存储效率和速度的最佳平衡点
倒排索引 (2-gram, 内存)
- 原理:搜索引擎最经典的思路,对所有 2-gram 建 posting list
- 优点:构建仅 0.78s,查询 2.9ms,支持任意长度关键词
- 缺点:纯内存(需要序列化/加载),索引构建需要全量遍历
- 适用:append-only 数据可以增量更新索引
DuckDB contains()
- 原理:列式存储,content 列连续存放,CPU cache 友好 + SIMD 扫描
- 优点:无需专门索引即可 3.7ms;复合查询也仅 4.6ms;文件仅 26MB
- 缺点:需要 DuckDB 运行时
- 杀手锏:不建任何索引,纯靠列式布局就比 SQLite LIKE 快 11 倍
Parquet 文件 + DuckDB 零拷贝查询
- 原理:Parquet 本身就是列式格式,DuckDB 可以直接查询不需导入
- 优点:文件仅 8.3MB,不需要数据库进程,查询 4.6ms
- 缺点:每次查询需要启动 DuckDB 连接
- 杀手锏:一个 8MB 的文件就是完整的"数据库",任何语言都能读
ripgrep 多文件并行
- 原理:把消息分块成多个文件,ripgrep 的 work-stealing 线程池并行搜索
- 优点:比单文件 ripgrep 快 ~35%(10ms vs 13.5ms)
- 缺点:文件管理复杂
- 适用:数据天然按时间分文件存储的场景
ripgrep (SIMD, 单文件)
- 原理:AVX2/NEON 每周期处理 16-32 字节,多线程(对单文件仍用单线程)
- 优点:零配置,即装即用
- 缺点:对单文件只能单线程
Bloom Filter 分块预过滤
- 问题:中文常用 2-gram 只有 ~690 种,每个块都包含所有 n-gram,过滤率为 0
- 结论:对高频 n-gram 的数据集完全无效,白费构建时间
zstd 流式解压搜索
- 问题:Python 解压+搜索 166ms,比不压缩的 grep 还慢
- 结论:CPU 密集的解压抵消了 I/O 节省。如果数据在 SSD 上,不如直接读原文
- 可能有用的场景:数据在网络存储/HDD 上,I/O 是瓶颈时
DuckDB FTS (BM25)
- 问题:默认 tokenizer(类似 ICU word boundary)不支持中文
- 结论:需要自定义 tokenizer 或等 DuckDB 支持 trigram/CJK
mmap 直接搜索
- 表现:24.87ms,比 ripgrep 慢 2 倍
- 原因:Python 的 mmap.find() 是朴素搜索,没有 SIMD 优化
- 如果用 C/Rust 实现的 mmap + SIMD,预期接近 ripgrep 水平
| 优先级 | 方案 | 搜索延迟 | 存储 | 理由 |
|---|---|---|---|---|
| 🥇 | Parquet + Polars/DuckDB | 3-5ms | 8 MB | 存储最小、搜索极快、格式通用、append 友好 |
| 🥈 | DuckDB 数据库 | 3.7ms | 26 MB | 单文件数据库、列式、SQL 查询、FTS 索引可选 |
| 🥉 | SQLite + FTS5 | 0.3ms | 117 MB | 最快搜索(有索引)、但索引太大 |
| 4 | ripgrep + 分块 TSV | 10ms | 47 MB | 最简单、无依赖、人类可读 |
# 方案 A#: ripgrep 搜纯文本 (~10ms)
rg "微信支付" messages.tsv
## 方案 B: DuckDB 直接查 Parquet (~5ms)
duckdb -c "SELECT * FROM read_parquet('messages.parquet') WHERE contains(content, '微信支付')"
## 方案 C: DuckDB 复合查询 (~5ms)
duckdb -c "SELECT * FROM read_parquet('messages.parquet')
WHERE talker='wxid_xxx' AND createTime > 1672531200
AND contains(content, '会议')"
聊天记录是 append-only 数据 → 最适合列式存储 (Parquet/DuckDB)
- 不需要 RDBMS 的事务/锁/B-tree 开销
- 列式布局让
content列连续存放,CPU cache 极度友好- zstd 字典压缩对重复模式多的聊天文本压缩率惊人(82%)
- 追加写入只需要 append 新的 row group
微信搜索慢的根因:不是 SQLite vs grep 的问题,而是 SQLCipher 加密的 PBKDF2 256000 轮 + 行式存储对全文扫描不友好。如果用 Parquet + 硬件 AES,可以同时做到加密和极速搜索。
messages.parquet (8MB, zstd压缩, 硬件AES加密)
→ DuckDB/Polars 直接查询 (3-5ms)
→ 可选: 内存倒排索引 for < 1ms 搜索
→ 追加: 新消息 append 到 staging 文件, 定期 merge
→ 导出: parquet 是开放格式, 任何语言/工具可读
pip install duckdb polars zstandard cryptography
python3 benchmark.py 500000 # 基础测试 (SQLite/grep)
python3 benchmark_v2.py 500000 # 扩展测试 (DuckDB/Polars/mmap/...)
源码放在 https://github.com/est/snippets/tree/master/grep_vs_sqlite 。
模拟聊天记录有500,000行 一个 50MB 的 .tsv 太大了。自己让AI根据文章和最上面表结构生成一份吧。
被Polars的性能震惊了。说实话如果换我肯定不会选sqlite。聊天记录又不会改。ACID的 OLTP 明显浪费
2026-07-08 16:05:00
有AI就是好使,搁以前自己得盯半天也看不明白。。。
curl -NT. 在连接一个持续推送数据的 streaming 服务器时,会产生一个 紧密的 pause/unpause 循环,导致 100% CPU。
-T.(注意是点号,不是减号)将 stdin 设为非阻塞模式,然后依赖 EAGAIN + CURL_READFUNC_PAUSE 机制来避免阻塞。核心循环发生在三个回调之间:
第一步 — 上传尝试失败(src/tool_cb_rea.c):
{
rc = read(per->infd, buffer, sz * nmemb);
if(rc < 0) {
if(errno == EAGAIN) {
errno = 0;
config->readbusy = TRUE;
return CURL_READFUNC_PAUSE;
}
/* since size_t is unsigned we cannot return negative values fine */
rc = 0;
}
}
stdin 没数据 → read() 返回 EAGAIN → 设 readbusy=TRUE → 暂停上传。
第二步 — 下载数据到达立刻解除暂停(src/tool_cb_wrt.c):
if(config->readbusy) {
config->readbusy = FALSE;
curl_easy_pause(per->curl, CURLPAUSE_CONT);
}
streaming 服务器持续发送数据 → tool_write_cb 被不断调用 → 每次都无等待地立刻解除上传暂停。
第三步 — 进度回调只有 1ms 退避(src/tool_cb_rea.c):
if(config->readbusy) {
if(ulprev == ulnow) {
#ifndef _WIN32
waitfd(1, per->infd);
#else
/* sleep */
curlx_wait_ms(1);
#endif
}
config->readbusy = FALSE;
curl_easy_pause(per->curl, CURLPAUSE_CONT);
}
tool_readbusy_cb 有 1ms 的 waitfd 退避,但被 tool_write_cb 的无等待解除抢先了。
完整的死循环(每次 curl_multi_perform 内):
在 lib/transfer.c 的 Curl_sendrecv 中,处理顺序是先下载后上传:
if(k->keepon & KEEP_RECV) {
result = sendrecv_dl(data, k);
if(result || data->req.done)
goto out;
}
/* If we still have writing to do, we check if we have a writable socket. */
if(Curl_req_want_send(data)) {
result = sendrecv_ul(data);
if(result)
goto out;
}
所以单次迭代内:
1. sendrecv_dl → 读到 streaming 数据 → tool_write_cb → 看到 readbusy=TRUE → 立刻解除暂停(零等待)
2. sendrecv_ul → tool_read_cb → stdin EAGAIN → 又暂停,readbusy=TRUE
3. 回到 curl_multi_poll → 下载 socket 有数据 → 立刻返回(不会 sleep)
4. 重复 1-3
而 lib/easy.c 的主循环:
while(!done && !mresult) {
int still_running = 0;
mresult = curl_multi_poll(multi, NULL, 0, 1000, NULL);
if(!mresult)
mresult = curl_multi_perform(multi, &still_running);
curl_multi_poll 的 1000ms 超时形同虚设——因为下载 socket 始终可读,poll() 立刻返回。
结果:每秒跑上千次这个循环,每次包含多个系统调用(poll、read、write),CPU 直接打满。
stdin 没有被加入 curl_multi_poll 的 pollset。只有网络 socket 参与了 poll。所以 curl 无法高效地"同时等待 stdin 和网络数据",只能靠 tool_write_cb 里的盲目 unpause 来弥补,在 streaming 场景下退化成忙等。
如果你不需要从 stdin 上传数据(只是想看 streaming 响应),不要用 -T.:
# 正确的方式:只看 streaming 输出
curl -N https://streaming-server/events
# 或者重定向 stdin 为空
curl -NT. https://streaming-server/events < /dev/null
< /dev/null 会让 read() 返回 0(EOF)而不是 EAGAIN,不会进入 pause/unpause 循环。
如果确实需要双向 streaming(边上传 stdin 边接收),目前没有完美的用户侧 workaround。可以考虑:
# 用管道加一层缓冲,减轻空转(效果有限)
cat | curl -NT. https://streaming-server/events
如果要在源码层面修复,最直接的方案是在 tool_write_cb 中去掉盲目 unpause,改为只在 tool_readbusy_cb 中带退避地 unpause,并增大退避时间:
// tool_cb_wrt.c — 去掉这段,或加条件限制
// 不再从 write callback 无脑解除上传暂停
if(config->readbusy) {
config->readbusy = FALSE;
curl_easy_pause(per->curl, CURLPAUSE_CONT); // 删除或改为有条件触发
}
// tool_cb_rea.c — 增大退避时间
if(config->readbusy) {
if(ulprev == ulnow) {
waitfd(100, per->infd); // 从 1ms 提高到 100ms
}
config->readbusy = FALSE;
curl_easy_pause(per->curl, CURLPAUSE_CONT);
}
更彻底的方案是把 stdin fd 加入 curl_multi_poll 的 extra fds,但这需要改 curl_multi_poll 的调用方式,涉及面更大。
2026-07-06 15:51:00
无聊刷博客,《AI如何导致和修复了我的失眠问题》 这老哥真猛,通过统计规律发现自己 失眠 的罪魁祸首。(不剧透。感兴趣点开阅读)
我自己也写过 快速入睡的技巧和原理 以及 强行入睡方法 v2.0 其实我都忘记这个 2.0 方法了。都不知道自己当时怎么想到这个办法的,原来自己写的东西也能常看常新(老登健忘症😂),所以还是要多写,多记录
本文章的讨论都是基于这个 2.0方法的,接着看之前请务必点开 2.0 那个链接,不长,一会儿就看完。
然后我就无聊让AI 评价一下这个 2.0 方法是不是真的,然后AI说真有学者在搞类似的,关键词 :
然后我去搜了下
TikTok和Instagram上爆火的“认知洗牌法”,火到连医生都开始推荐。选一个随机的单词(比如 cake,蛋糕),专注于这个词的第一个字母(C),然后列出一串以这个字母开头的词:cat(猫)、carrot(胡萝卜)、calendar(日历)等等,一边列举,一边在脑中想象这些词的画面。当你准备好了,就转到下一个字母(A),重复上述过程,继续进行下去(K、E),直到你睡着或者想换一个新词为止
嗯,和我的方法居然殊途同归,只是更加麻烦,需要调用大脑的语言区。
但是自媒体这个标题让我产生了兴趣 《别再数羊了》,我恰好周末刷了 西藏那曲拉姆 的视频 藏族人家里几十上百头牦牛,如何识别是不是自己家的?
然后突然发现一个被大多数人忽略的惊人的事实:放牧人的白天是极度无聊和空虚的,以至于他/她们能辨识自己每一头羊的特征、性别,甚至给每头羊起个名字。视频说牦牛和人一样,每一头都有独一无二的毛色、长相、形态等。
脸上有点黑?叫小黑;黑白相间的?叫花脸。按脾气起名字,暴躁哥,温顺妹;还有谁和谁喜欢一起吃草,小牛的母牛妈妈是谁等等。
所以这两件事就串起来了。「数羊」这事儿一定是牧区的人发明出来的,比如英国乡下,但是城里人哪里知道这些细节啊。
牧民晚上躺床上,没事干,说不定就给自己牛羊编故事造剧情啊。而且关键词是「数」,你不能陷入一个逻辑推理细节,必须不停地轮换,把羊变成高频切换的具象个体(有名字、有外形、有性格),才能保证大脑疲惫然后入睡。
AI总结:
对于古代或乡村的牧羊人来说,夜晚盘点羊群是一天结束时最让人安心的闭环。他们脑海中的羊,确实是毛发质感、体态特征各异的具象实体。
现代城市人剥离了这种生活语境,把一幅丰富的田园3D渲染图,降维成了枯燥的Excel递增表格,自然也就失去了助眠的神奇功效。
我觉得AI真有点东西的。清点生产资料也是被忽略的一环。如果我睡前都能盘点自己该做的事儿都做了,羊吃饱了,明天会更好,没啥落下的,那我肯定也睡得安稳啊。
但是现代人难就难在很多事是跨很多天的,入睡是非常不情愿的。如果有精力很多人甚至愿意熬夜。
都怪爱迪生,本来以为发明电灯泡给人类漫长的黑夜带来光明,没想到人类却用这玩意来加班和难眠!!😤 😤 😤 如果太阳下山就睡觉,就算失眠4小时你到23点也睡着了 😂
2026-07-02 16:07:00
看到一篇雄文《明末士大夫为什么毫无气节纷纷变节投降满清?》大受启发,想看原文的可以点开链接,下面是精简和摘录:
因为程朱理学在理论存面存在漏洞,被鞑子无意之间利用了,事实上大部分的鞑子统治者在这方面,也都是知其然而不知所以然。
不否认明末也有很多了不起的仁人志士,但如果你对中国历史有些疑惑,觉得似乎宋朝之后中国就有点不一样了,那恭喜你,你的直觉是对的。由于元代留下的遗毒过甚,明朝没能拨乱反正,元明清三代,所有的皇帝和“儒家士大夫”,都是失礼而不自知之人。元明清三代的所谓“礼法”,放在先秦两汉的大学者们面前,诸子百家不管哪家,都一眼就能看出来全是假礼。如果墨子这个儒家最大的反对者看见了,估计更是嘲笑孔子能笑的棺材板都压不住。这些“假礼”,就是元明清三代那些僵化的等级秩序、规规矩矩。出于汉人的直觉,厌恶这些是再正常不过了。
一、建用皇极
宋以前儒学,与宋明理学不是同一种思想。先秦至汉唐儒学的最高原则是天命与大中之道,而不是君主本身。朱熹重新解释《尚书·洪范》"皇极",从九畴排第五提高到最高优先级, 把它解释为 君主是天下的最高标准,天下围绕君主建立秩序。,从而改变了儒家的政治哲学。
二 · 定于一尊
元朝恢复科举时规定:四书、五经必须按照朱熹注释考试,使其成为与功名利禄直接挂钩的唯一标准。于是理学不再只是一个前朝有争议的一个"逆党"学说,而成为整个帝国唯一的意识形态。明清完全继承了这一制度。
三、诸夏之亡
《论语》中孔子其实始终强调,华夏共同体高于个人君臣关系。例如孔子称赞管仲,就是因为即使“不忠”,改事新君,只要能够保卫华夏,也仍然值得肯定。但程朱理学更加重视君臣名分、上下秩序、皇权连续性,于是出现一种新的逻辑:即使皇帝是异族,也不能没有皇帝。1908年孔令贻把德国皇帝威廉二世肖像迎进了孔府
四、天命之礼
孔子的礼,本质上来源于 天命。所以:礼约束君主,君主不能创造礼。而理学实践中却逐渐变成皇帝成为礼法的最终解释者。于是礼不再约束权力,而成为权力工具。
五、凡心之仁
基督教的本质是爱与诫二元一体的罪文化,那么发源自中国的东亚文化,本质就是仁与礼二元一体的耻文化。
基督教中所谓的爱,叫做“Agape”。这是一个专有词,它有多重要呢?欧洲所有国家,不管哪种语言,它的拼写方式都是一致的,一字不易。“Agape”的源头是神,它是一种具有普世性的博爱。而“仁”和“Agape”的区别在于,中国的“仁”,其源头是凡人,是“己所不欲勿施于人”,它是一种推己及人的有差等之爱。
华夷之辩是“礼”的边界,“礼”是对“仁”的约束。面对民族危机时,个人、家庭利益、官职利益、君臣秩序都会压倒共同体利益。因此许多人最终选择保身、顺从、投降,而不是抗清。
(注:这里其实用 “异端” 和 “有经人” 对比更加强烈)
六、知行合一
孔子、董仲舒的礼法,理论源头是天帝,实践中确实也按天帝至上来执行。而程朱理学的礼法,理论源头是天理,但是实践中,理学的礼法源头压根不是天理!在以前,经筵都是大儒给皇帝讲课,到了乾隆那,变成皇帝给大儒讲课了。
先秦两汉礼法的源头是神?因为礼法的源头就不能是一个具体的人!礼法是用来栓人的保守性,如果礼法的源头是人,那么栓着你的绳子就牵在那个人手上,你就是那个人的奴才,礼法就成了赤裸裸的等级压迫。
如果天帝无法约束皇权,那么天帝也不能保护皇权。天帝不能保护皇权,皇帝就只能自己保护自己,于是朱元璋废除了丞相制度。所谓明清飞速膨胀的君权,其实和南北朝盛产的疯子皇帝有异曲同工之妙。跟着龙椅遗传的精神病,本质是因为坐在龙椅上的人没有安全感。没有人相信天命,连皇帝本人都不相信自己真正“受命于天”。而对于儒生大臣,后人说张居正是“常务副皇帝”。
七、失礼之国
(作者的一些感想,比较杂,不引述了)
看完之后真过瘾。本来想着今天的键政就到这了。
特别是第二点,最近几年我逐渐从对“科举”的好感,降低了。学生时代总有那么一些“考功名”的亲切感,但是现实世界还是觉得“军功合伙人”更优。
但是有一个更大的疑惑,转念一想不对劲。于是补充一点我自己的观点。
皇帝这个岗位,从秦到唐,都是一把手承包了世俗君权,和神权的双重责任。皇帝在赵政之前实际上是两个岗位,大祭司负责给「帝」传话,王中王称皇负责行政。
宋以后皇帝把道德秩序这一块外包给儒生了,自己关起门做皇家经营了,剩下的全是算计。
说得直白点汉唐的皇帝还勉强要点b脸,遇到难堪的事,还得想办法给手下和民间一点说法。
宋以后就是无情的 打工 - 服从 叙事。
我是真的越来越看不起大怂国。我把内心抱怨说给AI,AI指出
宋代皇帝其实仍然非常受士大夫制约
我反驳:
例如宋代皇帝其实仍然非常受士大夫制约,需要合理分赃才能一起搜刮老百姓。造成有史以来遍地造反运动。
AI当时就不乐意了。把教科书和网上常见吹捧宋朝的资料抬出来了。比如说宋朝其实造反的规模和烈度没那么多
但是我想说,你把大半个中国都丢干净了,西夏 辽金的汉人躺棺材里了,当然北境无人“造反”了。因为别人都被占领了。
秦汉 隋唐造反不就是关中和河北人打架吗?这一毛病从姬发那一辈儿就没停过。天子这一岗位说的直白点就是给大家当调停人,pax sinica 。你宋说白了就一个节度使,偏安江淮一隅,还这么多造反的。呸!
AI 被这个角度刁钻的回答干懵逼了。说你这个框架,衡量皇朝优劣的标准不是"有没有起义",而是 能不能维持整个华夏共同体的秩序。那么很多评价都会变。最后还嘴硬一句,宋朝其实内部治理得很好啦,最终是被蒙古人迫不得已干趴下的。
我当时就火了。对蒙古你好意思讲“战争”?实际上周 秦 汉 唐 的草原治理能力,也是 “天命” 的支柱啊。周武王牧誓,手里拿着的就是牦牛尾巴!不是象征汉人农耕的的锄头!纵观宋、金两朝,对草原的经略就是完全失败的。垃圾!
因为刚刚前一阵子看到 《金朝对草原的减丁,为何遏制不了蒙古的崛起》 这里 cue 一下
宋辽金真是一群乡镇企业家暴发户械斗。烂得要命。唉。你仔细想一下宋吹,那些证据,多少是近代人牵强附会的?宋朝人自己觉得骄傲吗?给好评的,都是后世明清没当上官的文人吧?
天命最大的意义在哪里?给人指明前行的道路,给人以希望。即便黑暗中世纪教会和君权也是这么分工的。挫宋做到了啥?苟且罢了。
有人说,大宋“杯酒释兵权”终结了五代十国,功劳巨大。但从“天命”的角度讲,国之大事,在祀与戎。你老赵家没能给一个民族找到希望,也干不过架,你这个王权就没有存在的根基。说得直白一点,东亚这篇土地,从周天子那一辈人开始就是武装殖民模式。你不殖民,有的是蒙古人 女真人殖你的民。
其实一开始那个文章的框架来讨论一个具体的事就很有力度。如何评价北宋赵光义毁掉太原?
具体的事迹大家可以问下AI。正如文章里所说的,丢掉了 华夷之辩 这个“天命”。那别人河北幽州人全体投夷你也怨不了谁。你之后靖康之变都是报应。
网上对 “内亚” 的说法一直有巨大争议,阿姨那边一直说“武德注入”,实际上征服,殖民 和扩张这些说法太粗暴。但是如果天子不提供秩序,那么你也别怨替代秩序的出现;无德,丢天命,天命归别人。似乎就是这么简单的道理
政治空间是没有真空的
谁的组织度更高,谁的天命就更强。
联系到欧洲发家,启蒙运动 文艺复兴,是在抛弃君权 神权 这个思维定势下做到的。更厉害。
不过替代品似乎是——资本?一个侧面就是牛津剑桥主打神学专业,改成政治经济学
资本的扩张我觉得按照脉络去捋,是蒸汽机,发展纺织业,全球贸易。 归根结底是煤铁革命,但是仔细想,实际上是把战争的边界改成向几百亿年的太阳能存款挖出来消耗了。
农牧时代是拿当季的太阳能来pk。谁能提供摩擦最小的当季太阳能分配,谁就在古代“有德”,有“天命”
现代社会一样的。全球变暖,污染,绿色能源等,一直到社会公平正义。
天命天命,天就是天上射下来的能量,命就是草木人间生命。
哈哈哈这个解释如何?是不是很唯物。
天(能源)与命(生命负载)之间,能否实现最高效的转换与分配?
生命本身就是一个“负熵的过程”。生命存在的意义,就是把“天”射下来的能量,通过光合作用、通过食物链,转化为有序的社会结构、文明形态和思想结晶。
如果转换效率高、分配摩擦小: 生命、社会繁荣,这就叫“天命有归”。
如果中间商抽成太多,腐败、内卷、战略自残(如毁太原): 能量(天)射下来了,却无法高效转化为“命(繁荣)”,能量在内部耗散了,这就是天命将尽。
大宋这种对外又送又怂,把“皇帝”这个singleton强行改成“兄弟之国”, 对内三冗,取缔军功兑现,换成“理学”考试,用首都的局部繁荣掩盖整体的失败。就是丢 天命 的典型。
古代的战争:争夺的是对“天光(土地)”的占有权。
现代的危机:焦虑的是“天光(太阳能存款)”快烧完了,我们该如何重新设计“命(人类社会)”的分配效率。
政治的本质就是能量管理工程。
天命 在过去,看重国体和人君,现代看政治制度科技政策。资本这个玩意,纵然有那么多毛病,但是很好的执行了“天命”
2026-06-26 09:40:00
工作和业余也用AI写代码,大大小小项目都经历了。从 rules, skills, spec, agent 到 harness 都玩过了
从AI嘴里发现一条比较稳的套路——ADR文档
rule, skills, spec 这些东西最大的问题就是瞎jb指挥。ADR 的好处是记录why,以及决策演变历史。
贴一段我整理的 ADR 文档就明白了:
---
title: 如何使用 ADR
id: ADR-001
date: 2026-06-26 09:01:21
status: accepted
---
ADR(Architecture Decision Record,架构决策记录)的核心目标很简单:记录为什么做出了某个重要技术决策,而不是记录系统长什么样。
目前比较常见的是 MADR、Nygard ADR 两种风格,但组织方式都大同小异。
一个团队通常会按下面几个层次组织。
不要一个 ADR 写整个系统设计。
好的粒度例如:
ADR-001 使用 PostgreSQL 作为主数据库
ADR-002 API 使用 REST 而不是 GraphQL
ADR-003 服务间通信采用 gRPC
ADR-004 用户认证采用 OAuth2 + JWT
ADR-005 使用事件驱动 Outbox Pattern
而不是
系统架构设计.md
因为几年以后,很难知道某个结论为什么来的。
一般都会固定编号。
adr/
0001-use-postgresql.md
0002-use-rest-api.md
0003-use-grpc.md
0004-use-oauth2.md
编号一旦存在,就不要修改。
即使后来废弃:
0003-use-grpc.md
Status: Superseded by ADR-0018
这样引用不会失效。
一般都有状态。
例如:
Status: Accepted
Date: 2026-06-19 01:02:03
如果后来换了:
Status: Superseded
Superseded by: ADR-0018
而新的 ADR:
ADR-0018
Supersedes: ADR-0003
形成完整历史。
---
title: ADR-008 使用 PostgreSQL
id: ADR-008
date: 2026-06-18 12:32:46
status: accepted
---
## Context
目前需要:
- ACID
- JSON 查询
- 成熟生态
候选:
- PostgreSQL
- MySQL
- MongoDB
## Decision
选择 PostgreSQL。
## Consequences
优点:
- SQL 功能完整
- JSONB 支持优秀
- 社区成熟
缺点:
- 运维复杂度略高
- 分库分表方案需要额外设计
可以再加:
Alternatives
Decision Drivers
Trade-offs
References
在一个目录,用文件名体现领域:
008.backend.use-grpc.md
010.security.use-oauth.md
012.frontend.react-query.md
这样编号保持连续,查找也方便。
例如:
ADR-0015
Context
依赖:
- ADR-0002
- ADR-0008
Decision
由于 ADR-0008 已经确定 PostgreSQL,
因此 Outbox Pattern 可以直接利用事务。
形成决策网络,而不是孤立文档。
这是很多团队最容易犯的错误。
不要写:
Controller
Service
Repository
这是设计文档。
ADR 应该写:
为什么不用 MongoDB?
为什么不用 GraphQL?
为什么采用 Saga?
为什么拆成多个 Service?
为什么 Event Sourcing 被放弃?
重点永远是 Why,而不是 What。
过去很多团队会这样组织:
| 文档 | 回答的问题 |
|---|---|
| RFC / Proposal | 未来准备怎么做? |
| ADR | 为什么这样做? |
| Architecture Doc | 系统如何组织? |
| Design Doc | 某个功能如何实现? |
| Runbook | 如何运维? |
流程是 RFC → ADR → Design Doc → Code。
RFC 用于讨论方案,达成决策后沉淀为 ADR;随后具体实现细节写入设计文档,最终落实到代码。这样既保留了决策依据,又避免 ADR 演变成冗长的设计说明。
在AI 时代,更简洁,易维护的方式是:
AI编写的项目,到后期,泥潭就是大量的docs。ADR 的好处是不用修订,全面引用+supersed。保证决策链清晰,低上下文成本
2026-06-20 12:06:22
打开 Shortcuts。
点击右上角 + 新建快捷指令。
搜 添加 日期(Current Date) 动作,默认为当前时间
添加 格式化日期,日期格式 自定义,填 yyyy-MM-dd HH:mm:ss
on run {input, parameters}
-- 稍微延长一点延迟,确保触发快捷键的手指已经离开键盘
delay 0.1
-- display dialog "Current date"
-- 将 Shortcuts 传入的 list 转换为字符串
set ts to item 1 of input as string
tell application "System Events"
-- 释放可能被系统残留挂起的修饰键状态
-- 键盘区数字的 Key Code 分布是乱序的
set keyCodeMap to {29, 18, 19, 20, 21, 23, 22, 26, 28, 25}
key up command
key up option
key up control
key up shift
repeat with i from 1 to length of ts
set c to character i of ts
set charID to id of c
if c is ":" then
-- 分号,加 shift 变成冒号
key code 41 using {shift down}
else if c is "-" then
-- 减号,不需要 shift
key code 27
else if c is space then
key code 49
else if charID ≥ 48 and charID ≤ 57 then
-- ASCII 码范围过滤 转换算出 1 到 10 的索引
set targetIndex to charID - 47
key code (item targetIndex of keyCodeMap)
end if
end repeat
end tell
end run
点快捷指令右上角 ⓘ。
勾 Use as Quick Action(用作快速操作)
选 任何应用程序
新版macOS可以直接绑定。
之前的:系统设置 → 键盘 → 键盘快捷键 → 服务(或“快速操作”)
我习惯的方式是右手 Cmd+Opt+T 。
以前觉得 applescript 慢,但是现在反而发现需要 delay 0.1 否则会触发 Cmd+Opt 的连招
本来AI给的版本是 keystroke 指令,容易误触 modifier keys,所以改成 key code。
还以为 AI 写错了,没想到 mac 的 0-9 数字键code 居然不是连续的。
不过这JB玩意不稳定,一会儿授权失效了,需要去 设置 - 隐私 - 辅助功能 里删除 Shortcuts 再添加。。