社交文件系统 AT 协议技术分析
一、概述
1. 文章信息
A. 标题
A Social Filesystem(社交文件系统)
B. 作者
Dan Abramov
C. 来源
overreacted.io
D. 发布时间
2026 年 1 月 18 日
2. 核心主题
本文探讨了如何将传统文件系统的理念应用到社交网络中,介绍了 AT 协议(Authenticated Transfer Protocol)如何实现数据与应用的分离,让用户数据真正归属于用户而非平台。
二、背景分析
1. 传统文件系统的优势
A. 数据归属
文件不属于创建它的应用程序,而是属于用户。用户可以检查文件、发送给朋友、用其他应用打开。
B. 文件格式的作用
文件格式就像一种语言,应用可以"说"多种格式,同一种格式可以被多个应用理解。应用和格式是多对多的关系。
C. 应用无关性
我们的记忆、思想、设计应该比创建它们的软件更持久。应用无关的存储(文件系统)强制实施这种分离。
2. 社交应用的现状问题
A. 数据被困
在 Instagram、Reddit、Tumblr、GitHub、TikTok 等社交应用中,用户数据被困在应用内部,无法自由迁移。
B. 应用主导
传统模式是应用主导,用户的数据存储在应用的服务器上,由应用控制。
C. 缺乏互操作性
不同社交平台之间数据无法互通,用户需要重复创建内容。
三、核心理念:社交文件系统
1. 万物文件夹概念
假设有一个文件夹包含你的在线角色所有发布过的内容:
你的帖子、点赞、音乐播放记录、食谱等,可以称为"万物文件夹"。
在这个世界中:
- 在 Tumblr 发帖会在你的文件夹中创建一个"Tumblr 帖子"文件
- 在 Instagram 关注会在你的文件夹中放入一个"Instagram 关注"文件
- 在 Hacker News 投票会在你的文件夹中添加一个"HN 投票"文件
2. 文件作为真实来源
文件是真实来源,应用只是反映文件夹中的内容。对文件夹的任何写入都会同步到相关应用。删除"Instagram 关注"文件与应用中取消关注效果相同。
3. 应用反应式设计
在这个范式下,应用对文件是反应式的。每个应用的数据库主要成为派生数据——应用特定的缓存物化视图,反映所有人的文件夹。
四、AT 协议架构
1. 系统组成
graph TB
User[用户] --> owns[拥有]
owns --> Repo[Repository 仓库]
Repo --> contains[包含]
contains --> Collection[Collection 集合]
Collection --> containsRecord[包含]
containsRecord --> Record[Record 记录]
Repo --> hostedBy[托管于]
hostedBy --> PDS[Personal Data Server]
PDS --> syncWith[同步]
syncWith --> Relay[Relay 中继]
App1[App 1] --> subscribes[订阅]
subscribes --> Relay
App2[App 2] --> subscribes
App3[App 3] --> subscribes2. 核心概念
A. Record(记录)
记录是社交文件系统的基本单位,是一个 JSON 文件。例如一条推文记录:
{
"text": "no",
"createdAt": "2008-09-15T17:25:00.000Z"
}关键原则:
- 只包含用户创建的数据
- 不包含派生数据(如点赞数、转发数)
- 可以独立验证和解析
B. Collection(集合)
集合是包含特定词表类型记录的文件夹。命名规则采用反向域名表示法:
- com.twitter.post:Twitter 帖子
- com.instagram.follow:Instagram 关注
- fm.last.scrobble:Last.fm 播放记录
- io.letterboxd.review:Letterboxd 影评
graph LR
subgraph Collections
A[com.twitter.post]
B[com.instagram.follow]
C[fm.last.scrobble]
D[org.schema.recipe]
end
App1[App 1] --> defines[定义]
defines --> A
App2[App 2] --> defines
defines --> B
App3[App 3] --> defines
defines --> CC. Repository(仓库)
仓库是用户的"万物文件夹",由 DID 标识,包含集合,集合包含记录:
graph TD
DID[DID:did:plc:xxx] --> Repo[Repository]
Repo --> Collection1[com.twitter.post]
Repo --> Collection2[fm.last.scrobble]
Repo --> Collection3[com.ycombinator.news.vote]
Collection1 --> Record1[记录1]
Collection1 --> Record2[记录2]
Collection2 --> Record3[记录3]D. Lexicon(词表)
词表是记录格式的定义,类似于 TypeScript 类型定义,但更丰富:
{
"lexicon": 1,
"id": "com.twitter.post",
"defs": {
"main": {
"type": "record",
"key": "tid",
"record": {
"type": "object",
"required": ["text", "createdAt"],
"properties": {
"text": { "type": "string", "maxGraphemes": 300 },
"createdAt": { "type": "string", "format": "datetime" }
}
}
}
}
}词表特点:
- 可以表达约束(如字符串长度、格式)
- 易于解析,便于构建工具
- 可以生成多种语言的类型定义和验证代码
E. DID(去中心化标识符)
DID 是账户的永久标识符,支持多种方法:
graph TD
DID[DID 标识符] --> Methods[方法类型]
Methods --> did_web[did:web - 域名]
Methods --> did_plc[did:plc - 注册表]
Methods --> did_future[did:bla - 未来方法]
did_web --> Example1[did:web:wint.co]
did_plc --> Example2[did:plc:6wpkkitfdkgthatfvspcfmjo]DID 解析过程:
- 给定 DID,获取其文档(包含当前托管、句柄、公钥)
- 文档指向数据的实际托管位置
- 从托管位置获取记录
五、技术实现细节
1. 记录键命名
使用时间戳加随机性混合的编码:
posts/
├── 34qye3wows2c5
├── 34qye3wows2k3
└── 34qye3wows3k3优点:
- 可以本地生成,几乎不会冲突
- 按字母排序即按时间排序
- ls -r 可以得到按时间倒序的时间线
2. at:// URI
at:// URI 是指向记录的永久链接,可以经受托管和句柄变更:
at://did:plc:6wpkkitfdkgthatfvspcfmjo/com.twitter.post/34qye3wows2c5
└─────────── who ──────────────┘ └─ collection ─┘ └── record ─┘解析流程:
- 解析 DID 获取托管位置
- 从托管位置获取指定集合中的记录
3. 记录间关系
通过 at:// URI 实现记录间的引用:
graph TD
Post[Post 记录] --> hasLikes[有点赞]
hasLikes --> Like1[Like 记录]
hasLikes --> Like2[Like 记录]
hasLikes --> Like3[Like 记录]
Like1 --> references[引用]
references --> Post
Post --> hasReplies[有回复]
hasReplies --> Reply1[Reply 记录]
Reply1 --> parentField[parent 字段]
parentField --> references示例:
- 点赞记录:{ "subject": "at://did:plc:xxx/com.twitter.post/34qye3wows2c5" }
- 回复记录:{ "text": "yes", "parent": "at://did:plc:xxx/com.twitter.post/34qye3wows2c5" }
六、数据完整性验证
1. 哈希树结构
仓库结构化为哈希树(Merkle Tree):
graph TD
Root[Root Hash] --> Commit[签名 Commit]
Commit --> Tree[Tree Delta]
Tree --> contains1[包含]
contains1 --> Record1[记录 1]
Tree --> contains2[包含]
contains2 --> Record2[记录 2]
Tree --> contains3[包含]
contains3 --> Record3[记录 3]每次写入都是包含新根哈希的签名提交,这使得:
- 可以验证记录与原作者公钥的匹配
- 中继可以只重传证明,不存储内容
- 降低中继运行成本
2. 数据流订阅
除了将仓库视为文件系统(列出和读取),还可以将其视为流,通过 WebSocket 订阅。
sequenceDiagram
Repo as Repository
Relay as Relay 中继
App as 应用缓存
Repo->>Relay: 提交新记录
Relay->>App: 推送事件 + 树证明
App->>App: 验证证明
App->>App: 更新本地缓存七、实际应用案例
1. Sidetrail 应用
作者创建的 Sidetrail 应用演示了数据真实来源于仓库的概念:
export async function handleEvent(db: IngesterDb, evt: JetstreamEvent): Promise<void> {
if (evt.kind !== "commit") return;
const { commit } = evt;
const { collection, rkey } = commit;
if (commit.operation === "delete") {
switch (collection) {
case "app.sidetrail.walk":
await deleteWalk(db, uri);
break;
}
return;
}
const record = commit.record as Record<string, unknown>;
switch (collection) {
case "app.sidetrail.trail":
await upsertTrail(db, uri, commit.cid, evt.did, rkey, record);
break;
}
}用户在 pdsls 中删除 app.sidetrail.walk 记录,Sidetrail 应用中的对应 walk 会自动消失。
2. teal.fm 演示
一个展示最近播放曲目的演示:
graph LR
Users[用户] --> Scrobble[Scrobble 记录]
Scrobble --> creates[创建]
creates --> fm_teal[fm.teal.alpha.feed.play]
fm_teal --> indexed[被索引]
indexed --> Relay[中继]
Relay --> GraphQL[lex-gql GraphQL]
GraphQL --> Demo[演示网站]关键点:
- teal.fm API 实际上不存在
- teal.fm 产品也只是着陆页
- 任何人都可以开始通过在仓库中放入记录来记录播放
- 演示只索引 fm.teal.alpha.feed.play 记录
八、词表生态系统
1. 词表发现
通过以下工具发现现有词表:
- pdsls.dev:词表目录服务
- Lexicon Garden:词表展示平台
- UFOs:词例浏览器
2. 词表发布
词表可以作为记录发布:
graph TD
Lexicon[词表定义] --> publish[发布到]
publish --> com_atproto[com.atproto.lexicon.schema]
com_atproto --> contains[包含]
contains --> io_overreacted[io.overreacted.comment]需要通过 DNS 设置证明域名所有权。
3. 词表验证原则
- 应用始终将记录视为不受信任的输入
- 在读取时验证,就像文件一样
- 演进词表时不能改变哪些记录被认为是有效的
- 可以添加新的可选字段,但不能改变字段的可选性
九、信任模型
1. PLC 目录
PLC(Permissionless Concatenatable Log)目录的特点:
graph TD
User[用户] --> creates1[创建账户]
creates1 --> Generate[生成密钥对]
Generate --> Sign[签名操作]
Sign --> Hash[哈希]
Hash --> DID[DID 标识符]
User --> updates[更新]
updates --> NewOp[新操作]
NewOp --> Prev[prev 指向前一个]
Prev --> Chain[操作链]验证过程:
- 检查签名有效
- prev 字段匹配前一个操作的哈希
- 验证整个更新链到第一个操作
- 第一个操作的哈希就是标识符
2. 中继信任
中继不存储私钥数据、完全开源、操作日志可审计。理想情况下会独立成为法律实体。
十、优势与挑战
1. 优势
A. 数据主权
用户真正拥有自己的数据,可以自由迁移托管
B. 应用创新
任何人都可以为现有数据创建新应用
C. 互操作性
不同应用可以共享和理解相同的数据格式
D. 可验证性
数据可以验证真实性,无需信任中继
2. 挑战
A. 派生数据计算
应用需要计算派生数据(如点赞数)而无需额外开销
B. 中继信任
如何确保他人的中继不撒谎
C. 用户体验
密钥管理对大多数用户来说很复杂
十一、总结
AT 协议通过将文件系统理念引入社交网络,实现了:
- 数据与应用的真正分离
- 用户数据归属于用户
- 应用可以自由创新,不被锁定
- 数据可验证、可迁移、可互操作
这种范式转变可能重新定义社交网络的基础架构,让社交计算回归个人计算的本质。