前Uber工程师拆解数据模型 称文档与图数据库难撼关系型根基

[Chinese] Chapter 3: Data Model & query language — Scott Shi

Scott Shi

文档数据库的读时写回机制违背架构原则且扩展受限,工业级超大系统因数据局部性与迁移成本依然重度依赖关系型底层存储。

AI-assisted summary of [Chinese] Chapter 3: Data Model & query language — original source. Check the original for full context.

Source published:

Key ideas

  • 关注点分离是数据模型与声明式语言的核心哲学,使底层并发控制与上层业务建模彻底解耦,这一思想亦通过亚马逊 2002 年 API 圣旨深刻影响了分布式架构的发展。 (0:00)
  • 文档数据库的 Schema-on-read 虽然适应异构数据,但在处理字段演进时常出现的“读时迁移并隐式写回”模式违背了架构直觉,且由于缺乏有效的数据局部性控制极易引发跨分片性能退化。 (19:15)
  • 异构图数据模型在表达多实体、多跳深度递归关系(如地理层级与司乘冲突工单追溯)时优势显著,但工业界超大规模应用大多选择在分片关系库上构建逻辑映射,而非直接采用原生图数据库。 (41:00)

Detailed notes

## Background 该视频由技术博主 Scott Shi 于 2020 年 11 月 10 日发布在 YouTube 平台。视频简介指出,本期内容为 Scott 带领观众精读并梳理《Designing Data-Intensive Applications》(DDIA)中的数据模型(Data Model)与查询语言(Query Language)核心章节,并附带了演示幻灯片链接。

## TL;DR 本次分享从“关注点分离”这一底层架构哲学切入,系统对比了关系型模型、文档型模型与图数据模型的取舍演进与适用边界。讲者结合自身在 Uber 安全团队等一线大厂的工程履历,深度剖析了 SQL 优化器的内部复杂度、MongoDB 读时反向写回的反模式与容量陷阱,以及数据局部性(Data Locality)对多租户系统的决定性影响。同时,视频横向对比了命令式与声明式编程范式的威力,并以生动案例讲解了 Facebook 异构图、Neo4j Cypher 模式匹配及 Datalog 递归逻辑推理在复杂拓扑关系中的实战价值。

## Atmosphere & style 整场技术分享呈现出一种直率、犀利且极具实战沉淀的硅谷工程师硬核复盘氛围。讲者毫无照本宣科的枯燥感,而是频频结合大厂实操经历撕下工业界技术炒作的滤镜,既有对经典技术债务(如老旧 Redshift 与 Airflow 分支)的坦率吐槽,也有对 MongoDB 规模化扩展痛点的尖锐剖析,现场弹幕互动频繁且共鸣强烈。讲者在技术阐述上深入浅出,行云流水地穿梭于编译原理、硬件加速与业务风控建模之间,结尾即便略带抱恙也干脆利落收尾,给听众带来极为痛快且信息密度极高的大厂老兵带练体验。

## Bulletin - [00:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=0s) 关注点分离是数据模型与声明式语言的核心哲学,使底层并发控制与上层业务建模彻底解耦,这一思想亦通过亚马逊 2002 年 API 圣旨深刻影响了分布式架构的发展。 - [19:15](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1155s) 文档数据库的 Schema-on-read 虽然适应异构数据,但在处理字段演进时常出现的“读时迁移并隐式写回”模式违背了架构直觉,且由于缺乏有效的数据局部性控制极易引发跨分片性能退化。 - [41:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=2460s) 异构图数据模型在表达多实体、多跳深度递归关系(如地理层级与司乘冲突工单追溯)时优势显著,但工业界超大规模应用大多选择在分片关系库上构建逻辑映射,而非直接采用原生图数据库。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=0s) 数据模型划分为外部、概念、逻辑与物理四个层级,其核心哲学是关注点分离,由数据库底层兜底事务原子性与持久性,解耦应用层业务开发。 - [03:45](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=225s) 传统单机 SQL 数据库在早期缺乏成熟分片方案时常遭遇单表数百 GB 的性能与系统瓶颈,且商用数据库往往背负极重的老旧技术债务与代码升级障碍。 - [03:45](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=225s) ORM 映射层试图弥合面向对象与关系模型的鸿沟,但生成了海量冗长样板代码,在处理复杂事务时不仅设计脆弱,还常成为线上隐患的重灾区。 - [07:56](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=476s) 关系模型的规范化(Normalization)能有效消除数据歧义并实现多对多关系的原子级联更新,而文档模型在多对多引用场景下会导致昂贵的多次网络往返。 - [11:13](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=673s) SQL 查询优化器是极其复杂的科研与工程战场,为了突破 CPU 计算与解析算子的延迟极限,云数仓与量化高频交易场景已引入 FPGA 硬件加速。 - [15:10](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=910s) Schema-on-write 能够在写入边界强行拦截脏数据,而 Schema-on-read 虽适用于爬虫抓取等异构对象共存场景,却将字段校验与缺失防御的负担完全转嫁给了读取端代码。 - [19:15](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1155s) MySQL 5.7+ 引入的虚拟列(Virtual Column)支持在读时动态执行表达式且不产生物理落盘,避免了线上亿级大表执行 DDL ALTER TABLE 带来的严重 I/O 阻塞风险。 - [25:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1500s) MongoDB 在字段变更演进中依赖读取触发写回(Read-triggered Write)的做法属于架构反模式,极易破坏读操作的幂等性并在并发下产生隐蔽的数据脏写。 - [30:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1800s) 数据局部性(Data Locality)直接影响数据库的吞吐与碎片控制,Salesforce 的多租户存储改造、Google Spanner 的交错表以及 Cassandra 的分区键均致力于将高频关联记录物理聚集。 - [33:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1980s) 声明式语言(如 SQL)仅向引擎描述目标结果而非执行步骤,其结果无序性假定赋予了查询优化器跨分片并行扫描与无感重构底层的巨大红利。 - [41:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=2460s) 图数据模型以节点和边为核心基元,Uber 安全团队借助异构图将乘客、司机、冲突事件、遥测轨迹与客服工单打通,实现了单点穿透全息档案的风控中台体系。 - [47:45](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=2865s) 在关系型数据库中通过顶点表与边表硬性模拟图拓扑时,跨层级嵌套查询(如跨大洲出生与居住地匹配)会导致极其冗长且易使执行计划退化的递归 CTE 与多重 Join。 - [51:15](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=3075s) 图查询语言(如 Neo4j Cypher)以简洁的模式匹配大幅简化了多跳图遍历,但工业界出于亿级数据迁移风险与工程妥协,通常更倾向在成熟的物理分片关系库上搭建逻辑图映射层。 - [56:10](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=3370s) Datalog 与 Prolog 等声明式逻辑编程语言依靠公理事实与递归推导规则优雅求解图层级关系,但其符号推理体系仅适用于规则明确的受控闭环环境,难以直接承受非结构化脏数据的冲击。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=0s) Data Model 的核心抽象与设计哲学:Separation of Concerns

在讨论具体技术之前,首先需要明确 Data Model(数据模型)最底层的设计哲学——关注点分离(Separation of Concerns)。这部分内容对应《Designing Data-Intensive Applications》(DDIA)的第二章。书中回顾了许多早期历史上的数据模型,例如在 20 世纪 70 年代 SQL 关系模型出现之前风靡一时的层次模型(Hierarchical Data Model)和网状模型(Network Data Model)。尽管这些老旧模型早已过时,但回顾历史有助于理解数据模型本质上是一种高度精炼的抽象(Abstraction)。

以数据库系统本身为例,它对应用程序开发者(Application Programmer)而言就是一种极佳的关注点分离:数据库负责提供持久性保证(Durability Guarantee)和并发控制(Concurrency Control)等底层语义与屏障。当多个并发请求同时涌入时,开发者不必从零编写底层逻辑去处理冲突,数据库提供的抽象承诺替业务层兜了底。

这种关注点分离的思想在软件工程界影响深远,最著名的范例莫过于亚马逊创始人杰夫·贝佐斯(Jeff Bezos)在 2002 年发布的“API 圣旨”(Amazon API Mandate)。贝佐斯展现出了极具前瞻性的战略眼光,明确规定以后所有跨团队(Cross-team)的业务协作与数据交互必须全部通过定义清晰的 API 进行通信,这一极具远见的决定奠定了后来微服务与云计算的基础。

```mermaid flowchart TD accTitle: Multi-level Data Models accDescr: 展现从对外抽象到物理存储的四层数据模型体系

subgraph Business["业务与概念层"] Ext["External Data Model<br/>企业对外/跨系统暴露的模型"] Conc["Conceptual Data Model<br/>业务实体抽象 (如 Uber 乘客/司机)"] end

subgraph System["系统与实现层"] Log["Logical Data Model<br/>ERD 实体关系设计与表结构"] Phys["Physical Data Model<br/>物理存储拆分与落盘机制"] end

Ext --> Conc Conc --> Log Log --> Phys ```

在日常系统架构与设计中,数据模型通常可以划分为四个递进的层次: - **External Data Model(外部数据模型)**:关注跨系统、跨企业边界时对外部所暴露的数据视图。 - **Conceptual Data Model(概念数据模型)**:偏向高层业务抽象。例如在 Uber 的业务中,核心概念实体包括乘客(Rider)、司机伙伴(Driver Partner)以及外卖配送(Delivery);而在 Facebook 中,则是用户(User)与地点(Place)等核心概念。 - **Logical Data Model(逻辑数据模型)**:系统设计中最常使用的形态,通常体现为实体关系图(ERD),明确定义系统中的数据实体以及实体之间具体的一对一、一对多或多对多关系。 - **Physical Data Model(物理数据模型)**:关注底层存储落地,即如何把逻辑模型拆解并具体映射到特定数据库的物理文件、分片与存储引擎中。

当关系型数据库与 SQL(Structured Query Language)问世时,它带来了一场真正的工程革命,其核心依然是关注点分离: - **数据库内核开发者(Database Developers)**:专心研究底层如何做并发控制、Write-Ahead Logging(预写式日志)、故障恢复,以及如何保证事务的原子性(Atomicity)、持久性(Durability)或者在出错时支持事务回滚(Rollback/Abort)。 - **应用层开发者(Application Developers)**:专注于业务视角下的表结构(Table Schema)设计与索引(Index)构建。 - **数据分析师与分析开发者(Analytic Developers)**:在当时应用与分析界限尚不明显的阶段,专注于编写声明式的 SQL 查询,从海量数据中提取出业务洞察(Insights)。

---

[03:45](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=225s) SQL 的扩展瓶颈与商业数据库的技术债务

尽管 SQL 取得了巨大成功,但开发者社区长期以来对传统 SQL 也积攒了诸多痛点与挫败感,其中最核心的矛盾便是可扩展性(Scalability)。在现代数据库分片(Sharding)概念和技术成熟之前,SQL 数据库的单机容量受制于操作系统的物理限制。理论上现代关系数据库引擎可以支撑几百个 TB 的数据,但在早期底层操作系统环境下,单实例数据量往往在 2TB 左右就会遇到明显的系统级瓶颈。例如在 Uber 内部就曾有明确规范:单实例单表如果数据量超过 500GB,就不建议继续使用裸 MySQL,而必须转向专门的分布式数据架构。

除了分片缺乏导致的扩展难题,工业界的技术选型也在持续向自由开源软件(Free and Open Source Software, FOSS)倾斜,只有少数 To B 商业公司仍重度依赖商用闭源数据库。商用数据库内部充满了不透明的技术实现与极其陈旧的技术债务。以亚马逊的 Redshift 为例,其底层许多核心代码编写于 1997 至 1998 年前后,甚至长期停留在 C++98 或 C++03 标准上: - 源码中缺乏大量现代基础库支持,工程师必须手工编写大量定制库(Customized Libraries)。 - 根本无法升级到 C++11、C++14 乃至 C++19,任何基础语言版本的升级都会引发大面积的破坏性崩溃(Broken Code)。 - Redshift 虽然在性能上对传统 Hive 形成了降维打击——在 Hive 上跑一整天的查询,在 Redshift 上十几分钟即可返回——但其 SQL 查询前端(Front-end)实际 Fork 自十多年前极老版本的 PostgreSQL 解析与执行计划生成引擎,由于脱离了开源主干,只能在老代码分支上由内部团队缝缝补补。 - 类似的困境在 Uber 内部也发生过,Uber 内部的工作流调度系统 Piper 也是早期从极旧版本的 Apache Airflow 分支而来,随着内部演进彻底与开源生态脱节,维护成本极其沉重。

```mermaid flowchart LR accTitle: ORM Translation Layer Overhead accDescr: 描述面向对象代码通过 ORM 映射到底层关系数据库时的层级与复杂度

Code["Application Code<br/>(Business Objects)"] --> ORM["ORM Layer<br/>(Boilerplate Code & Mapping)"] ORM --> SQL["Relational Tables<br/>(Schema on Write)"] ```

与此同时,传统关系型 Schema 的死板限制也让开发者倍感折磨。在 MySQL 5.7 之前,数据库对 JSON 等半结构化数据的支持非常弱,字段只支持纯文本(Text)或整型(Integer)等固定类型。一旦业务需要存储灵活演进的数据格式,就必须在应用层手动维护一套 Schema 映射,这带来了惊人的系统复杂度。

最令现代开发者痛苦的莫过于对象关系映射层(Object-Relational Mapping, ORM): - 开发者试图在面向对象的代码与底层的关系型表之间架设桥梁,生成了浩繁冗长的样板代码(Boilerplate Code)。 - 许多代码表面上由工具自动生成,但一旦混入了定制的业务逻辑,往往极易出现隐匿的缺陷。每当线上出现严重故障时,根因往往就藏在这些平日里被忽略的 ORM 适配层代码中。 - 现有的主流 ORM 类库在处理复杂事务(Transactions)时设计并不完善,这也直接推动了后来更加动态、更具表现力的数据模型(如 NoSQL、文档型数据库)的大爆发。

---

[07:56](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=476s) Relational Model 与 Document Model 的对比:Normalization 的权衡

在建模 LinkedIn 用户个人档案(Profile)的场景中,关系型模型与文档型模型(Document Model,如 MongoDB)展现了完全不同的建模思路:关系数据库会将一个人的履历打散归一化(Normalize)存储在多张不同的表里(用户表、职位经历表、教育经历表、技能表等);而文档型数据库只需将整份履历直接序列化为一个单独的 JSON 格式文档即可。

关系型数据库坚持的规范化(Normalization)设计带来了数项不可替代的核心优势: - **保证数据的一致性规范与风格(Consistency)**:例如近期科技界推行的术语修改,要求将系统中的 `whitelist`(白名单)和 `blacklist`(黑名单)全局替换为 `permitted list`(允许列表)和 `blocklist`(阻止列表)。在规范化模型中,只需在对应的字典表或定义表中修改一行记录,全系统关联该项的地方便自动生效;若采用自由文本(Free Text)存储,则必须在无数字段中进行全局全文替换。 - **避免数据歧义(Avoid Ambiguities)**:现实世界中存在大量同名、相似名称或拼写错误的城市名称。规范化后可以通过唯一的 `City ID` 进行精准关联,即便后续修改城市官方名称,底层关联也不会错乱。 - **易于级联更新多对多关系(Many-to-Many Relationships)**:假设多名员工(例如员工与同事廖文文)均在 Salesforce 工作,当 Salesforce 官方更换了公司名称或品牌 Logo 时,在规范化的关系模型中,每份个人档案只是持有一个指向企业主表的外键指针(Pointer),管理员只需在企业表的一个记录中更新 Logo,所有员工的个人档案便全量同步刷新。若在非规范化的文档数据库中,所有员工履历的嵌入式 JSON 字符串中都存了一份公司名称,就必须写脚本去全量扫描并扫描更新所有文档。 - **天然的层级地理与本地化检索支持(Localization Support)**:例如当西雅图建立了所谓的“国会山自治区”(CHAZ 联邦)时,在地理关系上它可以挂载在西雅图市之下,西雅图又隶属于华盛顿州,华盛顿州隶属于美国。当用户发起“检索华盛顿州下属所有企业”的范围查询时,规范化的层级关系链能够直接将属于该自治区的公司正确检索出来;同时还能更平滑地支撑多语言本地化翻译(如 LinkedIn 中文版界面的展示切换)。

---

[11:13](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=673s) 数据库内核剖析:SQL Optimizer 的极致复杂性与硬件加速

SQL 接口表面上看似极其直观简练,甚至在工业界给人一种“任何需求都能写句 SQL 搞定”的常态化错觉,但其水面之下的数据库内核极其复杂。

```mermaid flowchart TD accTitle: SQL Engine Internal Architecture accDescr: 详细展示从 SQL 语句解析到查询优化器、执行计划以及底层存储引擎的处理流程

subgraph FrontEnd["SQL 解析与优化 (PhD Research 焦点)"] Raw["SQL Query"] --> Parser["SQL Parser / Command Parser"] Parser --> AST["Query Tree / Abstract Syntax Tree (AST)"] AST --> Optimizer["SQL Query Optimizer<br/>(基于规则与代价评估,生成最优路径)"] Optimizer --> Plan["Execution Plan / Query Plan"] end

subgraph StorageEngine["Storage Engine (底层系统工程实现)"] Plan --> Engine["Engine Execution"] Engine --> TM["Transaction Manager<br/>(并发控制与事务协调)"] Engine --> BM["Buffer Manager<br/>(Buffer Pool 页面置换与 Dirty Pages 管理)"] Engine --> WAL["Write-Ahead Log (WAL)<br/>(Durability 与崩溃恢复)"] end ```

当客户端发起一条 SQL 查询后,数据库内核会经历以下关键处理阶段: 1. **SQL 解析器(SQL Parser / Command Parser)**:首先对输入的 SQL 字符串进行语法与语义校验,验证其是否为合法的 SQL 语句,并将其解析转换为查询树(Query Tree),在编译器层面即抽象语法树(Abstract Syntax Tree, AST)。 2. **查询优化器(Query Optimizer)**:这是整个关系型数据库中最深奥、耗费了学术界数十年博士课题研究(“PhD Model Research”)的“怪兽模式”(Beast Mode)。优化器需要根据表统计信息、索引分布、物理分布等计算代价值,将抽象语法树重写并优化为最高效的执行计划(Execution Plan)。相比之下,绝大多数 NoSQL 数据库在架构上根本不做这种全局性的查询优化,直接按预设逻辑执行。 3. **存储引擎(Storage Engine)**:执行计划生成后交由底层存储引擎运行。存储引擎本质上是专门针对高并发优化的专用文件系统,包含若干核心组件: - **Transaction Manager(事务管理器)**:统一调度和管理当前并发运行的多个事务,保障 ACID 隔离性与提交回滚。 - **Buffer Manager(缓冲池管理器)**:管理内存中的 Buffer Pool,负责内存与磁盘之间的数据页置换,实时追踪脏页(Dirty Pages)并择机刷盘。 - **Write-Ahead Log(预写式日志 WAL)**:与通用操作系统文件系统不同,关系数据库的所有数据修改在落盘前必须先顺序追加记录日志,以此提供持久性保证与崩溃安全保障。

数据库内核的难度呈现鲜明的双轨分工:底层 Storage Engine 属于极为硬核的工程系统实现(Engineering-heavy);而前端的 Query Optimizer 则是充满数学建模与运筹推演的顶级科研战场。在追求极致低延迟的工业场景中,例如景祥提及的 Redshift 团队,为了将优化器和算子性能推向极限,已经开始抛弃通用的通用 CPU 计算节点,转而使用现场可编程门阵列(FPGA)芯片直接在硬件层面上固化逻辑来优化处理。类似的极致技术路线在量化对冲基金(Hedge Fund)中极为盛行——在量化高频交易场景中,降低一毫秒(1ms)的通信与运算延迟就意味着数百万美元的真金白银收益,因此对冲基金大量高薪招募 FPGA 硬件加速专家。

---

[15:10](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=910s) 查询性能机制:Locality 与 Schema on Write vs. Schema on Read

在对比关系型数据库与文档数据库的实际读写表现时,若只对比单条记录(Single Record)的定位与读写性能,两者在理论与基准测试上没有数量级上的本质差异,因为现代 SQL 存储引擎历经了数十年的极限调优。

两者的核心性能分歧在于数据局部性(Data Locality)与关系范式的权衡: - **完整个人档案读取场景**:在 MongoDB 文档数据库中,用户的职位、教育、技能全部紧凑内嵌在单一 JSON 文档内,系统仅需发起一次磁盘或内存的读取(Single Read)即可获取全貌,具备无可比拟的数据局部性(Data Locality);而在关系型数据库中,同样的业务需求必须从 6 到 7 张完全独立的物理表中分别检索若干条关联记录,将单次读取转变成多次离散的跨表查询或 JOIN 操作。 - **多对一与多对多引用场景(Many-to-Many References)**:文档数据库的优势在此类场景下荡然无存,甚至性能大幅落后于 SQL。SQL 的外键关联(JOIN)可以在数据库内部以极高效率的内存查找(In-memory Join)流水线完成;而在文档数据库中,引用外部实体往往依赖于“文档引用”(Document Reference),这会导致多次网络往返(Round Trips):客户端先读取主文档,解析出引用的 ID,再发起另一次网络请求去读取被引用的关联文档。即便部分 MongoDB 官方客户端会在 SDK 层面对此类连续读取做封装合并,其总体开销依然显著高于原生 SQL JOIN。

```mermaid flowchart TD accTitle: Schema on Write vs Schema on Read Comparison accDescr: 对比关系数据库在写入时校验与文档数据库在读取时校验的运行机制

subgraph Relational["SQL: Schema-on-Write (强约束)"] W1["Write Request"] --> V1{"Schema 静态类型校验"} V1 -- 合法 --> DB1[("写入落盘")] V1 -- 格式不符 --> R1["直接 Reject 拒绝写入"] DB1 --> Read1["Read: 字段结构确定,直接使用"] end

subgraph Document["MongoDB: Schema-on-Read (高弹性)"] W2["Write Request"] --> DB2[("写入落盘 (只要是有效 JSON 即可)")] DB2 --> Read2["Read Request"] Read2 --> V2{"读取端检查所需字段是否存在"} V2 -- 存在 --> Use["正常业务处理"] V2 -- 缺失/异常 --> Err["业务层必须处理缺失/异常逻辑"] end ```

除了局部性,两者在 Schema 约束的执行时机(Enforcement Timing)上存在根本性的哲学分歧: - **Schema-on-Write(写时约束)**:以传统 SQL 为代表。数据在写入数据库的那一瞬间,引擎必须强行校验记录类型、字段边界以及外键完整性约束;任何与预设 Schema 不符的脏写入请求都会被数据库底层直接拦截并拒绝。 - **Schema-on-Read(读时约束)**:以 MongoDB 等文档数据库为代表。写入时引擎对内容格式完全放任,只要是语法正确的 JSON 对象即可原样持久化。但在应用程序端读取该数据时,代码必须预判字段可能缺失或变更的情况。例如编写一个能够自动检测网页结构变化的动态爬虫(Smart Crawler)去抓取新闻网站,开发人员写入时可以全量丢入非结构化 JSON,但在下游解析读取时,系统必须在应用层显式判断是否存在 `news_title` 或 `news_author` 字段,并编写大量的异常回退分支来防御缺失风险。

---

[19:15](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1155s) Schema 演进实战:MySQL Virtual Column 与 MongoDB 读时迁移的陷阱

在实际业务迭代中,数据模型的字段拆分十分常见。例如早期用户表中只存有包含空格的全名 `name`(如 `FirstName LastName`),现在需要提取出独立的 `first_name`。针对这个需求,关系型数据库与文档数据库展现了完全不同的技术演进路径。

```mermaid flowchart TD accTitle: MySQL Dynamic Schema Approaches accDescr: 对比传统复制整表的 DDL 变更与利用虚拟列进行动态计算的两种方案

subgraph HeavyDDL["传统 ALTER TABLE (低效且有风险)"] OldT["亿级用户大表"] --> Copy["全表物理 Copy (耗时数小时甚至数天)"] Copy --> NewT["新增独立物理 Column: first_name"] end

subgraph ModernMySQL["MySQL 5.7+ Virtual Column (优雅高效)"] BaseCol["物理字段: name"] -. 动态表达式提取.-> VirtCol["Virtual Column: first_name<br/>(不物理落盘,仅在读时计算)"] Client["SELECT first_name FROM users"] --> VirtCol end ```

在 SQL 体系中,传统的做法是执行数据定义语言(DDL)更新 Schema: - 在 PostgreSQL 中,执行类似 `ALTER TABLE` 添加字段的操作效率极高,通常仅在系统元数据中追加定义,属于轻量级原地变更。 - 在 MySQL 的早期版本中,执行 `ALTER TABLE` 往往会引发全表复制(Table Copy)。如果线上用户表拥有数十亿行(Billions of rows)数据,该操作将导致长达数小时乃至数天的严重磁盘 I/O 堵塞与服务不可用,在线上变更时必须高度警惕。 - 不过从 MySQL 5.7 版本起,MySQL 正式引入了**虚拟列(Virtual Column)**功能。开发者无需真正物理落盘新增一个持久化列,而是可以定义一个基于既有字段表达式计算得出的虚拟列(例如通过字符串截取函数从 `name` 字段实时提取名字)。在底层物理存储上该列完全不存在,但对外提供无缝的 SQL 接口,应用层可以直接执行 `SELECT first_name FROM users`。

在 MongoDB 这类文档数据库中,处理类似演进则完全无需执行 DDL,通常采取应用层的读时迁移(On-the-fly Migration): - 读请求到来时,应用层检查该 JSON 是否包含独立的 `first_name` 字段。 - 若不存在,则应用逻辑动态从 `name` 字符串中解析截取,然后再顺手将补齐了 `first_name` 的全新 JSON 格式**反向写回(Write back)**数据库,使该条记录完成静默升级。 - **架构反模式警示(Caveat)**:这种在执行数据读取(GET/Read)操作的生命周期内静默触发数据库写操作(Database Write)的设计,在宏观架构原则上通常被视为极具隐患的反模式(Anti-pattern),极易导致幂等性失效或并发脏写。然而在 MongoDB 的特定实践与开发者圈子中,这种做法由于过于顺手,甚至演变成为了一种约定俗成的惯用手法。

在随后的问答环节中,现场学员与讲师针对这一机制展开了即兴交流: - **学员 Links 提问**:若客户端向基于 JSON 的文档数据库发送一个写请求,该 JSON 中包含一个历史记录中从未出现过的新字段(例如全新的 `FirstName`),系统是否能够成功写入? - **讲师解答**:数据库必定会无条件接受该写入。这就是 Schema-on-read 的本质特征——写端对任何合法的 JSON 完全敞开大门,代价则是下游读取时若缺失该字段必须由业务代码兜底容错。讲师随即调侃道,自己早年在学生时代也被 MongoDB 华丽的宣传(“Signin 阶段特别火爆”)给骗过,写过大量的 Mongo 代码;但在工业界实际经历过大规模生产运维的技术人员,往往深刻体会过其缺乏严格类型约束带来的系统混乱,大多对裸写 Mongo 抱有复杂的厌倦态度。 - **学员宇全提问**:在传统关系数据库(如 MySQL 5.7+)中创建 Virtual Column 时,系统是每次执行查询读取都动态重新计算一次,还是在首次读取后就顺势对整张表完成了数据物化迁移(Materialized Forever)? - **讲师澄清**:Virtual Column 在默认机制下是永远不物化落盘(Never Materialized)的。底层只保存表达式逻辑,每次客户端发起读取请求时,引擎在内存流水线中动态执行计算并即时返回结果,以此换取零维护成本与零存储膨胀。

[25:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1500s) 吐槽 MongoDB 的性能与扩展性陷阱:读操作竟暗藏写逻辑

在讨论传统 SQL 与文档型数据库在底层存储上的差异时,Scott 提到在 SQL 里如果只需要查询用户的 `first name`,底层的行为往往是直接按列读取;但如果存储的是 `full name`,数据库则需要在读取后经过运行时运算提取出 `first name` 返回给上层。接着话题转向公认饱受争议的 MongoDB。听众 Paul 在弹幕中吐槽曾被 Mongo 坑过,Scott 对此深表赞同,直接表态“不太推荐 Mongo”。他透露自己在多家公司亲眼见过大量反面案例,Mongo 的实际表现非常糟糕。

Scott 分享了一个真实例子:某家他不便具名的大公司曾用 Mongo 存储上下文信息(Context),当数据量仅仅达到十亿级别(1 Billion)时,Mongo 就直接崩溃了,性能急剧下滑。现场有听众补充“还剩 70% 的 space 就不 work 了”,Scott 纠正道,实际上是存储容量刚用了 70%、还有 30% 剩余空间时,Mongo 就完全无法 scale 了。这背后的核心痛点集中在扩展性(scalability)和索引(index)机制上:Mongo 本质是通过 Object ID 或 Document Reference 读写 JSON 文档。如果不建索引,很多查询需求根本无法满足;可一旦加上索引,不但没能解决扩展性瓶颈,反而引发了更多性能与维护难题。Scott 总结道,如果是平时自己做 side project 或者小玩具项目,用 Mongo 无所谓;但如果真正放在生产环境想要大规模扩展,必然会踩到深坑。

更有趣的是,关于 MongoDB 的黑料甚至在出版界都有体现。Scott 指出,在这本参考书(《Designing Data-Intensive Applications》)的纸质版与电子版之间存在微妙差异:电子版删减了一部分内容,正好是一段吐槽 Mongo 的犀利言论,他戏称“不知道是不是 Mongo 有人起诉它”,并庆幸自己看的是包含额外细节的纸质版。

此外,Scott 深入剖析了文档数据库中“读操作触发写操作”的不良设计: - **读时产生隐式写(Read-triggered Write)**:当你在 Mongo 中变更了 schema(例如新增字段 `first name`),如果读取某个旧文档时发现缺少该字段,系统并不会在 schema 变更阶段一次性全量迁移,而是在你读取该文档的瞬间,在应用层或底层拆解计算出 `first name`,紧接着反手写回数据库。 - **违背设计直觉**:这种行为就像发出了一个无害的 `HTTP GET` 请求,底层却偷偷执行了一次数据库写入操作(`database write`)。这在软件架构中是一个明显的“Red Flag”(警示信号),但在 Mongo 圈子里久而久之竟然被当成了一种被默认接受的习惯用法(convention)。

```mermaid flowchart LR accTitle: Mongo Read-triggered Write Flaw accDescr: 读操作发生时由于缺失字段而隐式触发写回数据库的不良设计流程

subgraph Client ["客户端请求"] R["GET / Document<br/>(Request First Name)"] end

subgraph MongoFlow ["底层隐式行为"] Check{"字段是否存在?"} Calc["拆解计算缺失字段"] WriteDB[("偷偷写回 Database<br/>(Implicit Write)")] ReturnData["返回组合后的 Document"] end

R --> Check Check -- "不存在" --> Calc Calc --> WriteDB WriteDB --> ReturnData Check -- "存在" --> ReturnData ```

当然,读时模式(Schema-on-read)并非一无是处,它在特定场景下有其不可替代的灵活性: - **异构数据抓取(Crawler 场景)**:网络爬虫从外部抓取的每条数据结构迥异(例如游戏对战复盘与新闻资讯的数据结构截然不同),且这些数据结构完全由外部系统决定。爬虫自身无法强加模式,直接序列化成 JSON 存入 Document Database 反而是最稳妥的做法。 - **多类型共存(Heterogeneous Objects)**:当系统内有大量不同类型的对象,却又不想为每个类别分别建立一张独立的数据表(如避免为每个爬虫源都建一个表),希望统合存储在通用的 `object` 表中时,Schema-on-read 是最佳方案。 - **系统解耦**:如果网络爬虫系统强依赖于前置 schema,一旦目标抓取网站改版改变结构,整个下游系统便会瞬间崩溃。将架构拆分为“爬取(Crawler)”、“解析(Parse)”与“模式校验(Schema)”三部分并实现解耦,系统韧性会高得多。

---

[30:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1800s) 数据局部性(Data Locality)与多租户架构挑战

聊完 Schema-on-read,Scott 引出了另一个至关重要的系统设计概念:**数据局部性(Data Locality)**。数据局部性的核心在于考察数据库的访问模式(Database Access Pattern),即常见的高频访问数据在物理底层是否紧密存放在一起。

数据局部性带来的收益显而易见:首先是读取吞吐量和性能的大幅飙升;其次是能够有效遏制底层存储碎片的恶化。Scott 以 Salesforce 的多租户(Multi-tenant)环境为例作了生动说明:Salesforce 承接着大量相互竞争的企业客户,比如 Uber 和 Lyft 都是其客户。在底层存储的单张 `user` 用户表中,每一行数据是混杂交织排列的——比如第 1 行是 Uber 的记录,第 2 行可能就是死对头 Lyft 的数据。当新用户请求到达时,虽然应用层会根据所属公司的 Tenant ID 做校验和路由,但当这些数据一行行以元数据形式写入底层同一张表时,竞争对手的数据在物理磁盘上近在咫尺。早期的 Salesforce 基于 Oracle Database 架构,后来构建了名为 Phoenix/Cionala 的层级存储系统,通过采用追加日志(Append-only Log)以及修改 B+Tree 存储引擎机制,确保同一家公司(如 Uber)的数据尽可能集中在连续的存储区域,实现按租户隔离与物理聚集,从而最大化提升局部性性能。

同样的设计哲学在现代主流分布式存储中随处可见: - **Google Spanner**:虽然呈现的是关系数据模型(Relational Data Model),但在 DDL 中允许显式指定表与表之间的父子关系(Interleaved Tables),将子表的相关行物理存放在父表行的同一个存储分片内。 - **Apache Cassandra**:强制引入了分区键(Partition Key)的设计,其初衷正是为了精准控制数据局部性。 - **Schemaless 的缺陷**:为什么业内很多无模式(Schemaless)系统表现不尽如人意?原因就在于它们完全放弃了对数据局部性的把控。如果没有明确的局部性控制机制,单个事务中需要读取的几条记录可能会被哈希分散到完全不同的物理 Shard 上。一旦发生跨分片读取(Cross-shard Query),网络往返和分布式协调便会让系统陷入混乱。Cassandra 允许应用层开发者根据具体业务语义指定 Partition Key,从而将关联记录牢固锁在同一分片甚至同一节点上,极大地拯救了系统的吞吐性能。

---

[33:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1980s) 声明式语言 vs 命令式语言:关注点分离的威力

接下来,Scott 对比了数据模型背后的两套编程范式——命令式语言(Imperative Language)与声明式语言(Declarative Language): - **命令式语言**:代表有 C++、Java、Go 以及底层的 JavaScript 操作。特点是详尽阐述“每一步该如何做”(How to do),紧密绑定执行过程。 - **声明式语言**:代表为 SQL。其底层根基是关系代数(Relational Algebra),特点是仅向引擎清晰陈述“需要得到什么数据”(What to find),例如简单的 `SELECT * FROM user WHERE city = 'San Francisco'`。

Scott 强调,声明式语言的核心魅力在于**关注点分离(Separation of Concerns)**,他甚至直言:“如果将各种系统设计理念进行排名,关注点分离在我心目中绝对能排进前两名(Top 2)。”在声明式架构中,作为业务方的 Application Developer 只需关心业务需求本身,完全与底层的实现细节解耦。

这种分离为底层基础设施团队带来了巨大的优化空间: - **无感重构**:只要数据契约(Agreement)未变(即提取旧金山用户的契约),底层即使将查询优化器(Query Optimizer)全盘重写、甚至彻底换掉底层存储引擎,对上层业务代码也是零感知、零侵入。而在命令式语言中,实现逻辑与业务紧紧耦合,底层的任何改动都极易导致输出结果产生偏差。 - **无序性换取高度并行**:SQL 规范中默认不保证结果的特定排序,表面上看功能被极大限制,实际上为平台开发者留出了巨大的并行执行(Parallel Execution)红利。例如上千个分片的 `user` 表,即便未按城市建索引,优化器也可以瞬间将该查询并行下发到 1000 个分片并发执行,极大缩短整体耗时;若用命令式代码逐个分片遍历处理,开发人员就必须亲自在业务层调度每个分片的读取顺序,陷在繁复的细节中难以脱身。 - **MapReduce 的定位**:Scott 顺便补充,经典的 MapReduce 既不是纯粹的声明式,也不是纯粹的命令式,而是介于两者之间的折衷形态(Somewhere in between)。

为了让没有编程语言理论基础的听众更容易理解,Scott 借用了前端开发中的生动对比: - **HTML + CSS(声明式)**:在 HTML 里定义包含 `class="user"` 的表格,CSS 里只需声明一句“此类表格呈现红色”。当需求变动希望改回绿色时,直接在 CSS 中修改属性值,浏览器渲染引擎就会瞬时将全局所有的用户表格全部变为绿色。 - **JavaScript 手动操作 DOM(命令式)**:若全由 JS 操刀,开发人员必须显式编写一个 `for` 循环,遍历页面上的所有 DOM 节点,逐一取出并将表格样式赋值为目标颜色。每次样式变动都需要重新触发并执行这段循环逻辑,业务逻辑与底层的循环遍历细节深深缠绕在一起。

---

[37:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=2220s) 图数据模型(Graph Data Model)与现实复杂拓扑

进入第四部分,Scott 探讨了许多工程师推崇备至的“图数据模型”(Graph Data Model)。他直言,许多人光听名字觉得图模型很酷炫,但一旦真正深入到底层实现和 traversal 细节,往往就得“打上一个大大的问号”。

图数据模型的核心极其简单纯粹,仅由两种元素构成: 1. **顶点(Vertices / Nodes)**:代表实体。 2. **关系(Relationships / Edges)**:代表实体之间的连接线。

图模型天然适合表达高度互联的复杂现实拓扑结构: - **社交网络(Social Graph)**:Facebook 中用户即节点,好友关系即边。 - **网页图谱(Web Graph)**:每个网页是一个 Node,超链接(Hyperlink)则是连接它们的 Edge,Google 的 PageRank 算法正是借助网页拓扑的链接权重来计算网页的流行度。 - **交通与铁路线路网(Road / Railway Network)**:交叉路口(Intersection)作为节点,公路线段作为边,是车载导航系统求解加权最短路径(Weighted Shortest Path)的标准建模方式。

```mermaid flowchart TD accTitle: Graph Data Model Core Elements accDescr: 图数据模型的基本构成要素及其在现实网络中的映射应用

subgraph Primitives ["图模型核心基元"] V["顶点 / 节点 (Vertices / Nodes)<br/>实体 (User, Page, Intersection)"] E["关系 / 边 (Relationships / Edges)<br/>连接 (Friend, Hyperlink, Road)"] end

subgraph Applications ["典型业务场景"] A1["Social Graph<br/>(Facebook 用户好友网络)"] A2["Web Graph<br/>(Google PageRank 排名)"] A3["Transportation Network<br/>(车载加权最短路径导航)"] end

V --- E Primitives --> Applications ```

为了剖析其复杂度,Scott 引用了 Facebook 经典论文中的异构图(Heterogeneous Graph)案例: 假设 Alice 和 Bob 在金门大桥(Golden Gate Bridge)签到(Check-in),Cathy 留言道“真希望我们也在那”,而 David 为该留言点了赞。在这张图中: - 用户实体 Alice、Bob、Cathy、David 各自作为一个 `User Node`; - Alice 与 Bob、Bob 与 Cathy、Cathy 与 David 之间由 `Friend` 关系的边连接; - 金门大桥作为一个代表物理位置的 `Location Node`; - Alice 触发的“签到”动作本身也被抽象为一个独立的 `Action Node`,它既指向签到发起人 Alice,又标注(Tag)了被关联的同行人 Bob,同时该动作的发生地边指向金门大桥 Location; - 下方 Cathy 的留言则是挂在签到消息下方的 `Comment Node`(包含具体的留言文本内容),指向其作者 Cathy; - 最后,David 的点赞行为也是一条由 David Node 指向该 Comment Node 的 `Like` 边。 整个场景交错着不同类型的节点与边,构成了一张极其复杂的异构图。

---

[41:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=2460s) Uber 安全团队业务架构:用异构图解构打车冲突与工单

顺着 Facebook 的异构图思路,Scott 结合自己在 Uber 安全团队(Safety Team)的工程亲历,分享了他曾在组内力推的图模型落地提案。听众 Paul 此时在互动中补充:Facebook 表面上对外提供的是 Graph Data Model(Social Graph),但其底层实际上并没有采用原生的图数据库,而是构建在基于分片的多主 MySQL(Multi-leader Sharded SQL)之上,这与 Uber 自研的 Schemaless 架构在设计哲学上非常相似。Scott 确认并赞同了这一点,指出 Facebook 核心用的其实也是分片 MySQL 代码。

Scott 重点介绍了图模型在 Uber 安全事件中的业务映射(Uber Safety Case): 在一次打车行程中,经常会出现司乘矛盾甚至人身冲突。如果用异构图模型来抽象这个场景: - **用户节点(User Nodes)**:场景中包含 Alice(乘客)、Bob(拼车同行人)、Cassie(司机)以及 David(Uber 客服/安全处置专员)。 - **乘车关联与冲突边**:Alice 和 Bob 拼车出行(Co-ride / Shared Ride),途中由于人际矛盾爆发了冲突(Inter-personal Conflict)。 - **工单与事件节点**: - 遇到不适体验的乘客向平台发起投诉投诉,系统生成一张 Blitz Ticket(Blitz 是 Uber 自研的客户工单支持系统,用于处理日常客服反馈)。 - 若冲突升级被判定为严重安全事件(Serious Incident),工单体系会联动创建专门的“零号工单”(Zero Ticket,即核心安全事件单),由平台安全专员 David 介入调查。 - **物理世界与遥测数据挂载**:Alice 上报的 Incident 关联到具体的打车订单(Order & Job)与实际行程(Trip)。该 Trip 节点下方不仅记录了司机 Cassie 正在履约的服务明细,还挂载着大量的车辆遥测数据(Telemetry Data)。

```mermaid flowchart TD accTitle: Uber Safety Heterogeneous Graph Architecture accDescr: Uber 安全团队将用户、拼车、冲突、工单系统与遥测数据统一建模的异构图架构

subgraph Users ["用户与员工节点"] U_Alice["User: Alice (乘客)"] U_Bob["User: Bob (同行人)"] U_Cassie["User: Cassie (司机)"] U_David["User: David (Uber 安全专员)"] end

subgraph ConflictTrip ["物理行程与安全事件"] T["Trip: 拼车行程<br/>(挂载 Telemetry 遥测数据)"] Inc["Incident: 人际冲突事件<br/>(Inter-personal Conflict)"] end

subgraph Tickets ["工单支撑体系"] BT["Blitz Ticket<br/>(客户投诉工单)"] ZT["Zero Ticket<br/>(严重安全事件单)"] end

U_Alice -- "拼车 (Shared Ride)" --- U_Bob U_Alice -- "参与" --> T U_Cassie -- "履约服务" --> T U_Alice -- "上报事故" --> Inc Inc -- "关联行程" --> T Inc -- "触发工单" --> BT Inc -- "升级严重事件" --> ZT ZT -- "指派专员处理" --> U_David U_David -- "调查沟通" --> U_Cassie ```

Scott 指出,一旦系统能够以类似 Facebook 的异构图形式组织数据,工程团队就可以在图上自由进行图遍历(Graph Traversal),并在此基础上搭建强大的可视化安全风控中台: - **全息档案洞察**:只需选中节点 Alice,就能顺着拓扑边一次性拉出她的全部档案、系统角色、过往活动行为、历史关联行程(Trips & Orders)以及所有关联的安全事故单(Incidents)。 - **事件追溯链条**:当调查某个特定事故(Incident)时,无需在多张分散的关系表中进行极其折磨的跨表 Join,沿着图边缘即可瞬间穿透所有关联人(卷入的乘客、司机与处理专员)及其底层的高精度行车遥测轨迹,使错综复杂的安全溯源变得井然有序。

[45:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=2700s) 关系型数据库与图数据库的适用场景对比

车联网与设备监控中常见的 Telemetry data(遥测数据)往往包含极高密度的时序追踪信息,例如车辆在每一分每一秒的具体 GPS 经纬度位置、瞬时速度、加速度大小以及加速度方向等。这类信息连同客服系统中类似 David 这样的工单数据(如响应时长 response time、所处理的工单 tickets work on、对话记录 conversation 以及评分 rating),完全可以用非常通用的 Entity-Relationship(实体-关系)模型在关系型数据库中落地。Scott 坦言自己最近很想抽空深入研究一些架构实践,无奈当下“没有时间是时间”,许多更紧急的业务项目排在前面。

如果只是存储与读取这种单层、结构扁平的常规实体与关联事件,使用图数据库(Graph Database)相较于传统 SQL 数据库其实没有任何性能优势。然而,一旦面对层级极深、错综复杂的嵌套关系(Nested Relationship),图数据库的威力便展现出来。以《Designing Data-Intensive Applications》(DDIA)中的经典案例为例:北美洲(North America)包含美国,美国包含爱达荷州(Idaho),Lucy 出生在爱达荷州却生活在英国伦敦;伦敦隶属于英国,英国隶属于欧洲;Lucy 的配偶 Alain 生活在伦敦,但他出生在法国的某个城市。如果此时要执行一个多重跨层级的关联查询——“寻找一对现居欧洲的已婚夫妇,其中一方出生在北美洲,另一方出生在欧洲”,用 SQL 来表达会变得异常痛苦与复杂,而在图数据库中却能够以极其直观的路径匹配轻松搞定。

```mermaid flowchart TD accTitle: 嵌套地理与人物关系图 accDescr: 展示北美与欧洲跨大陆、国家、城市与人物之间复杂的嵌套关系

subgraph NorthAmerica["北美大陆 (North America)"] US["美国 (USA)"] Idaho["爱达荷州 (Idaho)"] US --> Idaho end

subgraph Europe["欧洲大陆 (Europe)"] UK["英国 (UK)"] London["伦敦 (London)"] France["法国 (France)"] CityFR["法国城市"] UK --> London France --> CityFR end

Lucy["Lucy"] Alain["Alain"]

Idaho -.->|出生于| Lucy Lucy ===|结婚且同居| Alain Lucy -.->|现居| London Alain -.->|现居| London CityFR -.->|出生于| Alain ```

- **图数据库的两大主流实现流派**: - **Property Graph Model(属性图模型)**:节点和边都可以承载灵活的键值对属性,以 Neo4j 为代表。 - **Triple-Store(三元组存储)**:基于类似三段论的主谓宾(Subject-Predicate-Object)结构进行图描述。 - **Neo4j 的核心卖点**:采用“Index-free adjacency”(无索引邻接),在商业化落地与生态支持方面表现尤为成熟。

---

[47:45](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=2865s) 关系模型中模拟图数据与复杂查询的痛点

如果退回到关系型数据模型(Relational Data Model)中去硬性模拟一个图(类似早期 Facebook 的 Social Graph 实现思路),工程师必须手动设计两张核心表:Vertex(顶点表)与 Edge(边表)。Vertex 表包含 `vertex_id` 以及各类属性字段;Edge 表则包含 `edge_id`、起点 `tail_vertex`、终点 `head_vertex`、边标签(例如 `born_in`、`living_in`、`married_to` 等不同关系类型),以及附加在边上的自定义属性。为了能够双向检索,通常还需要为边的起点和终点分别建立两个外键索引。

一旦要在这种关系模型之上执行“找出出生在北美但现居欧洲的人”这样的跨层级嵌套查询,SQL 就会迅速膨胀为极其复杂的递归查询(Recursive CTE)。查询首先需要递归查找北美大陆之下的所有子行政区划(州、市、县),再递归查找属于欧洲的所有行政区划,随后分别查询出所有在北美区域出生的人以及所有在欧洲区域生活的人,最后把这四组中间结果通过多重 Join 串联起来提取姓名。数据库查询优化器在处理这种语句时,必须计算并挑选一个代价最小的集合作为驱动表去层层匹配,整体优化难度极高,且执行计划很容易退化。

- **顶点表结构(Vertices)**:包含 `vertex_id` 与属性载荷(properties)。 - **边表结构(Edges)**:包含 `edge_id`、`tail_vertex`(起点)、`head_vertex`(终点)、`label`(关系类型)以及边属性。 - **递归检索代价**:SQL 必须频繁依赖多轮自连接与递归遍历,对查询优化器的执行计划生成构成了巨大挑战。

---

[51:15](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=3075s) 图查询语言与 Graph Database 的选型思考

相比之下,图数据库(如 Neo4j 的 Cypher)在建模和查询这类数据时显得格外自然。在建模阶段,实体被直接创建为 Location(细分 continent、country、state)和 Person 节点,并通过直观的关系边(如 `WITHIN`、`BORN_IN`、`LIVES_IN`)彼此相连。在执行查询时,SQL 需要写六七行甚至更长的深层递归 Join,而 Cypher 只需短短三行声明式的模式匹配(Pattern Matching):匹配同一个 Person 节点,其 `BORN_IN` 出生地的节点递归位于美国境内,而其 `LIVES_IN` 居住地的节点递归位于欧洲境内,即可直接返回匹配人选。

关于架构选型,Scott 再次强调系统设计中“没有一个好的 solution 是 one size fits all”。选择图数据库的前提是核心数据模型本身就是图,并且业务充斥着大量诸如“朋友的朋友的朋友”(friends of friends of friends)这种多跳(Multi-hop)深度图遍历需求。如果是扁平或者单一维度的简单关联,MySQL 等关系型数据库已经足够优秀,早期的 Facebook 社交网络底层也大量依托 SQL 架构。面对听众提问“是否有特别著名的工业级产品底层完全采用图数据库”,Scott 直言像 Neo4j 确实在许多商业场景中提供 Index-free adjacency 遍历能力,但在超大规模知名产品的主核心链路中,自己尚未读到完全依赖纯图数据库作为核心底层存储的案例。

随后听众问及 Uber 内部的 Customer 2.0 系统在关联关系底层是否采用了图数据库,Scott 笑称“你把 Customer 2.0 想得太好了”。他读过该系统长达 70 页的设计文档与 ER 图,其底层物理数据模型(Physical Data Model)根本没有做重构,依旧跑在 Uber 自研的 Schemaless 旧存储架构之上。团队改变的仅仅是中间的逻辑数据模型(Logical Data Model)与映射层(Mapper/API 层):

> “你不管是用旧的架构还是 Customer 2.0,从 database 的角度来讲都没有变过,你始终还是读的同一个 database,读的同一个 table。只是读完以后在 mapper 那层把原本割裂的 Rider、Driver、Partner 关系抽象映射成了统一的 User 逻辑模型。”

从工程实现角度来看,涉及数以十亿计(billions)用户的跨数据库底层数据迁移极其痛苦且极易出事故,维持物理存储不变是完全合乎工程理性的务实妥协,但其商业宣传上的卖点确实远多于底层革命性的技术创新。

---

[56:10](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=3370s) Datalog 与 Prolog 的声明式逻辑递归查询

在讨论完 SQL 与 Cypher 后,Scott 引入了声明式逻辑编程语言(Declarative Logical Programming Language)的代表——Datalog 与 Prolog。在计算机编程语言理论课上接触过 Prolog 的工程师会发现,图结构中错综复杂的递归层级同样能以逻辑公理与规则(Rules)的形式优美表达。在 Prolog 风格的代码中,先声明一系列事实(Facts):如定义北美、美国、爱达荷州与 Lucy,并指明美国在北美境内、爱达荷在美国境内、Lucy 出生在爱达荷。

处理层级隶属时,Prolog 会定义两条核心规则: 1. **基础终止条件(Base Case)**:如果 X 直接位于 Y 内部(`in(X, Y)`),则判定 X 属于 Y。 2. **递归推导规则(Recursive Rule)**:如果 X 位于某个中间位置 Z 内部,且 Z 递归位于 Y 内部,则推导得出 X 位于 Y 内部。

依靠这两条公理,查询时只需要像自然语言一样声明所期望的目标:“寻找一个 Person,其出生的城市递归包含在北美之内,且其居住的城市递归包含在欧洲之内”。虽然初次接触这种思维方式时会觉得逻辑推导有些反直觉,但它完美契合了纯递归的逻辑世界。Scott 评价道,Prolog 宛如经典符号 AI(Symbolic AI)的缩影:如果将其丢到现实世界高度复杂的非结构化数据(unstructured data)中,它会“fail miserably”(一败涂地);但若置于规则明确、数据规整受控的闭环虚拟环境里,其推理性能与表达力非常优异。

在全篇结尾,Scott 总结道本章核心梳理了数据模型与查询语言的演进脉络。由于下一章节涉及的存储引擎底层细节内容庞大,他将留待后续分享。在向听众打完招呼并透露下周将讨论 YouTube 系统设计主题后,Scott 提到自己身体稍有不适准备前往医院就诊,随即干脆利落地结束了本次分享。

## Notable quotes > [33:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1980s) 如果将各种系统设计理念进行排名,关注点分离在我心目中绝对能排进前两名(Top 2)。 — Scott Shi

> [25:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1500s) 不知道是不是 Mongo 有人起诉它 — Scott Shi

> [51:15](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=3075s) 没有一个好的 solution 是 one size fits all — Scott Shi

> [51:15](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=3075s) 你把 Customer 2.0 想得太好了 — Scott Shi

> [51:15](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=3075s) 你不管是用旧的架构还是 Customer 2.0,从 database 的角度来讲都没有变过,你始终还是读的同一个 database,读的同一个 table。只是读完以后在 mapper 那层把原本割裂的 Rider、Driver、Partner 关系抽象映射成了统一的 User 逻辑模型。 — Scott Shi

> [25:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1500s) 不太推荐 Mongo — Scott Shi

> [25:00](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=1500s) 还剩 70% 的 space 就不 work 了 — 听众

> [56:10](https://www.youtube.com/watch?v=a9B9lwqTr8s&t=3370s) fail miserably — Scott Shi

## Mentioned Designing Data-Intensive Applications (DDIA), Jeff Bezos, Amazon API Mandate, Uber, Facebook, Amazon Redshift, MySQL, PostgreSQL, Apache Airflow, Piper, MongoDB, Salesforce, FPGA, Google Spanner, Apache Cassandra, Salesforce Phoenix/Cionala, Oracle Database, MapReduce, Google PageRank, Blitz, Neo4j, Cypher, Customer 2.0, Schemaless, Datalog, Prolog

## Video description In this video, Scott walked through the data model and query language part.

https://docs.google.com/presentation/d/164lN2QsA8zGmRUe2aNqNebwgeqqgoQjX1gP5QxOntuk/edit

[Source](https://www.youtube.com/watch?v=a9B9lwqTr8s)

中文简报

前Uber工程师拆解数据模型 称文档与图数据库难撼关系型根基 文档数据库的读时写回机制违背架构原则且扩展受限,工业级超大系统因数据局部性与迁移成本依然重度依赖关系型底层存储。 本次分享从“关注点分离”这一底层架构哲学切入,系统对比了关系型模型、文档型模型与图数据模型的取舍演进与适用边界。讲者结合自身在 Uber 安全团队等一线大厂的工程履历,深度剖析了 SQL 优化器的内部复杂度、MongoDB 读时反向写回的反模式与容量陷阱,以及数据局部性(Data Locality)对多租户系统的决定性影响。同时,视频横向对比了命令式与声明式编程范式的威力,并以生动案例讲解了 Facebook 异构图、Neo4j Cypher 模式匹配及 Datalog 递归逻辑推理在复杂拓扑关系中的实战价值。 关注点分离是数据模型与声明式语言的核心哲学,使底层并发控制与上层业务建模彻底解耦,这一思想亦通过亚马逊 2002 年 API 圣旨深刻影响了分布式架构的发展。 (0:00) 文档数据库的 Schema-on-read 虽然适应异构数据,但在处理字段演进时常出现的“读时迁移并隐式写回”模式违背了架构直觉,且由于缺乏有效的数据局部性控制极易引发跨分片性能退化。 (19:15) 异构图数据模型在表达多实体、多跳深度递归关系(如地理层级与司乘冲突工单追溯)时优势显著,但工业界超大规模应用大多选择在分片关系库上构建逻辑映射,而非直接采用原生图数据库。 (41:00)

Reading summary

讲师 Scott Shi 围绕《DDIA》梳理了数据模型与查询语言的演进脉络,指出其底层哲学在于通过“关注点分离”实现业务逻辑与物理存储解耦;并在对比关系型、文档型与图模型时强调,工业级系统选型需在规范化、数据局部性及演进成本间务实权衡。 核心设计哲学:关注点分离与分层抽象 (0:00) 数据模型本质是精炼的抽象,在系统架构中通常划分为外部、概念、逻辑与物理四个递进层次。数据库本身对应用开发者提供了持久性与并发控制的屏障,正如 2002 年亚马逊的“API 圣旨”推动了服务边界的解耦,关系数据库的声明式 SQL 仅要求表达“寻找什么”,而将并发调度、预写式日志(WAL)与执行计划生成交由内核处理。 声明式语言不仅使底层存储引擎和查询优化器的重构对业务透明,其默认不保证排序的特性还为大规模分布式系统的并行执行留出了红利。相比命令式代码必须在业务层逐个遍历分片,优化器可将无序查询直接并发下发至上千个分片处理。 规范化与局部性权衡:关系模型对比文档 (7:56) 在建模履历等场景中,关系模型的规范化(Normalization)通过唯一 ID 和外键消除了数据歧义、简化了术语一致性调整,并在多对多关联变更时实现单点级联刷新;而文档模型将完整履历内嵌于单个 JSON,凭借单次 I/O 获得极高的数据局部性(Data Locality)。但在多对多或跨实体引用时,文档模型依赖多次网络往返,性能显著落后于 SQL 的内存 JOIN。 两者在数据校验机制上也存在哲学分歧:SQL 采用写时约束(Schema-on-write),底层拒绝一切不符合规范的脏写入;文档数据库则采用读时约束(Schema-on-read),写入端放任合法 JSON 进入,但下游读取时应用层必须编写复杂的异常分支来防御字段缺失风险。 文档数据库的扩展陷阱与隐式写反模式 (25:00) 针对生产环境的技术选型,Scott 直言不推荐盲目采用 MongoDB。他援引真实案例指出,某大公司在十亿级数据且存储容量剩余 30% 时系统便无法扩展并陷入崩溃,其索引机制反而加剧了维护负担。更为严重的是,文档模型在模式演进时常采用应用层动态补全并写回(Read-triggered write)的惯用手法,在执行数据读取的请求中隐式触发数据库写入,破坏了架构设计的幂等性。 不过,读时模式在网络爬虫数据抓取、异构对象统一存储等场景下仍具独特价值。面对外部结构多变且不可控的数据源,直接持久化 JSON 可以避免目标网站改版导致下游校验系统级联崩溃,实现抓取、解析与模式校验的高韧性解耦。 图数据拓扑表达力与工业落地的工程妥协 (37:00) 图模型由顶点与边构成,天然适合表达社交网络、城市路网及 Uber 安全事件工单等复杂的异构关联拓扑。面对“跨大陆的多层嵌套人员检索”等深层递归关系,属性图(如 Neo4j 的 Cypher)以三行声明式模式匹配即可完成,而传统 SQL 必须依赖极易导致优化器执行计划退化的高成本递归查询(Recursive CTE)。 然而在工业落地的现实中,极少有超大规模系统的主核心链路完全运行在原生图数据库上。例如 Facebook 和 Uber(如安全风控中台与 Customer 2.0 体系)更倾向于维持底层分片 MySQL 或自研分布式存储的物理结构不变,仅在逻辑层与 Mapper API 映射层抽象出图视图与统一实体模型,以规避百亿级核心数据物理迁移的高昂风险。 数据模型没有通用的完美方案,架构选型必须紧扣数据访问模式与局部性需求。在大规模生产环境中,工程团队往往倾向于选择已被充分验证的基础存储引擎作为底座,再在其上通过抽象层按需构建声明式或图拓扑视图。

Source quotes

[33:00] Scott Shi: 如果将各种系统设计理念进行排名,关注点分离在我心目中绝对能排进前两名(Top 2)。 [25:00] Scott Shi: 不知道是不是 Mongo 有人起诉它 [51:15] Scott Shi: 没有一个好的 solution 是 one size fits all [51:15] Scott Shi: 你把 Customer 2.0 想得太好了 [51:15] Scott Shi: 你不管是用旧的架构还是 Customer 2.0,从 database 的角度来讲都没有变过,你始终还是读的同一个 database,读的同一个 table。只是读完以后在 mapper 那层把原本割裂的 Rider、Driver、Partner 关系抽象映射成了统一的 User 逻辑模型。 [25:00] Scott Shi: 不太推荐 Mongo [25:00] 听众: 还剩 70% 的 space 就不 work 了 [56:10] Scott Shi: fail miserably

Original transcript

[0:00] 我就说这个下周可能周三讨论然后再下周就变成周二周四讨论了也是时间改一下就这样的话周末的话时间更好安排一点好然后我们接着上次那个Data Model讨论Data model的话,一个很重要的设计里面就是separated concerns嗯这个呢是DDI Design Data Intensive Application的第二张,这张写的也相对是比较不错的,它列举了很多非常老的一些历史上的一些Data Model,比如说,就是在70年代Sycle出现之前的Hariaki Data Model和Network Data Model就是非常过时的,但是它除了这部分以外,还有一部分写的非常不错的。总体来说,Data Model其实是一个非常好的Abstraction,它采用的总体设计市场就是separate concern比如说Database就是一个对于Application Programmer,就是separate concern,Database就是guarantee durability,Guarantee Concurrent Control 这些,它相当于提供一个Guarantee,也是一个Abstraction那你在database上写的话就很多比如说同时有两个request进来,database会发生什么,database其实就帮你提供了一个这个的guarantee。然后separate concern其实用在很多方面,比如说这个Amazon对于API的mandate,就是Jeff Barrows,我也觉得Jeff Barrows是非常有visionary的一个leader。他就在02年的时候就说以后所有的cross team之间的coordination都要通过API,你必须定义好API来相互交流,这个也是非常有vision的,在02年的时候就做到这一点,就是非常有vision。然后data model其实是有多种data model的,比如说external data model,conceptual data model,external data model,就比如说你不同的company之间,或者是你对外的这个,这个你是exposed什么样的一个model的然后conceptual的话就比如说Uber的话就相对conceptual就比较简单,你有rider,有driver,partner,delivery,你另一个的话就是我想举个其他例子,比如说linkin或者是facebook的话就可能conceptual就是facebook,conceptual就是你有Facebook的话有Facebook user,有place这种是比较conceptual的。Logical data model相对来说是你design用的比较多的,就是ERD里面常用的那种,你把一个东西列出来,然后它之间的relationship是怎么样子的。然后physical data model就是你存到不同的database里面,你要把它怎么样把logical break down出来,然后存成physical的这种。然后我们又简单讲了SQL和NoSQL的这个简单历史啊,那Sycle呢是Sycle呢是relational database,然后它实际上是当时出来的时候非常revolucent的因为它其实提供了几种很重要的思想,最常见的思想就是separate concerns,就是database的developers focus on怎么样做concursion control怎么样做login,怎么样guarantee durability,怎么样guarantee atomic,或者是怎么样能revoque,就是那个rollback,rollback或者bot这个transaction他会在这张财产中的财产。然后这个其次呢那个Application Developer呢是想怎么样做index啊,怎么样把它index过去是吧,这个怎么样设计这个table schema然后其次的话还有analytic,那会儿analytic和application分的不是那么多,analytic developer的话就说我怎么样写这个SQL query,这个SQL叫simple query language,我怎么样SQL query来写然后能从这个data中得到更多的insights,这个其实分得非常开的,这个思想就是分的separate concern,把设计成不同的用途。然后但是大家都比对SQL比较frustrated,主要的原因就是scalability,在没有很好的sharding出来之前,没有这个杀点的概念出来之前,它其实是有这个Skills的限制的,看到这个Skills,Skills是可以支持非常大的,几百HundredTB的这个Data的,但是它Limitation在Operating System的,Operating System,我记得那天查了一下可能Limitation就是两个TB左右,就是Operating System,它这个对于这个Skills database的DataSize,Limitation大概是两个TB左右,不会差太多,Uber内部的话是500Gb,就是你如果是这个,你在一个,一个,一个,如果有超过500Gbps的话,你可能就要考虑用其他database,而不用就简单的卖 Seql 了然后所以scalability如果没有sharding的话是很成问题的,其次的话大家prefer用一些free and open source software,只有2B的可能偏向于用commercial database,因为commercial database有很多不透明,并且software其实中间有很多非常ugly的地方,比如说景强经常谈那个redshift里,它的code其实很多都是九几年写的,97年98年写的,他们还用那个C++98,C++99,反正就是 [5:00] 里面连很多基础的一些library都没有的,他们还得自己写一些自己customize library,因为他们根本没有机会去更新C++的那个Version所以1114什么都不用想,19都不用想,还是用98还是99的Version中间还是非常旧的这个,所以虽然看起来Redshift是比Hive要快很多,Redshift的performance比Hive要快很多的很多时候你在Hive上跑一个Core要跑一天,在Redshift上可能就十几分钟就返回了,他们从performance的角度来讲是真的是优化了很多所以然后去优化的,所以你update一下,upgrade一下language,可能会出现很多broken的地方。其次的话,包括Uber的Airflow,Uber的copy,就是Piper的Airflow也出现过这样的问题。他们是在Redshift里面他们copy了一个就是Postgres的一个PostgreSQL Core Engine 就是它怎么样把 SQL CorePost 成 Execution Plan,他们叫Front-end,就是把Sycocory,Post 成 Execution Plan,它是Copy的一个非常旧的Postgres的一个,按这个,是十几年前的版本,导致它没法更新,它就必须在那个Version上不停的更新,自己更新。Uber有类似的,Uber的Piper是Poppy的一个非常old version的一个Airflow,然后你后面的话发展就不一样了,就得自己更新。虽然用起来可能可能你可能进行这样的优化那样的优化,但是总体来说你和这个open source的ware就脱节了嘛,commercial software就有这样的问题然后其次的话,relational schema的话,它相当来说它非常restrictive,很多时候就非常frustration这个之前的话,比如说在SQL,我记得SQL 5.7之前,但是不是很好支持 Jason 的,所以你这个 field 只有几种模式 就是什么 text 啊 或者是 integer 啊这种 所以你不能存一个 JSON 上去可能在5.7在之前嘛,就是不是很好支持这个schema的,如果你要存一个就是relatively flexible schema的话,你需要maintain自己的schema,其实中间会增加很多complexity。所以这个NoSQL的话,它基本上都是非常dynamic和非常expressive data model。然后最后SQL是最painful的一个point,也是基本上所有人都讨厌的一个point,是有一个非常awkward translation layer,叫ORM。Object Relationship Matching,这个的话其实有很多Complexities在这个里面的,它很多时候是Boiler Plate Code,你有时候看的时候你都觉得它是自己Automatically Generated如果你有看过那个4M code,很多时候都觉得automatically generated,但是实际上里面有时候写的不好的话会有很多customized logicbusiness logic,然后很多时候broker的时候就那块broker,就是你其实ORM这块这个codebroker了,但是平常时候都会略过这一块。然后已有的几个,并且已有的几个ORM的library并不很好,因为我们到时候在讲这个SQL transaction的时候会看到它为什么已有的ORM很多时候实现的不好。在印度上你有一个非常 painful的RM layer我们这块上一次讲了就是怎么样用sql存,怎么样用document存,对于linkin来说,sql的话就把它breakdown成不同的table,如果是document存的话就存成一个jsonfile就可以了,如果用mongodb的话就是jsonformat就可以了那Normalization的时候呢,假设我们把它做Normalization有什么好处呢?首先你就是你可以consistent 是吧 你这个spelling 或者是style 比如说这两天要把white list 变成什么blacklist变成blocklist 是吧,你这样的话只要一个地方改了,其他就要全变成blocklist了Whitelist好像变成Permitted List什么的,就是这个要改变是吧,这个你如果是成为Free Text的话,你需要改无数多个地方。然后其次的话是可以Avoid Ambiguities,比如说你有Several City,有Same Name,Similar Name,Spinning Mistake什么的。如果是存存这个Normalize以后,你可以说这个City ID是多少,然后你City命名的时候你可以跟着改了然后update的话相对来说也是比较容易的这前三条基本是比较相似的一致的其次的话是有一些localization support,因为有的这个data呢,比如说,比如说San Francisco,我们比如说西雅图吧,西雅图不是建了一个西雅图联邦,最近叫了一个Chess联邦,你假设它真的列出来了,那这个西雅图自制联邦,然后会在西雅图市里面,然后西雅图是在华盛顿州里面,华盛顿在美国,那你在search, [10:00] 在华盛顿州的公司的时候,西雅图自治联邦也会把它列进去,因为有一个localization,就是localization search,这个是一些relationship,是可以better management的。然后其次的话,你把它翻译成不同language,我知道就是大家还有人用比如说linked中文版的时候,它可以translate的时候更formal一些,这样的话也比较好理解。其次的话这个这个relational database 如果把它normalize的话有一点好的是many to many relationship 比如说这个是我的一个link in profile 是吧 我可能在Southforce工作做 然后Southforce突然换logo了这个比如说是另一位Southforce员工 廖文文的profile 是吧 然后也在Southforce工作 结果Southforce换名称了或换logo了那他只要在同一个地方换就行了 因为这个都是pointer这个是many to many relationship,相对来说manager比较好一点。但如果你是document的话,你这块除了其实这个string,你可能需要对于所有的Salesforce的这个都需要update,需要Salesforce name什么都需要update。然后这个Seql的这个我可能以后会花点时间讲的比较多一点这个Seql其实是一个非常非常复杂的一个东西Seql虽然看起来是很简单但是就是现在是老生常谈就感觉什么事都用Seql但是Seql本身是一个非常复杂的东西它中间有很多中间有很多优化的部分比如说,比如说,SQL里面有很多优化的部分,比如说当你叫一个SQL Core的时候,它首先,SQL有一个就是有个interface,然后他会把它这个就是interface这个命名有时候不一样但一般来说这个叫什么sql-pulsor或command-pulsor的命名有时候有地方不一样但是他会把这个command的SQL先pause一下就是写过Compiler的话,这个Pause就和那个类似的就是,它会首先verify你这个是不是valid SQL Query,然后Pause一下,然后把它变成一个QueryTree,Compiler的话是Ebstract Syntax Tree然后他这时候会做optimization,Sycle optimization是非常非常complex的一个地方,Sycle optimization叫Beastmo,就是你其实是他其实花了几十年的research,很多PhD model进行research,然后把它optimize一个非常高效的地方,像景祥那个不是在他说那个Redshift,他实际上他optimization为了现在为了提升他那个效率,已经开始用那个嗯就是那个program bar,array 来进行实现了,pgma 还是什么,我忘了那个什么了,我之前也写过。PGFA写了,它其实已经不是用传统的计算器,Generic Node来去optimize,它用PGFA来optimize,你可以写一个CPU,用PGFA写一个CPU,它效率更高一点,它比Generic CPU效率更高一点,就是他们已经用了这么擅长的人了,另一个用PGFA的另用PGFA的这个optimized的是那个Hedge FundHedge Fund 赃了好多PGFA的人,就是他是为了把那个lentency因为它这个Hedge Fund里面一毫秒的Lentency值好几个Million所以它就是为了用PGFA算的速度是比传统的CPU快的就是把它先提前program好了算的速度是比CPU快的它就开始写这个优化了所以这个optimization是优化呢其实是SQL这个optimization的优化其实是它最复杂的地方就是它所有的research花了很多的地方然后它会生成一个Execution Plan但是你所有的NoSQL这个optimization基本上都不做的,因为它不做这个optimization,你知道吗?就很多时候它是没有这个SQL optimization这一步的。所以很多时候就没有这个optimization这一步,所以 depend on this noSQL,因为 noSQL本身自己差距很大好,然后optimizer做完了以后会产生一个query plan然后 query plan 就会交给这个 storage engine了Storage engine 里面呢 它其实是一个 file system 它有 transaction manager 就表示这个同时 manage 多少个 transaction 有一个 buffer manager 这个 buffer manager 就是表示是这个file底层,比如说哪些pages是Dirty换过来,哪一些是在BufferPool里面,它会根据你Modify的东西,然后把它同时写进去。然后除了这个以外,就是SQL和Filesystem有不同一点的,除了这个SQL Optimizer以外,Core Execution Plan以外。还有一个不同点是SQL会加Log,会ManagerTransaction,就是你所有写东西都会在Log里面有,会有一些Transaction的东西。帮你manage更好的transaction,这个不懂无所谓,这个你要看的话,它能整整讲好几百小时的课,这个不懂无所谓,就是现在有概念,就是SQL其实是一个非常complex的东西。 [15:00] 它下面这块是这个Storage Engine 是Engineering非常high位的这块有很多优化Engineering的海门,你可以到时候如果有空的话,我们可以看一下它怎么样design,怎么样manage这个buffer,怎么样manage这个transaction,怎么样manage这个叫riderheadlog,其实有很多复杂的地方。但是它真正的这个research复杂的地方,其实这个SQL Optimizer这块,这个其实是花了很多PhD在上面优化的。那么如果我们对比relational database和document data model的话它其实single record performance是没有fundamental difference,就不会差一个数量级的但是,我们说这个Document Database,比如说,我们讲这两个区别如果你是读一个这个配置这两个performance有什么区别呢?这个performance区别就是这个只要读一个record就行了,这个Mongo,就是这个Document Database,只要读一个record就行了。但是如果你用Sycle的话,你只是读了一个,两个,三个,四个,五个,六个,就是读了六个table里面的若干record。这就是它的区别,就是它把这个七八个读变成了一个读,在这个Document Database 里面,这就是它这个优化的地方。但是它这个单个Record的Performance是没有区别,因为SQL优化过很多很多次。所以这个SQL的performance其实是非常不错的,它那个不管是join什么,其实从SQL来说的performance是非常不错的。所以你如果看Money to One, Money to Mining relationship 的话其实本质上两个没有区别,甚至这个Document Database效果要更差一点,特别是many to many,因为Document Database SQL是读Foreign Key,它其实那个manyMapping,它那个4k的那个Join是在in-memory做的,它效率很高,但是你在Document database里面,你实际上Document Reference,你是这样要多一个Round Trip,你是要去,我先读这个table,是吧,我先读这个Document,我读了这个Document里面有个Document Reference,我再去把那个Reference的Data再读出来。如果你用Document,就是如果用MongoDB的Client的话,它可以把这两个变成一个,就是它可以做一些优化,在Client的端进行优化,变成一次读,但是它还是总体来说,它Performance还是,就是它这个Reference的Performance,Document Reference Performance,还是比Joy要稍微差一点。但是Document Data Model是比较schema flexible 的。它就是从某种意义上来说,它的 performance 呢,因为它所有的 data record 在一起,它那个 local 。locality,locality,locality,locality,就是它data record全放在一起,所以你逐一个record的时候你实际上data全组在一起就组一个地方而不是读多个地方,像我们之前看的那个linkin的case,如果你读sicle的话,你实际上读从七个table里面读七八个records,你实际上这个records读起来是非常分散的,但是如果用document database,你只要读一个地方就可以了。并且这个SQL相对来说呢更support是join,比如说你这个many to many,这个可以join, normalize一下,many to many support非常好。其次有一点非常不一样的地方,这个也是作者自己命名的,这个书里面做自己命名,我觉得说的蛮有道理,就是Sycle和MongoDB,Sycle和DocumentDB,这个schema其实都是有的,但是它Infos的时间不一样。在 SQL 里面是你叫 Schema on Write 意思就是当你写进这个 database 的时候,你其实就对了 schema 进行了要求比如说你这样写的时候,他会对已有的这个schema验证一下,你是不是这个record type对不对?然后你这个这个Record Type 对不对 然后其次的话会看你这个record会不会满足这个要求,它实际上在write的时候进行了schema的验证但是你如果不符合他的schema,这个write会被reject但是这个Mongo其实是schema on read,就是你写的时候你不管它什么都可以写,你只要是个json就可以写,但是你在读的时候你expect他有个film,有个column,比如说我们之前讲那个crawler,这个叫smartcrawler,能detect automatic change,是吧?比如说你要crawl这个news website,你还是expect他有一个news title啊,或者news author,expect他有这个有这个column的,所以你可能读的时候,就是你写的时候就写个json,但你读的时候你express它有个title这个column,如果没有这个column的话,你可能会需要处理它的exception 的情况。所以这个就是sicle or no-sicle的一个显著区别就是你到底是这个schema你是在写的时候infos还是你在读的时候infos谢谢大家然后这是那一点就是这个schema读的时候还写的时候Inforce不同的用处就是你它这块给了一个use case,比如说你之前的user name是一个name,但是你想把这个table分成两个就是新建一个正在user name,exact first name,是吧,你这些是吧, [20:00] 安全的时候是,就是first name, 空格, last name, 现在你希望 extract first name那如果是这个SQL的话,你实际上就需要update你的SQL schema了你需要的什么呢,就是你需要update user,把这个first name 呢,就是从那个name里面读取出来然后变成一个单独的column,这是非常static type,或者是schema on write的这种就是单独提取出来一个这个,但是这块我想提一下MySQL其实现在非常灵活了MySQL你可以定义一个Virtual Column就是什么叫Virtual Column这个Column其实是理论上在底下是不存在的但是你读的时候可以进行这个Column可以对另一个Column进行一系列运算然后计算出来所以这个你可以没有一个Column叫First Name但是你First Name可以变成一个Virtual Column它有个词我忘了具体叫什么了就是Virtual Column就是你可以对这个Full Name进行运算然后运算,然后把这个first name提起出来然后你读SQL的时候,就是select的时候,你可以直接select first name这个MySQL就是5.7版本之后是支持的所以这个也不一定要,也不一定需要做这个,就是真的把它update一个first name然后另一点来说,当你进行这个操作的时候Postgres效率是比较高,当你进行update column或altered column的时候Postgres效率比较高的,它实际上就是在原有的table上加一个column但MySQL是当你进行altered table的时候,它其实会把整个tablecopy一下所以如果你有一个非常大的user table,比如说几个billion row的话MySQL是非常非常满的,可能会take hours and days去copy这个table,所以在outretable,在MySQL里面要做起来非常小心但是一般的其他的database是不会有这个问题的我就这块说两点,一个就是如果是这种简单的运算,你其实可以不需要加一个真正的column你可以加一个你search一下MySQL的文档,它其实可以加一个virtual column这个column其实在database程度是不存在的,但是你从MySQL读的时候,你可以select first name,就是背上另一个column进行运算提取出来但是如果在mongo里面mongo里面的话,其实就相对来说不需要了你不需要对data进行任何运算你在写的时候,你在读的时候,你在读这个first name的时候你可以说它有没有first name,如果没有first name的话把first name从这个name里面提起出来,然后再写到database里面再写到database里面这个其实是有一个cavet,是有个陷阱的covet的陷阱是什么呢你在读的时候其实进行了database写的操作就是你在读一个database进行database写的操作这总体来说一般来说不是一个不是一个好的一个design就是你get一个table,get一个数据结果对于database写的操作一般来说不是一个好的但是mongo在这块已经就是在mongo里面变成一个约定书层的东西好,我们这块有两个问题啊然后说,一个是Links问的说写的时候如果json document写的时候如果document-based或者json,json里面没有的field有没有的field可以吗?我不问这个问题啊Links能说一下吗?就是这样的,就是那个,能听到吗?我能听到比如说我现在已经有的一个Document然后它的JSON比如说就是Name然后还有一个Job然后还有一个就是比如说Adress然后我现在有一个新的一个写的Request过来了然后这个写的时候这个时候我的JSON里有一个新的Field它之前没有存在过比如说它现在叫FirstName它写入的话会被接受吗当然会接受你提到了InforceOnRate它是InforceOnRate就是它写的时候任何都可以接受但是它读的时候你要是没有FirstName的话你可能需要额外的处理任何Json都可以写这是Document Database就是他当时就是我写过一些Mongos我其实上学的时候被Mongos骗过写过很多Mongos但是你知道在业界写过Mongos的人基本上都讨厌Mongos但是他那个Signin上学的时候我上学的时候他那块Signin特别热也写得比较好被Mongos骗过好另一个question说每次读的时候都做这次提取还是最后还是这么读一次后整个table就Virtual Migrated Forever以后读的就快了你尽管这个问的问题宇全你问的问题应该是mongo吧不是我说是你刚才不是说那个traditional database它会再create一个什么virtual column吗对它那个virtual column是永远不materialize应该是不materialize就是你读的时候 [25:00] 你比如说你读你读假设你读的first name从这个SQL里面读 first name是它是怎么读取呢?它其实是读取fullname,然后进行运算,然后把firstname提取出来,然后给你的。就是它其实是没有这个,我的理解,它其实是没有first name,在database底层里面,其实这个file里面是没有first name,这个column,它是读取full name,读取出来,然后把 first name 提取出来然后给你的 返回你的 所以它跟这个传统呢 full name 这个存在 database 其实是不一样呢然后被芒果骗,Paul说被芒果骗过什么是不太推荐芒果吗?不太推荐芒果。我有很多很多例子,就是我用过,见过很多例子,在不同公司见过很多例子。Mongo的performance很糟糕我举个例子,我不提这个公司是哪个了,因为我其实理论上没在这个公司工作过。它用Mongo存这个context,这个context只有一个边点,然后这个mongo就crash掉了, performance就很糟糕了- 还剩70%的space就不work了 - 对,还剩70%的space就不work了- 还剩30%的space就不work了 - 对,就是capacity只用了70%,这个mongo就不scale了mongo的问题主要是scalability和index的问题你想mongo的话,它其实是你读一个object id,或者document reference读document的gsm它主要是index问题,它没有index,有一些requirements meet不了它有了index造成了更多的问题,并且scalability还是有问题嗯用过Mongo的特别就是你如果做side project或者smart project无所谓,但是这个用到production真的skill出去的时候其实有很多问题包括这个,你其实看这本书,我觉得这个书的观,这个作者的观点,其实有很多地方或是一致的,然后你如果看纸质版和电子版,它电子版还删了一部分,就关于Mongo,说了一道关于Mongo的画画还被删了,我不知道是不是Mongo有人起诉它,但是我看的是纸质版的书,纸质版上有额外的数据,挺有趣的,好,再解释一下每次读都进行了写的操作的Mongo database,Document database,比如说你这块,你这块你实际上是对于first name,优优独播剧场——YoYo Television Series Exclusive就是当你读这个first name的时候,当你改变这个schema,在monger上改变这个schema的时候,你读这个Document First Name的时候,这个Document First Name如果不存在,它没有立马对这个Schema进行改变。当你读这个Document First Timing它不存在的时候,它实际上是把那个Documentsplay出来一个first name然后再写进database所以你其实这个读进行了写的操作,就是有点像http.get实际上有一个database write的操作,这个其实是不是很好的design,不是很好的design,这个是一个小的red flag吧,但是mongol这已经把它做成一个convention了,就是已经这么做,大家都可以接受了。Advantage on schema unread 就是当你读的时候进行schema验证的时候有什么好处呢?就是你比如说你这个data不是相同的structure,比如说我们在crawling的时候每个data的structure都不一样,比如说这个game replay的structure就和news的structure不一样的话,那你肯定希望schema on read,就是你希望更flexible一点。其次的话,如果像这种structure data实际上是被external system比如说你这个比如说在crawler里面,你其实自己也没法对这个structure进行决定,你只能是External system进行决定,那您肯定是希望用这个,就是希望把它变成一个JSON,然后直接存一个JSON就可以了,那存成Document Database是可以的。然后其次的话,其次的话当你有很多不同type的object,比如说每个type是hintergenius,hintergenius是什么意思呢?我后面会有个例子,就是说每一个object type不一样,比如说这个type就是一个newsnews,然后那个type就是一个game replay的话,那你希望就是把它,然后其次,其次你又不能这个create一个什么game replay table,那个create一个news table,你希望把它放在一个object table里的话,那可能用这个schemaonread的就是比较好的approach。谢谢大家这种情况下的话,当你有schema的时候,比如说你是crawler的时候,当你有schema的时候,一旦crawler的website改变schema,整个system就broken了,那还是把它分开好一点,schema是一部分,crawler是一部分,poss是一部分,这样会好一点。其次的话是Data Locality这块,Data Locality这块也有很多很有趣的地方,这什么意思呢?就是当你看这个Data Base Access Pattern的时候,Database access pattern 当你常见的common access pattern data 是不是放在一起的 [30:00] data locality. Data and locality are what's good for? First of all, data you read performance will be higher, is it right? You put data in the performance will be higher. In addition, you have to be found when data is reduced to fragmented, is it right? For example, in Salesforce, I'll use Salesforce as an example. Salesforce is actually a very good example. Salesforce is called multi-tenant environment. What is the meaning? For example, Uber is Salesforce customer. Lift is Salesforce in customer. The record in the database, in the database, is in line by line. User table is the Uber record, the 16th is the Leaf record. This is a very possible way, because it is metadata. In Salesforce, I have a request for a new user, I have a new user. It will first customer logic, like Uber has an additional validation, and it will be done with the company ID. and the database底层 is in the user table in the user table. So Uber customer user. Leafs are the same. Leafs are the same. Leafs will go to verification. But when you write the database, line by line is the same. You can find this problem. You can see your competitor data together. If you're not going to be released, how will it be? This is the first thing. Salesforce has built a Cionala, it was used to use Oracle Database. Cionala is to use a different way to use. It is to use a layer database, appending log, to change B+Tree. It is to do the Oracle. It is to guarantee you Uber data. It is to put in a separate area. Cmog data is close to each other, is to put in one side. increase data locality performance. Google Spanner is similar to it, it is a relational data model but it can also allow you to specify table row, to have a relationship with each other. and put in a parent table. Cassandra is similar to the same. Cassandra is able to create a partition key. Partition key is to solve the data locality. So why do we say schemeless is not good? Schemeless is not going to solve the data locality. Schemeless is to solve the data locality. Schemeless is to solve the UID. You can read a transaction. If your record and UOID are not the same, it is in two shards. You can't do cross-shards. You need to read two things, this is a mess. You can't control your data locality. So in the new Docsdown, it's been taught by Cassandrum. You can specify a partition key. When you want to write a record, it will be in a short period. You can have Application developer can be used to control your business sense. So, Dota locality is a very important thing. And the other thing is the data model. There is a different point. imperative language and declarative language. This is the programming language. It is a different place. Impressive language, like C++, Java, Go, these are imperative language. For example, this example is a simple example. Ciao a l'easys! It's JavaScript, it's JavaScript. And the next one is SQL. SQL is a declarative language. It is a logic. It is a logic. It is like... Relational Algebra is the general expression of the real physics. And you can describe what you need to find. This is different from the different types. Let's see if I can see if I can see if I can see if I can see the different values. First, the difference is declarative language. You can just see what data is like. uh select start from user table, where user ID, where user city is equal to San Francisco, you can just say that. It actually is the system database in the core, optimize your requirement, and then it's the best way to go to the data. And then, you are a separate concern. [35:00] 你把 implementation detail和这个你就是作为Application Developer的需求分开了那这样的话,比如说当它对database的这个 query optimizer 进行优化,甚至全部取代的时候全部换掉的时候,这也是API design的时候,就是常见的,就是你把这个implementation全部换掉了,那你其实对你来说也没有影响,你还可以用同样的一个SQL来使用,因为它这个agreement没有变,就是select user from San Francisco,这个agreement没有变,但是imperative不一样,imperative language是和implementation detail有关的,你比如说这个select的时候,如果你这个implementation换了的话,你其实会影响你的result的,很多时候会影响你的result的,然后其次的话, declarative language 很多时候是不garanty任何 order 的。实际上,这个,像 select SQL,这个 SQL,就是 simple query language,它是非常,它functionality是非常limited的但是它因为 functionality limited 实际上给这个这个platform developer 多了更多的空间去优化,是吧?这个是有更多空间的空间,其实还可以平行执行,parallel execution,因为你比如说你这个table,这个user table is sharded的,你shard的不是根据CTK来shard的,你select user from San Francisco,它有1000个shard的,你可以放在1000个shard同时执行,你的return效果很快的,但是imperative language的话,你可能就需要更多的一个一个shard的去读,你可能需要考虑更多的implementation detail,比如说这个data有没有在这个shard上,你就要考虑这个implementation,但是这个sicle,或者这个declarative language,你不需要考虑这个implementation,是非常好的一个separate concern,separate concern是设计中非常重要的一个理念,如果我把它放到这个设计,我自己理解的设计里面,我可能会把它放到top two,就是最重要的一个理念之一,就是separate concern,你怎么样把一个非常复杂的系统分成不同的层,然后每一层和每一层只相有一种agreement,然后就 separate concern最后一点是MapReducer呢,既不是Declarative Language,也不是一个Imperative Language是Somewhere in between好,这个就imparative和declative我就说完了,我这块再给举个例子吧,如果你之前没有接受过这个programming language课程的话,可能这块有点难理解。嗯我们就举一个常见的例子比如说我们在写HTML和CSS的时候,比如说你HTML可能会定义一个table,这个table叫Whatever table,user table然后你对这个table呢可以进行class进行定义对吧比如说这个class是说这个class是user的时候我把它染成绿色的这是 declarative language,然后你可以把这个比如说这个贴宝检上绿色的,然后你在这个CSS里面,可以说我现在不喜欢绿色,我改成染成红色的,那所有的这个user table在你改成红色的之后,css瞬间就把它全部渲染成红色的了。只要是class属于user table你的这个table就是红色的,就是在CSS里更改的时候。但如果你是imperative language,如果你用java script去展示的话,你可能需要一个follow loop去run这个,然后把一个一个table,改成红色的,那你比如说你在这个CSS里面,当然CSS本身就是属于declarity文的language的一部分了,你如果想展示的话,你需要这个javascript重新再run一点你需要去重新trigger这个JavaScory重新run一遍才能把所有的推包染成红色的这样的话其实你就是和implementation detail相关嘛这个例子其实在这本书上也挤了大家如果有兴趣的话可以再读一下有什么问题吗?有什么question?好,我们进会看这个Graph Theta Model很多人很喜欢Graph Data Model,但是如果你真正读到它的details的话,你可能对它喜欢不喜欢就打一个问号了。Graph Data Model里面它是有两种,一个是Vertix就是nodes,一个叫relationship,就是nodes的连接就是这个age,一个是点一个是线常见的Graph Database,Graph Data Model或者Graph Database能解决的问题,比如说出色Graph,比如说Facebook就是Social Graph,比如说Web Graph,你说的Web Page,其实每个Web Page就是一个Node,然后你的superlink hyperlink其实就是这个hs比如说这个road和network,realway network它那个age就表示roads,然后它那个intersection就是node常见的这个graph 比如说这个car navigation system 你要去找这个最短路径 [40:00] It. weighted. Graph Database, the Innovated Graph Database. And then, for example, Google PageRank You can use this web graph to do the popularity of the web page. Cool, I'm not saying that. Wow. This is a graph. How? This is from Facebook's paper. If you look at Facebook, Alice in Golden Gate Bridge and Bob in Golden Gate Bridge Cathy said, wish we were there as well, David likes this. This is a very complicated graph. It's a very complicated graph. What is the user? Alice. And the user is friend. Bob and Cassie are friends Cassie and David are friend, which is user node, which is a user node. And they are relationship, which is friend. And then there is another node called location, the Golden Gate Bridge. This is a Golden Gate Bridge name and location. Then there is a check-in action, the action is made by node. The action is who check-in? It's Alice's check-in action. And then tag-in Bob. So this check-in is where is check-in? This check-in is in Golden Gate Bridge check-in. 其次呢? The last thing is, the check-in is comments. This message is a check-in type message. Check-in is comments. Comments are texts. It's who is author and who is like. The whole situation is in the situation. This is a hint-genius graph. Facebook How to break down and become a social network. And then... And this is what I proposed to our team. I was in safety team. I proposed to our team. proposed the one. Paul said, "Facebook use graph data model to use Social Graph is using Graph Data Model, but the underlying is using Shared SQL. The tool is used by multi-leader shredded SQL. It is similar to our schema list. The tool is a scheme-less design, which is a scheme-less design. So it actually uses mycd code. It uses mycd code. This is the Uber safety code. done. We are not in this case, we are in a bit of trash. I won't say that. But actually Uber safety case can also use this. For example, you can use user Alice There are Bob and Cassie. And then Alice and Bob have a co-write, and shared write. and there was an inter-personal conflict, and there was a conflict. Then he filed a ticket, the Blitz Ticket is Blitz Contact Ticket, which is Uber's own ticket support system. When you're not satisfied, you're going to report it, it will be a please ticket. And then, if it's serious incident, it will be zero tickets. So Cassie is confirmed in zero tickets. And David will handle this incident. So it can also be a For example, in this case, there are four users, right? Alice, Bob, Cassie and David. David is Uber's employees. And Alice reports an incident. And she associates in this trip. Order and Job. She associates in this Uber ride. And Cassie is the driver. She is a service trip. She is with David. conversation, right? And this is an incident. The incident is with the order and drop, and the trip is connected. and then connect with the tickets. It can be designed to be like this. The data will be more similar to Facebook. You can do graph traversal, work on this graph. You can set up a visualized system. get the same information about Alice, you can read the profile, roles, activity, incidents, trips and orders. You can read Alice's information. For example, if you want to read the incident information, you can read the incident information from who has been involved, who has been involved, who has been involved, who has been involved, who has been involved, and who has been involved. And there are some associated trips, the trip is telemetry data. [45:00] 就是这个GPS data就是你这个在每一分每一秒在哪一个地方,你的速度是多少,加速度是多少,然后这个加速度方向是什么叫Telemetry data,就是比如说你开车的时候就有很多Telemetry data,就是你每一分每一秒在GPS哪一个位置每一分每秒在GPNS哪一个位置速度是多少,加速度多少,加速度方向是多少,这个就是tiny metric data然后你还可以比如说你还可以对David的这个information有更多的信息,response time,ticket work on,就是他work on哪些tickets,说的conversation,rating你可以用非常generic的entity relationship 实践的safety 结果但是读懂原因没有时间,主要的原因是没有时间,没有时间是时间。这是我觉得我最近比较 push 想做的一件事情,但是没有时间是时间,因为有更多 urgent project好,Graph Database 呢,如果你只是说这种,其实你是没有任何 advantage,就是说你其实完全可以用 SQL 存。你完全可以用 SQL 出来,你只是读这种简单的这种,右面列出来所有例子我右面列出来的这个所有例子就是比如说不管你是读一个人做事件、做事件、做事件、做事件、做事件、做事件、其实你用sql存是完全没有performance difference的,完全没有performance difference的。但是有一种Case,这个Graph Database是有它的自己的优势的就比如说这个例子在这个例子里面,我们这块有一个非常复杂的,就是Nested Relationship,有一个continent,叫北美,是吧,美国在北美里面。然后爱德华在美国里面,然后Lucy是在爱德华出生的但是他生活在伦敦,然后他和Alan结婚了他和Ilan同时结婚了,生活在伦敦,伦敦是英国的,英国在这个英国,英国在这个UK里面,是吧?然后这个在Europe里面。然后这个Ilan是在这个另一个地方出生的,他属于France,是吧?这个City我不知道怎么读。Codida,这是在另一个地方,它属于France,是吧?这是一个graph database。如果你想这个Core的时候,就说,比如说,举个例子,你想说,我想找见一个married couple,是吧?在Europe生活,其中有一方来自于北美,另一方出生在 Europe那这样的话你用Sycle表达非常非常复杂但是你用Graph Database就非常非常容易我再重复一下这个问题,这个Corey可以思考一下,对于SQL写的比较多的,或者有点建议可以思考一下你想再写一个sql query说我想找一个married couple然后他们现在生活在Europe,但是其中一方出生在北美,另一方出生在Europe。这个 query 是用 SQL 写起来是非常复杂的,所以就用 Graph Database。常见的 Graph Database 也有两种实践,一种叫 Property Graph Model,一种叫 Triple Star。Triple Star实践方式,Triple Star有点像那个三段逻辑论,我这块没有提,只提了 Neo4j 这个,因为 Neo4j 算是 Graph Database 里面,现在商业化和 support 相对比较最好的一个地方。它是推荐一个叫 Index Free 的一个 Graph Database。如果我们用relational data model,就是说像之前提的那个Facebook Social Graph,用relational data model来实践它的话,要怎么实践?你首先要提一个Vertix,是吧?Vertix就是这个点,你可能有个Vertix ID,然后你有一些property 就是vertix然后你要写一个Edge,就是把这个点连起来的线是吧,然后你Edge有ID,然后你Edge的话连Vertex它的tail vertex是什么,然后head vertex是什么,然后你这个edges可能要label,是吧?你比如说这个label可能是boeing这个label,可能是living这个label,可能是marry这个label,是吧?不同label,然后你这个edges有一些property是吧?就是就比如说这个Boeing的话可能有些额外的Profit,然后这样的话你可能需要两个Index一个是index 是什么呢,index 是这个可能从一个vertix的,它那个就是箭头指向的起始方来search你也可以从一个箭头的中指方来search 是吧像这种情况下的话,就是这个箭头的这个tail index起来,然后箭头的head index起来这就是你可以这样来model这个graph,就是有node和edges,有vertix和edges,然后你index的时候把这个node index起来。那如果你要想,sorry,如果你要写,就是这个我们刚才说的这个逻辑的话就是说你看找这个人 [50:00] 其实它这个逻辑还更简单一点找生活在欧洲出生在北美的人然后找见它的property name但是你join的时候就很复杂了你首先得我们看一下这都是找见什么意思第一个cord的意思是说我们首先得找见北美这个node然后呢找见所有的location在美国的location对吧找见所有的在美国的location所以第一个就是找见所有美国的location第二个相似的是找见所有的Europe的location第三个是什么呢第三个是所有的找见在美国出生的人然后第四个呢是找见所有在Europe生活的人然后这个Jone一下然后把name读出来这样的话就找见什么呢出生在美国但是生活在欧洲的人我们再重复一下这个逻辑有点复杂你要对SQL不说的话可能想一会儿它还是recursive的多整个下面是recursive的多首先早见说recursive的早见说美国的地儿就是属于美国的市啊县啊这个地儿然后再早见说属于欧洲的地儿然后再早见所有出生在美国的人然后再找见所有生活在欧洲的人然后把它join一下找出来这个在这个database optimize的时候它可能会找见一个最小的site然后对这个site来说进行一个mapping就是这个的复杂度是什么呢这个复杂度是最小的site然后把这个加起来就是不会是层的关系它是一个最小的site加的关系但是这个最小的side和大小也相关,这里面最小的side是什么呢?最小的side,还挺复杂的不是很好优化,我自己直观看也不是很好优化如果我们看一下这个graph database的话graph database,你database creation的时候就可以这样子说我created一个,北美是location,然后它的content是什么,然后它name是北美然后这个create一个create一个usa作为location然后它name是什么,country是什么成为一个爱德华作为location它type是state是吧这个第一个type是大陆,第二个type是country第三个type是state然后create一个lucid所以爱德华是在美国里面的,是在北美大陆上的luci是出生在爱德华的那这样的话你就你相当于是visualize,你体现了Lucy出生在美国的爱德华然后但是生活在这个Europe然后你写matching card的时候这块matching card可能有七号,但是这块matching card是三号怎么呢?同一个person他出生在一个地方然后他recursively within us他生活在一个地方recursively within us然后把这个人select出来就可以了所以他这个复杂度可以看见非常直观就是在这个graph这个里面非常直观所以在选的时候当然就是之前说过就是没有一个好的一个solution是one size fits all根据你的use case来你什么时候选graph database当你这个是graph data model的时候如果你很多的use case是friends or friends or friends这种use case那你可能需要用graph database注意一下就是friends or friends or friends这种relationship可能用graph database如果不是这样的就是简单的一层的关系的话其实用MySQL是可以用SQL database是挺好的一个实践这个你像Facebook用的SQL,Facebook这个social network用的SQL然后这个样子然后雨泉问说有什么有名的product用在graph database,我还真不知道您可以看一下Neo4G,Neo4G肯定会把它写到这个商业宣传上,你search了Neo4G,有谁叫Neo4G把它变成这个这个上面我还真不知道谁在用就是特别大的有名的product我还真没有读到过但是是有用的因为neo4G它是有自己的它有个叫index-free graph working我记得叫index-free graph working就是它还是有用的neo4G 你就search你测试graph databaseneo4G算是最大的graph database然后好问说customer 2.0里面association底层是用graph database你把customer 2.0想的太好了Customer 2.0他的这个physical data model是没有换过的我读过他那个70页的那个那个ERD的他的Design Dock的他physical data model是没有换过的他是用scheme类似里面那个最原始的那个physical data model的他只是把自己的logical data model换掉了什么意思就是说你如果读 [55:00] 旧的Customer to Panel是Uber内部的一个管user的Data Model Concept它底层的这个DataModel是没有换过的,它还是用Schemeless旧的DataModel,它只是把这个从Physical Data Model读出来的Data,然后转化成Logical Data Model那块换了就把Logical那层换了,什么意思呢?如果你用,不管你是用Customer,就是用之前的Populus,就是旧的那个Customer方式,还是用Customer2.0你从这个database的角度来讲都没有变过,就是你始终还是读的同一个database,还是读的同一个table。但是你读完以后,相当于是你在这个mapper那层角度,我换了一下,以Mapper或API的角度换了一下,你之前是Rider, Driver, Partner, relationship,现在你就变成了User,这个logical data model了,所以这个你把customer图坏的时候想太好了但是这个它的设计其实是有道理的因为这个data migration做起来非常痛苦并且很容易出错啊这几个billion user的Migration做起来非常痛苦,非常容易出错。不Migration,用一样的 physical data model 本质上是没有问题的,我觉得从 engineering 设计角度来说是没有问题的。但是我个人觉得他的那个business signing point,就是比他那个带来的fundamental revolutionary change 要多,没有任何有趣的地方,我个人读了一下那个一二点。最后一点就是datalog,这个是declarative logical programming language,我之前写过一些prolog的东西,就是之前上programming language的时候写过一些prolog的东西。这个prolog就是你可以用prolog来实现就是Dequietive Logic Language,就是这个relationship不仅可以用MySQL实践,可以用Graph Database这个实践,也可以用Prolog实践,Prolog实践也是非常好奇的,它更像一种recursive的这种逻辑,比如说我们来看这种,他先升称了三个node,第一个node就是北美,前面这个是ID,后面是text,然后type它属于什么type然后又双双了美国,前面是ID,后面是Tax然后泰国的话属于country,然后说美国在北美之内然后这个 name type和 vz 都是 keyword然后这个紫色都是KWord的然后生存了一个爱德华,然后它是一个state,爱德华在美国之内,然后又定义了一个Lucy,Lucy是出生在爱德华的。然后它这个relationship就很有趣了,它是一个recursive relationship,就这块比如说它就定义入嘛,就是这个prologue的定义入。第一个入就是说,这个location呢,是第一个入是,我看第一个入试谢谢大家第一个书相当于是recursion的第一层,就是如果是fe return 什么这种感觉。第二个Rule是相当于是什么呢?就是你location可能在Vzinc,就是Vzinc这个recursion的定义啊,就是你这个location可能在什么location里面。比如说这个里面,你套用爱德华和这个爱德华和这个North America的话,你可以得出这个爱德华是在North America里面的。为什么呢?因为这个爱德华是在UIC里面的然后USA是在North America里面,所以第二个是Recursive这个关系,第一个是Recursive中止条件,第二个是Recursive关系,然后你取的结果呢就是描述型的吗描述你想要什么你要找一个person是吧描述你要找一个person,他出生在一个bone location然后呢,然后这个Bone location呢,是它生活在这个Bone location呢,是在这个Bone in这个里面。出生在一个Bone location,就是Recursor 出生在一个Bone location,然后他生活在一个Living location,Living location,Recursive 在那个地方,然后当你读的时候,你就可以把它变成一个Descriptive language,就是你找一个人,who,就是你要找的人,然后他出生在美国,生活在Europe这个Bond Location就是美国所以你叫recursively找建一个city,它出生的city可能就是recursively在美国里面,然后它这个生活的cursively在Europe里面。你要找见这么一个人第一次写的时候,这个mind side可能会稍微不一致,其实出版的是requestion的mind side好Prolog,Prolog用的地方还挺有趣的,Prolog有点像那种传统的AI,如果你在特别复杂的一个onstructured data 里面 [60:00] feel miserably. You have a failure to be very much But if you are in a虚拟 environment, and data is structured expected, Prolog performance is pretty good. You can do a small experiment in a small experiment. The things are regulated and expected. Prolog performance is pretty good. It's a very interesting thing. This is the last one. I'm not going to talk about database. Database is very long. This is very important. There are 24 hours later. Um, DataModel just talked about it. Do you have any questions? Thank you. No, that's just like this. I'm going to say that next week, I'll talk about YouTube, design YouTube. I won't talk about the following week, because I've been a lot of people. I'm not going to talk about it. Next week, I'll go to the next week and the next week. That's probably the plan. Thank you. Hello, I'm going to go to the hospital.