日历系统设计弃用应用层锁 谷歌面试官拆解数据库并发机制

[Chinese] Mock interview: Design calendar — Scott Shi

Scott Shi

应用层内存队列与外部锁在节点宕机时必然引发重叠预订漏洞,会议室高频争抢必须依托数据库行级锁与快照隔离下沉裁决。

AI-assisted summary of [Chinese] Mock interview: Design calendar — original source. Check the original for full context.

Source published:

Key ideas

  • 系统设计切忌“API 驱动选型”的积木误区:组件的 API 只是外形,其存储引擎、持久化和并发模型等底层“质地”才是承重关键,OLTP 系统中 90% 以上的时延瓶颈在磁盘 I/O 与数据库检索而非内存计算。 (5:00)
  • 设计 Google 日历的核心技术硬骨头在于会议室预订的并发冲突控制;应用层必须保持无状态,严禁在应用内存中自建队列或外部有状态锁,否则在节点故障容灾切换时必然引发并发重叠导致重复预订(Double Booking)。 (70:00)
  • 对于读远大于写(10:1 以上)的日历系统,两阶段锁(2PL)会导致读写互斥阻塞而拖垮吞吐量,工业界更推荐采用快照隔离(MVCC)实现非阻塞读,并在写时配合数据库索引排他锁(如 SELECT FOR UPDATE)或异步队列乐观快速失败。 (80:00)
  • 系统设计面试中基础 CRUD 与数据模型应在 15-20 分钟内快速带过,真正的区分度在于能否清晰阐明数据库隔离级别、锁粒度以及对时钟漂移和写偏斜等边缘失效模式的系统性推演。 (89:41)

Detailed notes

## Background 该视频由 YouTube 频道主 Scott Shi 于 2020 年 12 月 22 日发布。视频内容是一场围绕“设计谷歌日历(Design Google Calendar)”展开的系统设计模拟面试实录,并在简介中附带了面试对应的 Google Docs 协作设计文档。

## TL;DR 视频首先通过复盘候选人既有的 Instagram 设计,借由“搭积木与材质”的隐喻破除“看 API 外形驱动架构”的误区,指出 Redis 等内存组件与底层写优化数据库的本质差异。随后正式开考“设计 Google Calendar”,在厘清功能边界与完成百亿级容量估算后,全场重心聚焦于“实体会议室并发预订冲突”这一核心难题。Scott 针对候选人提出的全库排队、单机内存队列等应用层锁方案层层施压,并通过节点宕机切换导致重复预订的边缘案例,彻底阐明了为何应用层必须保持无状态,且必须依赖数据库底层的快照隔离(MVCC)与索引排他锁来解决写偏斜;最后结合工业界实践给出了系统设计面试的高阶通关指南。

## Atmosphere & style 全片洋溢着硬核实战、直击痛点且循循善诱的工程思辨氛围。Scott 谈吐随和风趣但切中要害,用“旧金山到土星”、“塑料做承重梁”等生动隐喻瞬间戳穿浮于表面的架构幻想,并频繁构造极具破坏力的分布式故障场景进行极限推演。候选人经历了从最初依赖应用层硬写逻辑的局促,到被反例拆解后的震惊,再到理解数据库底层锁演进史后的豁然开朗,给观众带来极其过瘾且具备强烈认同感的沉浸式技术洗礼。

## Bulletin - [05:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=300s) 系统设计切忌“API 驱动选型”的积木误区:组件的 API 只是外形,其存储引擎、持久化和并发模型等底层“质地”才是承重关键,OLTP 系统中 90% 以上的时延瓶颈在磁盘 I/O 与数据库检索而非内存计算。 - [01:10:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=4200s) 设计 Google 日历的核心技术硬骨头在于会议室预订的并发冲突控制;应用层必须保持无状态,严禁在应用内存中自建队列或外部有状态锁,否则在节点故障容灾切换时必然引发并发重叠导致重复预订(Double Booking)。 - [01:20:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=4800s) 对于读远大于写(10:1 以上)的日历系统,两阶段锁(2PL)会导致读写互斥阻塞而拖垮吞吐量,工业界更推荐采用快照隔离(MVCC)实现非阻塞读,并在写时配合数据库索引排他锁(如 SELECT FOR UPDATE)或异步队列乐观快速失败。 - [01:29:41](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=5381s) 系统设计面试中基础 CRUD 与数据模型应在 15-20 分钟内快速带过,真正的区分度在于能否清晰阐明数据库隔离级别、锁粒度以及对时钟漂移和写偏斜等边缘失效模式的系统性推演。

## Key takeaways - [05:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=300s) “积木理论”与 API 驱动设计误区:API 是积木的形状,底层存储引擎与持久化才是材质,不能因为 Redis 提供 List 操作就将其作为 Feed 流写密集场景的承重柱,后者首选 Cassandra 或 DynamoDB。 - [05:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=300s) OLTP 系统的真实性能瓶颈在于磁盘 I/O 与数据库查找,通常占总请求时延的 90% 以上,盲目优化纯内存中的归并或排序算法往往只作用在不到 1% 的非关键路径上。 - [10:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=600s) 慎防厂商销售宣传:Elasticsearch 本质是辅助二级索引,不能充当唯一可信数据源(Source of Truth);Kafka 等流系统也不能随意当做通用数据库使用。 - [15:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=900s) Redis 写入建议控制在 10 KB 至 100 KB 以内,避免大 Value 阻塞单线程事件循环;其固定 16384 个哈希槽将逻辑分区与物理节点解耦,扩缩容时只需迁移槽位归属。 - [20:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=1200s) 系统设计面试的四大考察维度:候选人过往工程背景、问题拆解的逻辑推演表达能力(Articulation)、分布式与并发底层知识储备,以及实际工程架构取舍(Trade-off)的成熟度。 - [33:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=1980s) Google 日历容量估算:按 10 亿用户、单事件 10 KB 计算,保存 5 年历史数据约需 15 PB 到 18 PB 存储;日历读写比约为 10:1,写 QPS 约 1K~5K,读 QPS 约 20K 左右。 - [44:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=2640s) 全局消息队列或全局数据库布尔锁会导致系统吞吐退化为全局单线程串行处理,并发控制必须精细化到特定资源粒度(如按单个物理 Room)。 - [53:15](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=3195s) 跨节点无法依赖本地时钟排序,分布式系统中严重的“时钟漂移(Clock Drift)”会导致依赖服务器时间戳仲裁先后顺序的方案彻底失效。 - [53:15](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=3195s) 按 Room ID 进行 Hash 分区路由虽能让单房间请求在单机内存队列内串行化,但会破坏就近接入策略,且单点节点崩溃会导致内存队列中未落盘请求全部丢失。 - [01:10:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=4200s) 应用层外部锁存在致命缺陷:主节点持锁写入数据库期间若突发崩溃,备用节点快速容灾接管后因缺失锁状态会放行后续请求,导致数据库层事务并发重叠与双重预订。 - [01:15:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=4500s) 应用服务器必须保持无状态(Stateless)以便水平伸缩,并发冲突控制应下沉至经过数十年工业检验的数据库底层成熟机制(如行锁、索引锁)。 - [01:20:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=4800s) 两阶段锁(2PL)的核心代价在于读写相互阻塞,在日历这种读流量远大于写流量的系统中极易造成系统性能崩溃,必须引入快照隔离(MVCC)支持非阻塞并发读取。 - [01:20:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=4800s) 日历周期性会议冲突策略:如面对每周例会预订,Google Calendar 采取首周无冲突即建会,后续冲突周次通过发送拒绝邮件(Partial Failure)通知用户的工程折中方式。 - [01:24:43](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=5083s) 异步队列与乐观锁方案:如 LinkedIn/Outlook 实践中将会议室预订请求发往以 Room ID 为 Partition Key 的 Kafka 队列,先到先得写入落库,后续冲突请求走快速失败(Fail-fast)拒绝处理。 - [01:29:41](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=5381s) 面试中提及“数据库锁”不能仅停留在名词本身,必须清晰阐明具体隔离级别、是否支持非阻塞读、以及具体在哪张表的哪个索引字段上加锁。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=0s) 开场介绍与候选人的 Instagram 设计分享

本次模拟面试的候选人是拥有三年工作经验的刘瑞。他提到自己本科学的是 IT,后来转行写代码,平时没有系统学习过算法与数据结构,基本都是在实际工作中逐渐积累的。他此前看了公开课受到启发,希望通过这次模拟面试让主持人 Scott 帮忙找出深层次的问题。刘瑞透露自己在年初(二三月份左右)面试 Uber 时遇到系统设计题表现很不理想,当时稀里糊涂设计了一个“奇奇怪怪”的架构;今天他特意带来了自己之前做的一份类似 Instagram 的设计方案,希望能先接受 Scott 的检验与指点。

Scott 笑着回应称大家直接叫他 Scott 就好,叫“老师”听着不习惯,因为自己“平时比较不正经,很难蹭得上老师的称号”。刘瑞开启共享屏幕展示他的设计方案:他利用 Redis 支持 Linked List(链表)这一数据结构的特性来组织推文。在他的设想中,当用户 A 发布内容(Post)时,系统就用一个 Linked List 依序存入;例如先发了 Post 2(昨天),再发了 Post 1(今天),新发内容直接追加到链表头部或尾部,天然满足按时间逆序排列的要求。当用户 B 想拉取关注者的动态流时,系统只需对多个用户的链表执行一次 Merge K-Sorted Arrays(合并 K 个有序数组),取前 10 条数据直接返回给客户端。

- 候选人背景:三年工作经验,IT 专业转码,算法与系统设计基础在工作中自学。 - Uber 面试经历:曾因思路奇特、未掌握系统设计要领导致面试表现不佳。 - 候选人的 Instagram 设计核心思路:基于 Redis 的 Linked List 存储单用户的帖子,读取 Feed 时在内存中做类似 Merge K-Sorted Arrays 的合并取前 K 项。

---

[05:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=300s) 破除“API 驱动设计”与积木隐喻

```mermaid flowchart TD accTitle: API与底层质地的系统设计对比 accDescr: 对比错误地根据API外形选型与正确地根据底层存储介质及吞吐特性选型

subgraph WrongWay["错误设计路线:外形匹配"] A["需求:按时间排序的列表"] --> B["看API外形:Redis List 支持链表操作"] B --> C["脆弱系统:单线程写入瓶颈、内存易失性导致丢数据"] end

subgraph CorrectWay["正确设计路线:质地匹配"] D["承重需求:海量数据写入、高可用性、持久化"] --> E["选材质:Cassandra / DynamoDB(金属承重轴)"] E --> F["Partition Key = UserID<br/>Sort Key = Timestamp"] F --> G["高吞吐写入与自动按时间排序"] end ```

听完刘瑞的分享,Scott 直截了当地指出该设计在根本思路上是错误的,并用“搭积木”的经典隐喻进行了深入剖析:系统设计确实像搭积木,积木不仅有形状,更有其内在的“质地”——有些是金属或不锈钢的,有些则是塑料、纸糊甚至是玻璃做的。积木的“形状”相当于系统的 API,而“质地”则是其底层存储引擎、数据持久化与吞吐能力等核心机制。设计系统时,最致命的错误就是“看 API 的形状来做架构”,看到某个系统提供了看似方便友好的 API,就把它放在了承重轴的位置上,结果就是“拿塑料球当承重柱,墙体直接垮掉”。

Scott 指出,Redis 的 List API 虽然表面上契合了按时间追加帖子的形状需求,但用在这里是严重的误判:首先是持久化与可靠性问题,用户刚发了帖子,一旦内存实例故障数据就丢了;其次,Redis 的写入是单线程串行处理的(Serialized Write),针对大吞吐量的发帖场景,其 Write Throughput(写入吞吐量)非常糟糕。工业界(如 Facebook/Instagram)处理这类多帖子用例的标准解法是使用金属质地、专为写入优化的分布式数据库(如 Cassandra 或 DynamoDB),以 `UserID` 为 Partition Key,以 `Post Timestamp` 为 Sort Key,天然且完美地在底层实现按时间排序的高吞吐写入。

此外,Scott 还纠正了一个关于性能瓶颈的常见误区:很多工程师热衷于在内存中优化 List 合并等 CPU 计算逻辑,但实际上,哪怕是复杂度为 $O(N^2)$ 的动态规划算法,现代 CPU 每秒也能轻松完成 $10^9$ 次运算。在线事务处理(OLTP)系统中,最慢、最耗时的部分永远是磁盘 I/O、索引查找和数据库读取,这部分往往占用了整个 API 请求耗时的 90% 以上。花大把精力去省去几毫秒内就能完成的列表排序,优化的只是不到 1% 甚至不到千分之一的非关键路径(Non-critical Path)。

- “积木理论”:API 只是积木的形状,存储引擎的 Durability、Reliability 和并发模型才是积木的材质;绝不能拿塑料做承重梁。 - 真实的性能瓶颈:数据库 Look-up 和 Disk I/O 占请求总时延的 90% 以上,纯内存数据结构的归并计算耗时往往不足 1%。 - Python 与 Go 的性能比照:曾有系统 Profile 显示,在某些极端场景下高开销脚本语言(如 Python)执行时间占总耗时高达 30%,改写为 Go 后语言本身的计算开销迅速降至 1% 以下。

---

[10:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=600s) 选型深度辨析:Elasticsearch、Kafka 与写优化数据库

讨论进一步延伸到其他常见组件的误用上。Scott 提到经常有初学者询问“为什么不能把 Elasticsearch 当作主数据库”。他指出,Elasticsearch 的设计初衷是做辅助检索索引(Secondary Index),其在数据持久性(Durability)和可靠性(Reliability)上的保障远不能满足 Source of Truth(唯一可信数据源)的标准。在标准架构中,主存储必须是关系型数据库、MongoDB 或 Cassandra 等,Elasticsearch 仅作为下游同步数据的辅助检索层。Scott 告诫道,技术市面上经常充斥着各种厂商的公关宣传文章(Sales Articles),比如在 Kafka 大会上厂商宣扬“要把 Kafka 做成通用数据库”,但工业界真正做严肃架构的人绝不会盲信这种宣传。

针对听众提出的“若 Redis 不适合高并发写,那什么系统适合写密集型场景”的疑问,Scott 解释道:Redis 是在单线程内执行命令,适合写入体量极小的数据,写吞吐存在物理上限;若系统核心目标是写优化(Write-heavy),业内首选是 DynamoDB 和 Cassandra。这两者天生基于 LSM-Tree 等机制为写入进行极限优化,在适当配置读写 Quorum 的情况下,单次写入甚至可以在 0.5 毫秒以下完成,比读操作(通常在 1 到 2 毫秒左右)快出数倍乃至一个数量级。

- Elasticsearch 定位:辅助搜索索引,绝不能作为承载业务核心资产的 Source of Truth 主数据库。 - 厂商推销陷阱:警惕各种宣称“把 A 替换为通用 B”的商业宣传(如把消息队列当主库)。 - 写优化数据库的 Benchmark 表现:DynamoDB / Cassandra 的写延迟可压至 0.5 ms 以下,天生面向高吞吐写设计。

---

[15:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=900s) Redis 写入开销、分片机制与 16384 Slots 探讨

现场听众就使用 Redis 时的序列化开销、写入大小以及分布式分片原理展开了深入交流。关于在 Redis 中存取复杂对象的序列化与反序列化代价,Scott 澄清道:这正是 In-memory Database 的核心优势所在,内存数据结构可以直接 Dump,省去了关系型数据库中拆表、打平字段再组装的繁重过程。比起写入磁盘与写入内存之间跨越数量级的巨大鸿沟(如同“旧金山到萨克拉门托”与“地球到土星”的距离差异,至少差 10 到 1000 倍),内存序列化的损耗完全是可以承受的。不过,由于 Redis 是单线程处理单条指令,单次写入的 Value 绝对不能过大(如几兆字节),否则会阻塞后续所有请求;建议控制在几 KB 到几十 KB(比如十几个对象的聚合)的细粒度模型。

随后,现场围绕 AWS ElastiCache 和 Redis Cluster 的分片机制爆发了一场精彩的技术探讨。有听众提出困惑:Redis Cluster 内部有固定的 16384 个 Hash Slots(哈希槽),这些槽位是如何与用户数据及底层服务器节点进行映射的?针对这个问题,讨论明确了分布式系统 Partition 的两种核心分法:

```mermaid flowchart LR accTitle: Redis 槽位路由与集群映射机制 accDescr: 展示从Key通过CRC16计算槽位,再由Routing Layer寻址到具体节点的流程

subgraph DataLayer["数据映射 (逻辑层)"] Key["User ID / Key"] --> CRC["CRC16(Key) % 16384"] CRC --> Slot["Hash Slot (0 ~ 16383)"] end

subgraph RoutingLayer["路由寻址 (物理层)"] Slot --> Router{"集群路由映射<br/>(Cluster State / Etcd / Proxy)"} Router --> Node1["Node A (Slots 0 - 5460)"] Router --> Node2["Node B (Slots 5461 - 10922)"] Router --> Node3["Node C (Slots 10923 - 16383)"] end ```

- 内存与磁盘的速度隐喻:访问内存像开车从旧金山去萨克拉门托(近距离城际车程),写磁盘则像从地球飞往土星,性能差距少则十倍,多则百倍千倍。 - Redis 最佳写入尺寸:避免 Megabyte 级大包,控制在 10 KB ~ 100 KB 以内,防止阻塞单线程执行回路。 - 分布式 Partition 的分类: 1. 固定数量分片(Fixed Partitions):如 Redis 的 16384 个槽位,哈希映射复杂度固定,扩缩容时只需动态迁移槽位在节点之间的归属。 2. 节点比例分片(Proportional to nodes):如 Cassandra 根据物理节点数量动态分配 Token 环范围。 - 寻址三层机制:Key 映射到 Slot/Partition $\rightarrow$ Partition 映射到 Node Name $\rightarrow$ Node Name 经由内部 DNS/注册中心解析为物理 IP 地址。

---

[20:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=1200s) 系统设计的四个考察维度与 Instagram Feed 异步解耦

有听众补充提到,曾见过一种 Instagram Feed 设计:主数据库采用 SQL,为了避免在读取 Newsfeed 时多表 Join 查询导致系统被拖慢,系统采用异步写入架构——当用户发帖后,后台异步任务把每个粉丝需要读取的 Feed 预先写入属于该粉丝的 Redis 缓存中,读取时粉丝直接点查 Redis,即使关注关系发生变化也异步刷新 Redis。Scott 认可了这种异步写扩散与缓存解耦的设计,说明此前讨论 Slack 频道消息时也有类似做法,把 Redis 作为读取缓存层本身没有问题,关键在于“切入的角度是否正确”,而不是一上来不顾写瓶颈直接让 Redis 充当主存。

在正式进入本次题目前,Scott 面对直播间新加入的学员,系统性地重申了系统设计面试(System Design Interview)的四大核心考察维度:

1. **Background(背景经历)**:候选人过去做过什么规模的项目,是否具备独立设计架构的实践经验。 2. **Articulation(表达与切入方式)**:如何陈述问题、拆解逻辑链路,思路是从 A 顺理成章推导到 B 再到 C,还是跳跃混乱;若切入逻辑反常或违背工程常识,面试往往直接被否决。 3. **Knowledge Accumulation(知识积累与深度)**:对底层分布式系统、数据库原理、并发模型的理解扎实度,能否敏锐预判系统在大规模场景下的单点瓶颈与失效模式。 4. **Project Experience(实际工程落地考量)**:在系统取舍(Trade-off)时所展现的技术成熟度。

- 异步写扩散(Fan-out-on-write)机制:在异步任务中将推文推送到关注者的 Timeline 缓存(如 Redis),用高频异步写置换快速的常数级读,是 Feed 流系统的常见模式。 - 系统设计面试的评价重心:绝非死记硬背某种框架,而是在于结构化的表达能力(How to articulate the problem)和合理的推演路径。

---

[25:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=1500s) 正式开考:设计 Google Calendar 需求梳理

闲聊与预热答疑告一段落,模拟面试正式拉开帷幕。Scott 切换至英文环境,抛出了当天的核心设计考题:“Design Google Calendar(设计谷歌日历)”。Scott 特别提示,日历系统的核心概念包含定期重复的周期性日程(Set of frequency)以及事件调度(Schedule the event)。

刘瑞作为候选人开始梳理系统功能需求。由于对系统级要求的边界尚不够完全熟悉,他一边思考一边尝试建立用例清单。他首先指出了两个最直观的基础交互流:其一是用户作为发起人创建日程,并将事件发送给参与者(Send an event to the participants who will join this event);其二是参与者作为接收端客户端(Recipient client),能够准确接收并同步该事件。围绕事件的创建、派发与同步,面试正式进入到核心功能需求的 Clarification(需求澄清)阶段。

- 面试考题正式明确:设计 Google Calendar 系统。 - 面试官提示的核心特征:日历的核心不仅是单次会议,还包括周期性事件(Recurring events/frequency)的管理和冲突调度。 - 候选人提炼的第一阶段用例: 1. 发起方(Host)创建并向参与者分发会议事件; 2. 接收方(Invitee)获取并呈现会议事件。

[30:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=1800s) 功能与非功能需求整理:会议室冲突与强一致性

面试官与候选人(Rui)继续明确 Google Calendar 的需求边界。在基础功能方面,日历支持跨设备同步(例如用户从一台电脑同步到另一台电脑),以及查看好友或同事的可见日程事件(visible events)。除基础日程同步外,另一项核心功能是**为日程添加会议室(Add Room)**,前提是该会议室在目标时间段内处于空闲状态(available)。

```mermaid flowchart TD accTitle: Meeting Room Booking Conflict Scenario accDescr: Shows User A and User B attempting to book the same room simultaneously, causing a race condition without concurrency control.

subgraph Users ["并发请求"] UA["用户 A (User A)"] UB["用户 B (User B)"] end

subgraph Service ["日历与会议室服务"] Check["查询会议室状态 (Check Room 1: Free)"] Lock{"并发一致性控制<br/>(Lock / Isolation)"} Book["预订成功 (Book Room 1)"] end

UA -->|"1. 检查可用性"| Check UB -->|"2. 检查可用性"| Check UA -->|"3. 提交预订"| Lock UB -->|"4. 提交预订"| Lock Lock -->|"成功预订"| Book Lock -.->|"冲突拒绝 (409 Conflict)"| UB ```

关于系统属性,候选人指出系统必须高度可靠且保证数据强一致性(Consistent System)。如果两个用户几乎同时点击预订同一间会议室,系统绝不能将同一间会议室在同一时间段分配给两个人,否则就会出现两人带着完全不同的会议目的走进同一间实体会议室的尴尬冲突。面试官补充道,系统同时还需要具备高可用性(Availability),避免服务长时间无响应。候选人随后将需求规整为两大板块: - **功能需求(Feature Requirements)**:创建与同步事件(Create & Download Event)、跨设备/好友日程查看(Visibility)、为日程预订会议室(Add Room)。 - **非功能需求(System Requirements)**:预订流程保证强一致性(Consistency)、系统高可用性(Availability)、高可靠性(Reliable)。

---

[33:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=1980s) 容量估算:存储容量与读写 QPS 计算

面试官建议以典型 Google 级别规模来进行估算: - **用户规模与行为**:假设拥有 10 亿(1 Billion / $10^9$)活跃用户;平均每人每天创建 1 个事件,候选人提出若算上交互与修改可以按每天 3 次计算,面试官认可此假设。 - **单事件大小**:候选人起初猜测每个事件 200 KB,面试官指出日历事件主要是文本与元数据,200 KB 过于冗余,更合理的均值在 10 KB 左右。 - **存储容量(5 年数据)**:系统若需保留 5 年的历史数据,总存储量计算如下: $$10^9 \text{ users} \times 1 \text{ event/day} \times 365 \text{ days} \times 5 \text{ years} \approx 1.8 \times 10^{12} \text{ events}$$ 按照每个事件约 10 KB 计算,总存储量约为 15 PB 到 18 PB(面试官纠正了候选人初算的 150 PB,指出实际在 15 PB 级别)。 - **QPS 估算与面试技巧**: - **写入 QPS**:平均写入 QPS 约为 1,000(1K QPS),写入峰值(Peak QPS)估算为 2K 至 5K。 - **读取 QPS**:日历系统读取往往具有杠杆效应,不仅自己会反复刷新,还会拉取同事/好友的日历视图。通常读取量级比写入大一个数量级(10x),因此读取 QPS 预估在 20,000(20K QPS)左右。

面试官在此给出面试节奏上的建议:在真正的面试中,面试官往往不会像现在这样主动搭话纠偏,候选人必须独立、自信地推导这套数学计算;而在当前模拟中为了加快进度,可以通过合理的生活常识(如读写比 10:1、均值与峰值比 2:1~5:1)快速定下量级。

---

[39:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=2340s) 并发冲突核心痛点:Read-After-Write 与全表粗粒度锁

在明确单人可以参加多个重叠会议(例如挂着会议去做别的事)、但一间实体会议室绝对不可共享的前提后,讨论进入预订冲突的核心设计。针对普通的事件增删改查,标准的数据库即可满足。整个系统的硬骨头(hard point)在于**会议室的可用性检查与并发占用**。

候选人画出时序图: 1. 用户 A 查询 Room 1,发现 7:00 PM 空闲; 2. 随后用户 B 也查询 Room 1,同样看到 7:00 PM 空闲; 3. 用户 B 抢先一步提交并完成预订; 4. 此时用户 A 紧随其后提交预订 Room 1。若无并发隔离控制,就会出现覆盖写入或重复预订。

候选人提出第一种解决方案:将读与写合并,引入**数据库锁(Database Lock)**。候选人最初的描述是在代码层维护一个布尔变量(`true` 表示锁定,`false` 表示空闲),只要有人占用数据库,其他人就不可访问,实现“只有一个人能在读写期间访问数据库”。

面试官立刻质疑该锁的**粒度(Lock Granularity)**:“你到底在锁什么?你不是在用数据库自带的行级锁,而是自己在外部造了一个布尔变量,那系统竞争的资源粒度到底是什么?”如果只是一个布尔值,意味着全系统每次只能有一笔预订在执行,相当于锁住了整张表甚至整个数据库。

---

[44:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=2640s) 方案探索:消息队列排队 vs 内存集合缓存

面对面试官关于锁粒度的追问,候选人提出用 **消息队列(Message Queue)** 来实现排队加锁: - 当 Scott 和 Rui 在 10 月 4 日晚上 7:00 PM 争抢 Room 1 时,所有预订请求送入 Message Queue。 - Scott 的请求先入队排在第一位,Rui 排在第二位。Scott 率先获得数据库访问权限,完成校验与写入后释放;Rui 随后出队读取数据库,此时发现已被占用,预订失败。

面试官指出:这种方案本质上依然是将系统吞吐退化为全局单线程串行处理。候选人承认“方案一(全库锁/全局排队)非常慢”,并随即提出**方案二(基于内存 Set 的细粒度管理)**: - 在内存中维护一个 Set,里面存储当前被锁定的 `Room ID`。 - 当用户预订 Room 1 时,先在内存 Set 中登记该 Room 1。 - 候选人分析,一般普通公司通常只有约 1,000 间(1K)物理会议室,单台服务器的内存容量完全足以容纳所有会议室状态。

```mermaid flowchart LR accTitle: Solution 1 vs Solution 2 Lock Granularity accDescr: Contrasts coarse-grained queue locking with fine-grained in-memory set locking for room reservations.

subgraph Solution1 ["方案一:全局消息队列串行化"] MQ["消息队列 (Message Queue)<br/>Scott (1st) ➔ Rui (2nd)"] DB1["全局数据库<br/>(串行读写,性能瓶颈大)"] MQ --> DB1 end

subgraph Solution2 ["方案二:基于内存 Set 的细粒度锁"] Req["预订请求 (Room 1, 7PM)"] MemSet["内存 Set<br/>[Room_1, Room_4,...]"] DB2["数据库持久化"] Req -->|"检查并加入 ID"| MemSet MemSet -->|"写入成功"| DB2 end ```

---

[48:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=2880s) 深入推演:节点延迟与队头阻塞边界条件

面试官顺着队列排队方案提出具有挑战性的故障场景: 如果使用排队/加锁机制,排在队首的用户 Scott 请求到达后,遇到了**严重延迟**——无论是客户端到服务器的网络延迟、处理进程卡顿,还是后台节点突然触发了长时间的 **垃圾回收(Garbage Collection, GC pause)**,导致 Scott 的请求停滞了整整一分钟。此时排在后面的请求(如 Rui 的预订)该如何处理?是陷入无限等待的队头阻塞,还是设置超时机制?

候选人开始分阶段拆解延迟到底发生在哪一层: - 如果延迟发生在**客户端到 Web 服务器之间**,那么实际网络先送达的请求(如 Rui)会先进入队列; - 但如果面试官设定的场景是:Scott 的网络请求**已经率先抵达服务器**并占有了排队位置,随后在执行阶段因 GC 或进程挂起而停滞。

讨论由此深入到分布式并发控制中不可避免的超时重试、锁租赁(lease/TTL)与容错恢复机制。

[50:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=3000s) 锁粒度的拆解:从锁全库到每个房间独占内存队列

面试官指出在写入流程中如果出现从 Web 服务器到数据库或缓存的长暂停(Long Pause),会导致事务迟迟无法提交(Commit)。候选人对此提出了超时淘汰策略:如果整个处理流程超过 1 秒,系统就直接将当前请求踢出并触发重试。面试官随即敏锐地指出,如果卡在 1 秒超时,系统的最大吞吐量就会受到严重制约,并追问当前的加锁粒度到底是锁了什么。候选人解释道,之前的方案一相当于锁住了整个数据库,而修改后的方案二则是把锁的粒度细化——每个房间(Room)分配一把独立的锁,每个房间维护属于自己的内存队列(In-memory Queue)。

面试官梳理后认为,方案二在本质上与方案一并无二致,核心逻辑完全一致,只是将并发控制的粒度从“整个数据库级别”缩减到了“单个房间级别”(reduce the granularity to per room)。从房间的视角来看,每个房间都挂着一个专属的内存队列,系统借助这个队列来严格判定谁先来、谁后到。候选人随后提出,也可以通过记录请求到达时打上的时间戳来确定先后顺序。

- **方案一与方案二对比**:方案一是在整个数据库层面排队加锁;方案二则将粒度细化为单房间,每个 Room ID 拥有独立的锁和队列。 - **超时机制**:单次处理如果超过 1 秒阈值即被判为超时并踢出(kick out),释放资源供后续请求处理。

[53:15](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=3195s) 时钟同步陷阱与按 Room ID 固定分区路由

```mermaid flowchart LR accTitle: Room ID Partition Routing accDescr: Requests for Room A are routed through a partition proxy to a single dedicated server hosting Room A in-memory queue.

subgraph Clients["客户端请求"] R1["请求 1 (预订 Room A)"] R2["请求 2 (预订 Room A)"] end

subgraph Routing["网关与路由层"] GW["Consistent Hashing / Proxy<br/>按 Room ID 路由"] end

subgraph Worker["Server 1 (负责 Room A)"] Q["In-Memory Queue<br/>(Room A 专属队列)"] Lock["Room A Lock"] Q --> Lock end

R1 --> GW R2 --> GW GW -->|"固定路由"| Worker ```

当候选人提出“服务器在收到请求的数据包上打上时间戳来排序”时,面试官立即提出了一个经典的分布式系统挑战:“谁来提供这个绝对时间?”面试官设想了一个场景:有两个预订房间 A 的并发请求(Request 1 和 Request 2),在分布式环境下几乎不可能保证这两个请求恰好打在同一台服务器上。如果 Request 1 去了 Server 1,Request 2 去了 Server 2,而不同机器之间常常存在严重的“时钟漂移”(Clock Drift,极端情况下甚至能慢两个小时),这就意味着依赖本地时钟排序会导致某些机器上的请求永远排在后面、永远抢不到房间。

面对时钟漂移的诘问,候选人调整了架构策略:预订同一个房间的请求,必须强制路由到同一台特定的服务器上。面试官顺着这个思路进一步追问容灾与就近接入问题:如果为了保证同一个 Room 总是落在同一台机器,那么无论用户是从中国发起的跨国出差预订,还是在美国本地发起的预订,都无法采用就近接入(Nearest Server)策略,而是必须将所有请求静态哈希(Partition based on Room ID)到同一台固定的服务器上。一旦这台 Server 1 宕机崩溃,其内存中的队列状态将全部丢失,此时只能通过迁移机制把路由切换到备用节点,并要求客户端重新发起重试。

- **时钟漂移(Clock Drift)风险**:多台应用服务器之间无法保证时钟绝对一致,不能依靠机器本地时间戳做跨节点的全局仲裁。 - **Room 分区策略**:系统请求严格按照 Room ID 进行 Hash 分区路由,确保同一物理房间的并发操作在单台机器内串行化。 - **单点宕机处理**:如果负责某房间的服务器崩溃,内存队列中未落盘的请求全部失效,客户端需捕获错误并在新节点上重新发起重试。

[58:45](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=3525s) 写入排队全流程与只读请求的处理

面试官开始帮助候选人把整套方案的写入链路从头到尾清晰梳理出来:首先客户端发起预订请求;网关层根据 Room ID 将请求分区路由(Routing to server partition by Room);对应的服务器接收请求后,将其压入该房间专属的内存队列(In-memory Queue);当轮到该请求处理时,再去锁定数据库中包含该 Room ID 数据的记录并执行写入。

梳理完写链路后,面试官将焦点转移到了读请求(Read Request)上:“在当前的加锁模型下,读请求该怎么处理?用户总得先查到哪些会议室是空闲的,才能决定发起预订吧?”候选人打开共享文档解释道,系统里的读操作分为两类:一类是纯粹的单次查询(Single Read);另一类是预订事务内部紧密绑定的“先读后写”(Read-then-Write bundled operation)。候选人认为,内存中的房间锁只对这种读写绑定的事务生效,单纯的列表查询并不会去抢这个锁。

- **写入处理完整链路**:客户端发起请求 $\to$ 按 Room ID 分布式路由 $\to$ 进入目标节点内存队列 $\to$ 出队并获取房间锁 $\to$ 操作底层数据库。 - **读请求分类**: - **纯读请求(Single Read)**:普通的用户浏览与可用性查询,不参与排队加锁,保证读操作的轻量与响应速度。 - **读写绑定请求(Read-then-Write)**:检查冲突并占座的复合原子操作,必须通过互斥锁进行保护。

[01:02:15](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=3735s) 悲观锁缺陷、写偏斜与两阶段锁(2PL)的权衡

面试官指出,当前的设计本质上是一个非常重的“悲观锁”(Pessimistic Lock)方案。悲观锁的好处在于它确实能彻底避免数据库事务中的写偏斜(Write Skew)和幻读(Phantom Write)等并发异象,但带来的代价和容错隐患极为严重。面试官抛出了连锁异常故障场景:锁是存放在单机内存中的,如果服务器拿到锁后突然 Crash,或者在拿着锁向底层数据库写入时数据库发生超时(DB Timeout)或崩溃,整个锁状态该如何安全释放与恢复?

讨论随即深入到并发控制的底层理论与数据库并发机制的对比。针对会议室预订这种涉及时间区间重叠的场景,常规数据库需要处理实体冲突(Material Conflict)。如果采用传统的两阶段锁(Two-Phase Locking, 2PL),通常需要在索引(Index Lock)或对应房间记录上施加严格的读写锁,而两阶段锁的核心特征是“读写相互阻塞”(Read and Write block each other)——读锁会阻塞写锁,写锁也会阻塞读锁。面试官引导候选人对比思考:在面对写偏斜问题时,纯粹依赖内存队列加上粗暴的悲观锁,虽然在逻辑上简单,但在分布式故障容灾、数据库长耗时与整体系统吞吐效率(Efficiency / Throughput)上,往往不如数据库原生的两阶段锁或乐观锁(Optimistic Locking)结合版本控制来得稳健。

- **写偏斜(Write Skew)与幻读**:并发预订中最棘手的问题是时间段交叉重叠,悲观锁虽能阻止写偏斜,但锁粒度过粗会导致高并发吞吐受挫。 - **两阶段锁(Two-Phase Locking, 2PL)机制**:读锁与写锁互斥,事务在增长阶段只加锁不释放,在收缩阶段只释放不加锁,以此保证严格的可串行化(Serializability)。 - **悲观锁与乐观锁的抉择**:内存悲观锁面临节点崩溃丢状态、数据库长调用挂死等单点恢复难题;在高并发与分布式场景下,需要权衡是否通过数据库层面的索引锁、范围锁或乐观版本比对来替代粗粒度的单机内存互斥。

[01:10:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=4200s) 应用层锁的致命缺陷:节点宕机与并发冲突分析

面试官 Scott 针对候选人在应用层维护锁的方案进行了深入的正确性(Correctness)推敲。Scott 指出,从性能上看,候选人试图通过外部系统给会议室加锁(Lock on Room),其性能甚至比传统的两阶段锁(Two-Phase Locking, 2PL)还要糟糕。更严重的问题在于系统的正确性保证。Scott 现场构造了一个极具说服力的边缘案例(Edge Case):假设系统有两台应用服务器,即主节点 Server 1 和备用节点 Server 1'(Backup Server)。此时,第一个预订会议室 Room 1 的请求 $i$ 到达 Server 1,Server 1 在本地加上了锁,并将请求转发到数据库开启事务执行“先读后写”(Read then Write)。

就在数据库正在执行该事务、尚未提交(Commit)的窗口期内,Server 1 突然发生宕机(Crash)。集群的故障转移(Failover)极其迅速,可能仅耗时 1 毫秒,流量随即切换到备份节点 Server 1'。紧接着,第二个预订同一房间的请求 $j$ 到达。由于 Server 1' 是全新接管的节点,本地根本没有请求 $i$ 的锁状态信息,因此请求 $j$ 也被顺利放行并送入数据库开启事务。如果此时底层数据库因为写入性能抖动或网络延迟花了数十毫秒,两个并发事务就会在数据库层产生重叠与冲突,从而导致灾难性的重复预订(Double Booking)。面对候选人的困惑,Scott 梳理了整个时间线,强调只要节点存在故障切换且锁状态没有强一致性保障,应用层锁就必然会出现漏洞。

```mermaid flowchart TD accTitle: Failover Race Condition Sequence accDescr: Sequence diagram showing how application server crash leads to double booking in database. subgraph Clients["客户端请求"] R1["请求 i (Book Room 1)"] R2["请求 j (Book Room 1)"] end subgraph AppServers["应用服务层"] S1["Server 1 (主)<br/>加本地锁并转发"] S1Crash["Server 1 宕机 💥<br/>丢失锁上下文"] S1Prime["Server 1' (备)<br/>无锁信息,直接放行"] end subgraph Storage["数据库事务层"] DB1["事务 i:Read then Write<br/>(耗时数十毫秒)"] DB2["事务 j:并发执行<br/>(产生数据冲突!)"] end

R1 --> S1 S1 --> DB1 S1 -.-> S1Crash S1Crash -.->|快速容灾切换| S1Prime R2 --> S1Prime S1Prime --> DB2 ```

---

[01:15:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=4500s) 极端容量估计反思:从 15 PB 到有状态锁的教训

随着候选人恍然大悟承认应用层锁方案的破绽,讨论转向了前期系统规模估算的合理性。在早期的资源计算中,系统被设定了高达 15 PB 的海量数据存储需求。Scott 直言这个数字给得过于夸张——若按照单个 MySQL 实例承载 500 GB(0.5 TB)数据计算,要存储 15 PB 数据至少需要 3,000 到 30,000 台数据库服务器。现实中维护数千台机器的集群运维成本极高,面试时给出的约束条件过于随意,通常设计能支撑 300 到 1,000 台服务器的集群就足够展示架构能力了。

由此引申出核心架构原则:为什么绝不能在应用服务器搞复杂的外部有状态锁?应用层(App Server)必须保持无状态(Stateless),以便随时根据流量水平伸缩或随时销毁替换;而真正的并发冲突控制,必须沉降到具备强一致性保障的底层数据库中。数据库内部本身就经过了高度优化,支持索引锁(Lock on Index)、行级锁等成熟机制。如果为了避免并发而自己造轮子在内存或外部弄一套状态同步,一旦遇到网络分区或节点宕机,反而会引发难以收拾的系统漏洞。

---

[01:20:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=4800s) 面试官系统复盘:会议室冲突的核心考点与并发控制策略

在面试仅剩约五分钟时,Scott 对整道“设计 Google 日历”的题目进行了系统性的复盘。他肯定了候选人开局在需求分析和容量估算上的基本功,但强调该系统的真正技术难点在于会议室预订(Meeting Room Booking)。日历中普通的日程增删改查属于标准 CRUD,字段也容易补全;唯独会议室就像 Uber 办公室里的会议室一样“永远都是满的”,面临极高频的并发争抢。针对这种冲突,业界主要有以下解决方案:

- **数据库级并发控制**:严禁在应用层自行写锁,必须依赖数据库内置的排他锁机制,例如在查询时使用 `SELECT... FOR UPDATE` 将行或索引锁住(Lock on Index / Time Slot)。 - **两阶段锁(2PL)与物化冲突(Materializing Conflicts)**:可以通过严格的并发控制协议或构造物化冲突实体来避免幻读与重叠预订。 - **业务逻辑层面的冲突处理(循环会议挑战)**:例如用户预订每周二下午 6 点的例会,第一周没有冲突,但第二周房间被其他人提前占用了。系统该返回部分失败(Partial Failure),还是返回成功但后续周次被剔除?Scott 指出 Google Calendar 的做法是:只要第一周成功就允许创建,但后续只要有任何一周冲突,就会给用户发送一连串拒绝邮件(如 “Week 2 Room Rejected, Week 3 Room Rejected”)。 - **读写比极高带来的隔离级别选择**:系统中日程查看的读请求远超写请求(Read >> Write),读操作之间绝对不能相互阻塞(Non-blocking Read)。如果简单粗暴地采用两阶段锁(2PL),读锁会阻塞写锁、甚至读写互斥,系统性能会彻底崩溃。因此底层必须引入快照隔离(Snapshot Isolation / MVCC)来支持非阻塞读。

---

[01:24:43](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=5083s) 异步队列与乐观锁方案探讨:以 LinkedIn / Outlook 为例

在问答互动环节,候选人结合自己在 LinkedIn 参与开发 Outlook 日历集成的实际工程经验提出了替代方案:当时团队将会议室预订做成非阻塞调用(Non-blocking Call)——添加参会人员是同步阻塞的,但预订房间则是异步非阻塞的。具体做法是将预订请求发送到 Kafka,以会议室 ID(Room ID)作为分区键(Partition Key)进行 Hash 分区。这样同一间会议室的所有预订请求都会串行化排队进入同一个消费端写入数据库,先到先得;若第一个人已经成功写入,后续排队到来的冲突请求直接作拒绝(Reject)处理。

Scott 对此展开了深入剖析,指出这种排队异步拒绝的本质更偏向于乐观锁(Optimistic Locking)哲学,通过快速失败(Fail-fast)来解决争用。而 Google Calendar 则偏向悲观控制,在用户界面选择时就极力避免冲突。此外,并发控制最大的挑战在于用户“预拉取(Prefetch)”可用会议室与最终“点击预订”之间存在不可控的时间差: - 哪怕两个人在同一秒甚至同一毫秒内看到房间都是空闲的,最终落库时也必然存在竞争。 - 无论采用基于 Kafka 分区串行化、乐观版本号校验,还是数据库行级锁,其核心目的都是在最后一道防线上裁定谁被接受、谁被拒绝。

---

[01:29:41](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=5381s) 总结与面试启示:数据库锁的发展历程与系统设计考察重心

在复盘尾声,候选人坦言在这次模拟面试之前,自己完全不知道数据库内部还有如此精妙完备的锁机制,以往总是习惯从 API 和应用代码层面去硬写逻辑。Scott 感慨道,数据库技术自 20 世纪 70 年代诞生以来,已经历了整整 50 年的发展与沉淀,其单机事务支持(Single Node Transaction Support)与并发控制能力已被打磨到了极致。虽然在跨节点分布式系统中事务较为棘手,但在单节点上数据库原生提供的功能极其可靠强大。

当候选人询问未来面试遇到类似场景是否可以直接向面试官提议“调用数据库锁”时,Scott 给予了明确指引并总结了高阶系统设计的核心打分点:

- **不要停留在名词表面**:不能光说“用数据库锁”,必须清晰阐明数据库采用何种隔离级别(例如 Read Committed 还是 Snapshot Isolation),读操作是否非阻塞,以及具体是在哪张表的哪个索引字段上加锁。 - **面试时间分配与轻重缓急**:基础的 CRUD 实体与数据模型设计(如 Event CRUD、User CRUD)最多用 15 到 20 分钟快速过掉,因为这些只是基线要求,无法拉开差距。 - **面试官的核心意图**:考察此类日历题目,真正的深度在于观察候选人如何处理复杂并发与事务冲突。对于只有三年工作经验的工程师而言,这道题确实难度偏大,因为它高度依赖对数据库底层并发原理的深刻认知。

整场模拟面试在热烈互道感谢的融洽氛围中圆满结束。

## Notable quotes > [00:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=0s) 平时比较不正经,很难蹭得上老师的称号 — Scott

> [05:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=300s) 看 API 的形状来做架构 — Scott

> [05:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=300s) 拿塑料球当承重柱,墙体直接垮掉 — Scott

> [10:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=600s) 要把 Kafka 做成通用数据库

> [15:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=900s) 旧金山到萨克拉门托 — Scott

> [15:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=900s) 地球到土星 — Scott

> [53:15](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=3195s) 谁来提供这个绝对时间? — Scott

> [39:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=2340s) 你到底在锁什么?你不是在用数据库自带的行级锁,而是自己在外部造了一个布尔变量,那系统竞争的资源粒度到底是什么? — Scott

> [01:20:00](https://www.youtube.com/watch?v=NFZs5fu4bSk&t=4800s) Week 2 Room Rejected, Week 3 Room Rejected — Scott

## Mentioned Scott Shi, 刘瑞 (Rui), Uber, Instagram, Google Calendar, LinkedIn, Outlook, Redis, Cassandra, DynamoDB, Elasticsearch, Kafka, MySQL, MongoDB, AWS ElastiCache, Go, Python

## Video description Mock interview of design google calendar doc could be found here: https://docs.google.com/document/d/1IdEKaXNoCwUaSx1lZtBD5kbPkERjUbc0mUi8sLt3g30/edit

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

中文简报

日历系统设计弃用应用层锁 谷歌面试官拆解数据库并发机制 应用层内存队列与外部锁在节点宕机时必然引发重叠预订漏洞,会议室高频争抢必须依托数据库行级锁与快照隔离下沉裁决。 视频首先通过复盘候选人既有的 Instagram 设计,借由“搭积木与材质”的隐喻破除“看 API 外形驱动架构”的误区,指出 Redis 等内存组件与底层写优化数据库的本质差异。随后正式开考“设计 Google Calendar”,在厘清功能边界与完成百亿级容量估算后,全场重心聚焦于“实体会议室并发预订冲突”这一核心难题。Scott 针对候选人提出的全库排队、单机内存队列等应用层锁方案层层施压,并通过节点宕机切换导致重复预订的边缘案例,彻底阐明了为何应用层必须保持无状态,且必须依赖数据库底层的快照隔离(MVCC)与索引排他锁来解决写偏斜;最后结合工业界实践给出了系统设计面试的高阶通关指南。 系统设计切忌“API 驱动选型”的积木误区:组件的 API 只是外形,其存储引擎、持久化和并发模型等底层“质地”才是承重关键,OLTP 系统中 90% 以上的时延瓶颈在磁盘 I/O 与数据库检索而非内存计算。 (5:00) 设计 Google 日历的核心技术硬骨头在于会议室预订的并发冲突控制;应用层必须保持无状态,严禁在应用内存中自建队列或外部有状态锁,否则在节点故障容灾切换时必然引发并发重叠导致重复预订(Double Booking)。 (70:00) 对于读远大于写(10:1 以上)的日历系统,两阶段锁(2PL)会导致读写互斥阻塞而拖垮吞吐量,工业界更推荐采用快照隔离(MVCC)实现非阻塞读,并在写时配合数据库索引排他锁(如 SELECT FOR UPDATE)或异步队列乐观快速失败。 (80:00) 系统设计面试中基础 CRUD 与数据模型应在 15-20 分钟内快速带过,真正的区分度在于能否清晰阐明数据库隔离级别、锁粒度以及对时钟漂移和写偏斜等边缘失效模式的系统性推演。 (89:41)

Reading summary

在系统架构设计中,工程师常犯“仅凭 API 形状选型”以及“在应用层自行构建有状态锁”的误区;面试官 Scott 指出,日历系统的核心技术硬骨头在于会议室高频争抢下的并发一致性,必须依赖经久验证的数据库底层机制或异步串行化策略来规避冲突。 破除API驱动架构:积木质地与存储选型 (5:00) Scott 提出系统设计如同搭积木,API 只是积木的“外形”,底层存储引擎的持久性、可靠性及并发模型才是积木的“质地”。如果仅因 Redis 提供了按时间顺序操作的链表 API 就将其作为发帖主存储,系统将面临单线程写入吞吐受限与内存易失性丢数据的严重风险;面对高频写入场景,业界标准做法是选用基于 LSM-Tree 等写优化的分布式数据库(如 Cassandra 或 DynamoDB),以 UserID 为分区键、时间戳为排序列,写延迟可压至 0.5 毫秒以下。同时,Elasticsearch 等组件设计初衷为辅助检索,绝不可当作业务的唯一可信数据源(Source of Truth)。 此外,在在线事务处理(OLTP)系统中,磁盘 I/O 与数据库检索通常占据整体时延的 90% 以上。花大把精力在内存层面优化微秒级的列表合并算法,往往只是在优化不到 1% 的非关键路径,脱离了真实的性能瓶颈。 日历系统的容量规模与核心冲突场景 (30:00) 在 Google Calendar 的架构推演中,普通的日程增删改查属于标准 CRUD 范畴,系统真正的技术难点在于会议室的可用性校验与并发预订争抢。以 10 亿日活、单事件约 10 KB 为基准测算,平均写入 QPS 约为 1,000(峰值 2,000–5,000);由于用户反复刷新以及拉取同事日程视图的放大效应,读取 QPS 通常比写入高出一个数量级,达到 20,000 左右。 针对前期候选人推导出的 15 PB 五年存储规模,Scott 直言该数字在工程实际中过于夸张(按单库 500 GB 算需要上万台机器),强调容量估算应符合生产常识。系统最关键的业务约束在于实体会议室不可重叠共享,当多个用户几乎同时发起预订时,读写交织容易引发写偏斜、覆盖写入以及循环会议周期性冲突等并发异象。 应用层有状态锁的脆弱性与故障转移漏洞 (70:00) 为控制会议室争用,候选人先后提出了全局排队锁、按房间维护内存队列以及按 Room ID 哈希路由到单机的策略。然而,跨节点依赖本地时间戳仲裁面临物理时钟漂移(Clock Drift)风险;若将同一房间的读写静态绑定至某台应用服务器,单机崩溃不仅会导致未持久化的队列请求全部丢失,还会破坏就近接入策略。 更致命的问题在于故障转移(Failover)引发的并发冲突:当主应用节点接获请求并加上本地锁、正向底层数据库提交事务时,主节点突然宕机;集群在毫秒级内完成流量切换至备用节点,但备用节点缺乏先前的锁上下文,会将后继到达的同房间请求放行入库。底层数据库若发生写入抖动,两个重叠的事务就会产生重复预订(Double Booking)。这表明应用层必须维持无状态,不能自行造轮子维持脆弱的内存状态。 数据库级并发控制与多版本隔离权衡 (80:00) 解决预订冲突的正统方案是将并发控制沉降到底层数据库。系统可在事务中利用 `SELECT... FOR UPDATE` 针对特定房间的时间槽或索引实施排他锁定,或利用两阶段锁(2PL)构建可串行化保障。然而,日历系统的读取流量远超写入流量,两阶段锁中读写相互阻塞的机制极易造成全系统吞吐瘫痪,因此底层必须引入快照隔离(Snapshot Isolation / MVCC)以支持高效的非阻塞读。 另一种可行的工业界方案(如 Outlook / LinkedIn 的实践)是将预订解耦为异步非阻塞调用:以 Room ID 为分区键将请求推入 Kafka 等消息流中,同一房间的预订在单分区内串行消费落库,先到先得并让后续冲突请求快速失败。Scott 分析指出,这种异步排队本质上倾向于乐观并发控制哲学,无论悲观或乐观,关键都在于保证最后裁定边界的强一致性。 Scott 总结指出,针对有一定经验的工程师,高级系统设计面试的核心考察重心不在于快速堆砌标准 CRUD 实体,也不在于单纯宣称“使用数据库锁”这一名词标签,而在于能否讲透底层的具体隔离级别、索引锁粒度、读写互斥成本以及节点宕机后的容错恢复逻辑。

Source quotes

[0:00] Scott: 平时比较不正经,很难蹭得上老师的称号 [5:00] Scott: 看 API 的形状来做架构 [5:00] Scott: 拿塑料球当承重柱,墙体直接垮掉 [10:00] 要把 Kafka 做成通用数据库 [15:00] Scott: 旧金山到萨克拉门托 [15:00] Scott: 地球到土星 [53:15] Scott: 谁来提供这个绝对时间? [39:00] Scott: 你到底在锁什么?你不是在用数据库自带的行级锁,而是自己在外部造了一个布尔变量,那系统竞争的资源粒度到底是什么? [80:00] Scott: Week 2 Room Rejected, Week 3 Room Rejected

Original transcript

[0:00] 我们今天interview candidates是刘瑞是吧?然后你要不要先简单介绍一下自己好,简单介绍一下自己就是有三年工作经验, 其实本身是学IT的, 转到写码, 没怎么学过算法跟数据结构, 还是在工作期间慢慢学的。听Scout的课挺有感悟的吧, 上次觉得那个志愿者真的是表现挺好的, 希望这次Scout能多多找出我的问题, 多给我提意见, 我在今年,1月份还是三月份吧可能就二三月份左右面了一回Uber然后我当时系统设计觉得特别不好所以稀里糊涂的给人家设计了一个奇迹怪怪的系统然后今天打算带着这个系统来也想让那个Scott老师帮我指点一下就是我知道我这个设计的不好但是我觉得思路挺奇特的这个对叫Scott就好我之前可能说过在群里面说过一下就是叫Scott好叫老师或者是什么我都不习惯叫Scott就好好,我很难蹭得上老师,我这个人比较不正经,所以经常很难蹭得上老师。然后我们打开摄像头,然后讨论开始讨论好了。然后我记得你跟我聊天,我看你昨天给我发了一个东西,但我没空看。你还跟我说你就是设计了个Instagram,想分享一下,然后你现在来讨论一下。是稍等我这个摄像头好像卡住了好了,应该能看见我了。您想先说哪个呢?还是?先说你的好了。先说我的,好的。先说你的。你能给我个权限,我share一下屏幕吗?好像应该有。好的有这个是我设计的一个系统,是一个Telegram,然后这些数据当然都是瞎编的了,然后我当时拿到这个题目的时候,我想的就是因为RIDIS是可以存LinkedList的,所以我基于这个原理设计了这个系统。比如说如果UserA他post了一些东西的话, 我就用一个linked list把它存进去。比如说他的post1,然后我们copy一下。谢谢大家他的Post 2, 我觉得这样的好处就是,他post的2,这样做的好处就是,他恰好能够根据时间顺序排列。也就是说,2是他比如说昨天po出来的,然后1是他今天po出来的。那么就是再泡出来就往如果它破出来新的,那么就往这个位置加就好了。基于这样一个原理,而且我觉得这样一个原理,当userb想get所有的post的时候,那么只需要那个 该只需要 more 指 k-thoulted array谢谢大家比如说我想要前10个,那么我只需要more case out of the array,more to the 10th,然后return给candidate端。就好了。这块我想说几件事啊我觉得我这块说可能说的稍微不客气一点就是这个设计我觉得是不对的啊然后我周末的时候刚跟那个instagram一个同事啊我一个同学我之前研究生同学他他在facebook工作五年了然后他上周末我们一起去hacking然后他说他走到instagram的这个Instagram Infra-群了,然后他们maintain这个就是整个世界上最大的Cassandra他们组织有三个人,然后门顿市场最大的Cassandra Server有4000多个 nodes,然后就讲了讲他们怎么用RockDB搞的这块有几个很明显的问题啊就是我想提一下说的不客气一点不好意思啊说直观一点没关系您尽管说对就是首先最大的一个问题就是说设计系统设计的时候不能看API来设计就是你永远不能看API来设计所有的API看起来都很friendly然后看起来都很人畜无害但是你看API设计是最有问题的一个事情我之前的时候 [5:00] 早期的时候讲系统设计课的时候,最早期的时候我经常说是,你讲系统设计是,你就是搭积木的一个过程,然后你积木有不同的形状,有不同的质地,有不同的质地,然后积木的形状是API,就是它有圆形的API,它有方形的API,然后积木的质地它可能是木头的,可能是金属的,是吧,可能是不锈钢的。有可能是塑料的这个是它的质地,你不能说这个形状看起来适合,然后本来应该放了一个层纵轴,然后就放了一个玻璃的上去,玻璃的形状。玻璃质地的东西放进去,这个是明显不对的。也就是说,你系统的设计永远不能用看著API来设计应该用什么,不能说这个 system,这个 API 看起来 work,然后我就把这个 system 放过去,这个是绝对不对的,这是系统设计中,基本上一上来就被 毙掉的这个东西,就一上来被 毙掉的。这个什么呢可以接受的是什么呢?就是可以接受说这块车从纵走,那我放一个不锈钢的那我放个不锈光,但形状不行,那我把形状稍微tick一下然后这个是可以的,但你不能说这个是个承重轴,我放个形状像是可以的,但是这个它是个纸做的或者是个塑料做的,那这个是完全不行的。你懂我意思吧,就是它这个database的质地,它Database的它背后的这个怎么设计的它这个质地是远远大于它API的API 是它的形状,可能这个Database没有你想要的API无所谓 但它质地如果是金属的你把它稍微Polish一下稍微修改一下 是吧然后你还是可以把它放进去做乘重轴的但你不能说这个是个圆形的我需要一个圆形的然后看这个塑料球可以我把它放过去然后这个墙直径就垮掉了然后这块的话我觉得属于墙会垮掉的一种情况,为什么呢?你这个user post data 的时候,你刚 post 完,然后发现 data 丢掉了好然后就是对吧,Post完以后对他丢掉了。然后其次的话,我们周四的时候会讲Redis的RightThroughput是很糟糕的。Redis 的 write 是很糟糕的,它是 serialized 的 write,然后它讲究的是非常小的 write,它的这个 write skill 是不好,它 read 是比较好,它是 focus on read 的,然后然后这个情况下的话,所以这个用Redis,我觉得不是一个很好的选择,我觉得不是很好的选择。然后最后的最后这个这个这个Use Case就是你一个User post的多个Post这个Use Case呢是用Cassandra或Dynamo就可以完美实现的Facebook用的是Cassandra完美实践的,你把partition可以变成userID,然后这个sot可以变成posedtimestamp,这个是可以完美实践的。所以这个是所以这个是不太合适的设计然后我再提一下这块设计中常犯的一个错误是我觉得这块有一个list 是吧我读个list 放到list更方便但是在这整个系统执行中这个时间是不一致的你如果从,我这么说一下好了,就是如果你做一个普通的算法来做,就是什么意思?普通算法呢?就比如说,你做一个,我举一个算的比较慢的吧,我想想什么算的比较慢一点。DP吧,Dynamic Programming,N-square的算法复杂度,N-square算法复杂度它没面也能算十的九次方,机器可以很轻松的算十的九次方实际10的9字方是什么概念?就是你把所有的user全部scan一遍,也就是10的9字方。假设你有user data,你把user全部进行一个operation,全部把user id 加起来,你把你所有的user id 加起来也就是10的9字方。这个算的是很快很快的,就是计算这方面是很快很快很快的。最慢的是什么?当你读database的时候最慢的,就是你所有的transaction系统,最慢的部分永远是这个data lookup和从database里面读取,就是说白了是你来一个request,然后我看根据你的request找件我要找的一个index,然后就是disk嘛,如果不在memory的话,很多时候在disk上,首先找见disk在哪里,然后把disk的poyote到那个地方,然后从上面读取data,这个部分是最慢的。这个部分经常是几十几毫秒,就是占了整个API request的90%以上。你在优化的计算这部分,在的是可能不到1%很多时候是不到1%的你知道吧,就是这个计算,把这个list全部读出来,然后把它12345这样list标好,这个salt计划是不到1%的,甚至比1/1000都要少我觉得我之前只读到一篇文章,他是做高性能应算的,他说这个,他当时是用的当时用的Node.js还是Python来着,他说可能是Python,他说Python慢的几乎和database一样慢了。 [10:00] Python居然占了database总时间的30%database占70%Python 占了 30%,然后把 Python 转换成 Go 以后,这个 language 占的时间就有就变成基本上1%以下了所以这个majority时间,如果你把那个做一下System Profile的话,majority时间,基本上都在做这个database read和read,所以这个你优化的这个是1%的时间啊,这个其实是没有必要的所以是两个问题,第一个是优化的不是最主要的,System中不是最主要的Critical Path,其次的话不能看这个积木的形状来决定它用不用它,必须看积木的质体,必须看要的这个Service,这个Mojo的质体,不能看它的形状来,不能看它的API来进行设计。好的明白了,这个是彻底明白了。从设计的最开始的点就是错的。对,我觉得用Redis一般来说,Redis 设计,还有之前有人问我为什么不能拿 Elasticsearch 作为 main database 是吧?就是直接把 Elasticsearch 我记得上周的时候有一个人问我说为什么不能把 Elasticsearch 作为 main database因为这个Elexig Search它设计的时候,它Durability和这个Reliability就不是作为Media-Base设计的Elasticsearch一般来说,你都有个其他的SourceWord Truth Database,你不管是Seql,Mongo,Cassandra都可以,你把这个有个SourceWord Truth Database,然后用Elasticsearch呢,是提供它更多像一个index,你知道吗?Elexizer,我直观感觉更多是index,它设计的时候也这么设计的然后他说更多像一个index就是你用Elexic Search帮你作用辅助的search,但是你不能把Elexic Search作为major storage,这个本身就是很有问题的。然后你会看到很多文章,database的时候,就是你database这块会看到很多文章就是说,Electric Search, Reliability也提高了,也可以用作什么经常是一些Sales的文章,然后我觉得最常见的就是这个我去参加Kafka Conference的时候整个 Kafka 这个 team 不厌其烦的说我们要把 Kafka 转化成一个 database 是吧?但是我觉得谁会把 Kafka 做 database 做得好运 是吧?你会看到很多cells的文章Sales文章他会尝试把它推销成A推销成B,B推销成C,但是这个都是没有任何参考性的。我觉得这个对好的,那确实明白了。好的,那我们来看一下我们出的那道题好了。我们今天就不讨论,不问这个Challenge Project了,我觉得之前的话也问过几次。我们就看一下那个题好了。你可以点一下Google Doc在这个Chat里面。好哦,ok稍等然后我刚才说的话大家有什么额外的想补充的吗谢谢大家谢谢大家您好,Scott,有一个问题,您刚才说Redis它的读的性能高,但不是用来写的,如果我们需要写的话,有一个比较好的代替品是什么?写的话一般,也不是说完全不用来写的,就是我们下周讲Transaction的时候,你会看到Redis的话它写是single side的写,所以它推荐的写是什么呢?它这个写是非常small data,small size data,然后你这个写的话是非常快的完成,然后你这个同时你这个写的话你的这个Redis,这个Rest throughput是有上限的,因为它是single side写的。但是一般来说,假设你问我的话说有一个database或者database-like的这个system一个基目什么是优化写的话那肯定是Dynamo和Cassandra,Dynamo和Cassandra是纯粹为写优化的好的,谢谢你。Denimal和Cassandra,看你怎么做configuration了,你可以把read和write的corum改的不一样点。但是我记得我之前看的那个效果的话,write是0.5毫秒。Rate是0.5毫秒以下,然后那个Rate是比Rate要稍微慢一点,大概就是一两毫秒,就是一毫秒左右。但是它那个Rate其实是比Rate要快几倍的,甚至快一个数量几倍的,所以它是专门对那个Rate进行优化的。你可以看到更多benchmark我看的benchmark比较早了可能是15年还是16年的benchmark好的,谢谢Scott,我想问一下那要是我就是我会把一些 [15:00] 有一点点复杂的object写到Reddit上面去做serialize和deserialize,那这个cost会很高吗?没有,这就是in-memory database 的优点比Memory Database和传统的SQL比,虽然它是从Memory读肯定会快的但是它其实最大的快的地方就是你memory database 你不需要把它拆分成不同的module 然后存到这个这个 SQL 里面你直接就存在一个 list 存在一个什么的就是你可以把它就是你可以把你自己的 in-memory 的 data structure 直接 dump 到 in-memory database 里面这个 serialization 和 deserialization 可以省很多时间就是它这个很少时间的部分一方面是你不小心写disc我们之前讨论过就是写Memory的话有点像写到Memory有点像你从这个San Francisco到这个去吧 这样是麦的sacramento 但是你要写到disc上就感觉到土星那种感觉就这个distance感觉到土星这种感觉差的他的至少10倍可能100倍1000倍有可能但至少差10倍好的 那什么样的WriteSize是比较合适呢?Redis吗?对,对的,是Redis。你如果Redis size很大的话,你后面所有的Red都有一个overhead,你如果Ride size很大的话 顺便所有的Ride都有overhide 因为它一个一个鞋嘛嗯那我觉得Redis的话,其实很难说出来,这个可能还是要做一些Benchmark 以后才能看出来,但总体来说的话可能不适合就是写一个非常大的上级你可能得把他的Data Model再Break 细一点好的,谢谢。你不能上来写一个megabyte 肯定还是比较糟糕的,但是break 细一点的话,几十k,几k的话,我觉得应该问题不大。好的,那应该没有那么大,好,谢谢。对,一般来说,一个密碗拜次数比较大的。几十k,几十k的话,二十来k其实不是很大。就大概10个object左右吧。10个object,对,那不上你打,那可能就是个10k到100k。好的,谢谢。Scott,在AWS里面,这个是用elastic cache,然后那个是distributor的redis吗?这个话是不是也难道也是serialized的鞋吗?Electric cache谢谢大家Elastic cache can be used for Redis, there are two types, one is main cache and one is Redis. Oh, I wanted to use elastic search, I thought how about Redis? Elastic cache, because Redis is not the它是每一个redis 你可以认为一个partition是有一个single write thread吗但是你可以partition了,比如说你现在Aless的话,比如说ID的话,你可以把ID你可以把你要写的user这个space,然后partition到比如说一万个server,1000个server都可能是吧,然后每个server相当于你有一个writing thread这个skill起来是比较简单的每一个partition就只能写就三个thread的写但是你可以partition成比如说根据user partition这样子对对对每个partition是单个thread写的但是你可以partition很多份是吧你比如说你user id是假设举个例子从1到100万的话你比如说从1到1万可以到partition1,然后1万到2万到partition2,当然这个partition是错的,你可以参考我们之前的partition那一节但是这样的话,不是position了100个node吗?那你这样的话,其实是你有100个writing thread可是我理解的Redis它是有16000多个slot就说然后slot然后再去划分到register cluster里面的,我有点不太明白这个过程。因为有这样的slot?嗯因为我的理解就是说Redis是有固定的slot吧,是16000多个,对不对?然后比如说你有10个Redis的机器的话,然后它就把这16000多个slot给分成10份嘛,就是分成10个range,然后每个range放到一个机器上是这样的吗?才是我理解错了对这个的话可能跟你使用相关吗?你也可以只有一个Redis instance啊?你这个不是建的时候可以说?虽然只有一个Redis instance 但它还是有16000个slot在里面它好像是Redis internal的设计吧对吧?它就这么做的对这个我不知道这个我确实不知道啊如果你有rading的话你会有点问题。因为我一直很困惑就是说如果我送了16000多个slot,然后每个slot就是说比如说slot1到1000就送到第一个机器,1000到2000送到第二个机器上,然后这个是我存的比如说我存一个data,比如说我的data, [20:00] 用户上的User ID来传的,那么你是说根据User ID来去划分到哪个机器上去?哦哦,我知道了,这个16000个slot可能是它partition的一种分的方式,partition有三种分的方式吗?一种是上来就先划分成固定的partition我估计这16000个partition就是它提前划好的partition但是这个partition可能在同一台机器上这个是Partition方式不是有三种吗?一般来说有三种,一种就是你上来就固定成一个具体的数量的Partition16000个的话比如说有3000个机器那你可能一个机器五个是吧4000个机器一个机器四个盘队上然后这是一种,然后其次的话还有一种partition划分方式和nodeproportional的,像Cassandra的话是partition是proportionaltonode来划分的还有一种partition对在有不同的partition化分方式我觉得这个partition下来的我知道我不是说这个Redis的因为我的印象中Redis是对Redis 是固定的 partition。对,这个固定的固定是帮你标定了这个horizontal scalability了。但是如果是这样的,比如说如果它的Redis固定了这个slot的话,然后我在想问的几个问题,一个问题就是说,比如说我拿Redis来cache我的user data,对不对?比如说每个User有UserID,就是UserID应该怎么跟这个slot做一个Mapping这样的关系?第二点就是说我拿到这16000个slot,我要怎么把它distribute到我的class里面?对这个就是说对啊这个的话你很多时候你需要一个routing layer 你可以看参考一下这个你可以参考一下,我这块没有部署过,我没有部署过1万多6千,我没有部署过这个这个Bredis的这个但是如果你就是想那个时候的话你可以看一下这个就是之前讲partition那一张就是很多时候它是你有个你需要有个routing layer然后这个routingLayer是决定几个的,一个是它其实就决定这几个事情一个是给一个ID然后他找见哪个partition是这个ID对应的哪个partition然后其次的话是给一个partition它找见这个partition在哪个node上就是那个node name是什么然后最后的话是给一个node name然后找见IP address最后这个是在那个就是DNS在解决的,domain name server解决的。但是前面这两个一般是有个routing layer,它可能需要有一个像有的那个比如说 rulekeeperI'd like keep routing them对啊,但这个始终还是个general的一个law,一个partition的一个布置,但是我就是想针对redis,因为我们在谈redis嘛,就想针对,因为redis多了这么一层的一个slot,没关系,如果我们也许可以恢后讨论,但是我觉得我还是不太清楚,对。因为我一直看得对如果Redis已经提供你partition的话我估计它也提供这个它可能它自己可能会wrap一层etcd或者是wrap一层roupaper它实际上就帮你做了这个routing他应该是帮你做肉丁,他如果做这个的话,他没有道理不帮你做肉丁。但我这话不熟啊,确实不懂啊,你可以搜一下,或者你发一下,我可以帮你再offline看一下。好的,我只是知道它有固定的Slot,但是怎么去分的我也不熟悉,对好的好的好我们还有其他问题我觉得有很多很index好了我觉得还挺有气的Scott我之前看到过一个design是instagram的design是这么做的他的主DB是用SQL做然后把存法跟刚刚说的是差不多的然后这样的话但是慢就会慢在比如说我pull newsfeed的时候他需要去join table根据我的follower然后去join对吧就是可能要join两个table或者三个table然后他解决这个问题的方法就是一步的搞一个就是一步的搞一个每次你比如说有新的post以后你就会一步的去Redis上面把每一个人应该看的东西写在那上面然后你每次看的时候其实只要查Redis就可以了就是看那个人有什么post要看的或者然后你follow的时候你也是更改Redis然后就把Redis当成cache这样一个层在这里,其实这样也是写的操作在我看来也是挺多的,只是它是异步的。对,我记得我们讨论slack的时候,我当时propose的slack就是你进去那个界面也是放到Redis上的,它这个data也是一个user,然后在channel也是放到Redis上。放Redis上本身是没有错的我觉得还是我们讲System Design的时候是讲这切入点我觉得刚才我们讨论这个问题的时候刘瑞同学这个切入点是不对的就是说我看到这API以后然后讨论到Redis [25:00] 支入点是不对的啊对我在 chat 里面发了一下我刚收到的 Redix 怎么做 partition 的就是说对,它跟一般的 partition 做的有点不一样这个没有这个是标准partition做法之一这个就是固定数量的partition是这么做的它会根据你的ID然后把它hash过来这样的话其实你从data到partition这部分复杂度是基本上固定死了但是你从partition到node这块是要dynamic adjust的就是标准的这个fixed partition的做法是这么做的如果这块有点糊涂的话,或者其他东西没有context,或者有点糊涂的话,你可以看一下就是我几周前吧,两周前讲的partition那一张,三周前讲的partition那一张那块的话是讲了很多partition的做法和怎么做partition好,我还是说一下,我感觉好像也有很多看见很多新加入来的同学你个是这样子就是嗯讲System Design的时候,有很多讨论的方式,但是System Design主要考虑的是四个方面,我再强调一下讨论四个方面。第一个方面是看这个就是被面试同学的这个background是吧就是看他之前做过什么项目,独立设计过什么项目,然后做过什么项目,然后其次的话是看这个表达方式是吧,你就是怎么切入这个表达的,你是怎么样表达的,这个表达方式很重要那你如果是表达方式是从这个思路,就是从A到B到C的话,如果这个思路和这个就是常见的思路不一致的话,就是这个思路会有问题的话,多半会就是否决掉了。和表达的方式有关,就是articulate,你怎么样articulate这个problem。然后其次,再其次的话是看knowledge,这个knowledge积累怎么样是吧,你对于这个系统的理解怎么样,那你对这个系统可能出现的问题怎么样,这个是怎么样。四分之一很多的时候是之前的一些部分是很重要的一部分然后问怎么看之前内容在那个群公告里面,在那个群公告里面有,如果你不在那个群的话,你得找人拉一下,就是说,找别人拉一下,在那个群里面。好,我们还有什么额外问题吗?好像讨论时间有点久了,我们是不是可以开始Systems计算了?考量了二十分钟看有没有额外的问题,如果还有的话再回答最后一个,再讨论最后一个。谢谢大家谢谢大家谢谢大家谢谢大家好,我们开始好了。OK, so let's switch to English. So here I would like to ask you to design a Google Calendar. In this case,你也可以说什么是个订阅的订阅的订阅也可以说什么是个订阅的订阅to a set of frequencyand schedule the event嗯OK,I think I used Google Calendar before, but I'm not pretty familiar with your request.with the systemthe requirements.So now I list the requirements.So I just谢谢大家I think I count all the requirements嗯 嗯 嗯是正确的?我认为第一件事我们需要去做和去做的事Send an event to the number who will join this eventand for another kind of anotherClient, the guy will receive the event.I will say these two [30:00] This two match the I had a guest and will my create a I think we need to create first. And then, so... So number three is download all in one. Meeks Such like I have two computers. I need a Yeah. from i need to download my calendar from one computer to another computer and sometimes we need to see my friend's event my friend's schedule so it's also download also with also with visible event? I will say it meets this request. Thank you. And we also need the. I the room. add room on our event if the room is available. So we will talk about the this request for this request after we finish the traffic. And so I think the system will be a reliable system. And the, at here we, Uh... we related, so we need to check the room available and the son book the room. So we need a consistent consistent system we don't want to two user who maybe clicks the book room together but we just have one room we cannot give this one room to the two guys at the same time Okay, that makes sense. You don't want to have two people go to the same meeting room with totally different purposes. Yes. Okay. So exact meat of your requirement. - Yeah, so probably I want to add availability there as well. You don't want the system to be not responding? Yes. And also I will separate two two part ways like feature requires the other is like a system request something like that. Okay, system requirement. Mm-hmm. Mmm. System... I Requirements. Thank you. Okay. So here is a few feature requirements. Add room, We just talked about "Wisability" Thank you. Okay, so can we ask how many users, how many active users for this system? Yeah, let's assume that it's Google style. So you probably have a A billion user. Let's say you have a billion user. Ability user. Thank you. um and how many times for one user create a event for one day yeah let's assume that you create uh uh one event a day One event Thank you. or They. So can we assume I will risk. I will resale three event per day. Yeah, makes sense. Interaction. Yes, even pretty. Thank you. Thank you. Next things, I will talk about the storage. Shhh. 1 billion users. Thank you. Sure. - One... - Is it cracked? Yeah. Yes. I think so. It's 10 to the power of 9. Sorry. 10 to the power of 9. That's 1 trillion. Oh, OK. Time for one event. And can I assume 200 KB for one event? I think it's too big. Probably around 10 kb is more likely. - Turn KB. - Sure. [35:00] 200 kb is very big, yeah. Okay. Mm-hmm. Uh... 疼KB! Thank you. Thank you. This is for one year. So how many years we will... For beginning how many year let's assume that you want to stop five years data five years Mm. Thank you. All right. Thank you. Thank you. Thank you. P.G. Goodbye. I think. Yeah, so close to that, yes. Yeah. So what is next? I will say QPIs. About the QPIs, so... I think it's a 15 PB instead of 150 PB. It's a 15 PB. Faith. 15. Yeah. Yeah. So the QPS about the right. AIDS All right. Thank you. Thank you. Thank you. . Thank you. Hmm. Almost the Lexis. Thank you. Sure. Probably close to a thousand. Close to thousand. Yeah, it's probably close. Average is one thousand, one kQPS. Thank you. Thank you. Okay, 2K QPS and write. So read. Thank you. I will say it's Thank you. Thank you. Thank you. 2K is right, but not only you, not only you, I will say, read, it's not only from my from my co-worker sent to me. But sometimes I will such like double check it, I will I will check my friends calendar. So I will time for right here. Yeah, I think it's really the value probably really it's probably one level magnitude larger than right so probably 20 K or something more reasonable. Okay, 20K. Yeah, 20k per second. 20k per second. So here I just saw the conversation. Well, in your interview, probably your interviewer wants to mention anything. You need to go on this calculation yourself. But here just to speed up the process a little bit. And also we can do some assumption here. For example, if average QPS is 1K, then we can say peak QBIS is probably 2K to 5K. And if If a write is like 1K, rate here, like a typical rate here, could be like times 10 or something. So it's a user common sense, and then we can roughly estimate the number. Okay. Make sense. Thank you. So now I will talk about my solution. Mm-hmm. Thank you. Thank you. Thank you. Can I assume? I think I need to assume one more thing. So when people can draw two or three meeting. But sometimes the people just go to one. And sometimes maybe such like such like me, in my home, just open the meeting and do something other things. So can I assume one people can go to multiple meetings? Yeah, that's normal. So sometimes you need to go to multiple meetings. That's a pretty normal thing. Okay. And one room, one physical room. It's just for one meeting. Yeah, you cannot share a room. Yes. The solution. Thank you. Okay. Thank you. Hmm. [40:00] So, - Uh, I will say the solution one, the request one, two, one and two and three is a normal request. So we just need a database to save all of the basic information. Right. Sounds good. Okay. And I think the hard point is the So we need to check the room, So is this rumor available? Um... Yeah. The hard point. The hard point I'd hear such like this is time. Thank you. Thank you. We have a user A and another user A. B. Mm. Thank you. A check room one. Thank you. A check room 1 at here and A book room 1. at here. So between a check and book, B It's maybe a by the guy. B check after A, but B book before A book room. So I will say in this point, this is a consistent problem. Where is my solution? My solution. Thank you. My solution will do... My solution will do write after read. So every time when we write something, we will add Uh... Read at here. So when we do read and the time to Right. Thank you. So how is the difference? So at here, there is pretty close. no so you cannot consume you can under some uh yeah yes so i will i will do something i will do something more at here just to explain the read the right after read um so in this case so i will add a In this case, I will add a database locker at here. So at here we will combine the read and write together. Just to make sure between the read and the right, Thank you. Between this time, only B can access database. Inserate and write only Only one user can access the database. Okay, so here, what's the log granularity? What you're going to log here? - Uh, The lock, we just need a variable. If we just need a variable, if it is true, it's locked. It means someone used the database. If it's false, it means the databases are it's open, I mean, we can access it. So you are saying, you're not using database default. Like, database provider log, you're not using that. You actually use another system to provide log. We can use the database function Okay, so... Mm-hmm. Yeah, so if you're a database log, what's the granularity you're going to log on? So what's the resource granularity you're competing here? Okay. Thank you. For example, I'm a user-- Let's actually draw a few examples, like work through a few examples and say, If we can have this sorted out, let's say, you know, Scott come here on to book room one. - Today's October 4th. 7:00 PM, right? So I want to book room one at 7:00 PM. So break on here. You want to book room one All right. Thank you. [45:00] October 4th. 7 p.m. Okay, so what are you going to lock here? We will add a message queue. So who is going to the message queue first? So who will get the locker? - Message queue, so you actually introduce another system to do the log. Okay, so could you see yes. Yeah. Could you walk me through how you're going to use message to do the lock. So such like scout, it's go first. At here we have a message queue and Scott is go first. So Scott in the first position and then Rui in the second position. Maybe we have other guys such like Jim. So Scott is the first guy, so now Scott can use the database first. And the one Scott finished the record when the scout finish read and write. So it's race time. So read your check. Review use database second. So you're going to log the whole system to one? Like only starting? This is the solution one. Yes, in the solution two, I will change it. Solution one is lock all the database. Okay. Yeah. Let's see what's the other solution here. Okay. Now solution two. I know solution one is pretty slow. So solution two, I will create a site. So site is intake. It's the Room ID Thank you. Oh, oops. So such like Scott, Thank you. Scott want to book room one. Thank you. Okay, one scout in this couple of time. So we will see we will save we will save we will save room one in this side. So where is this side stock? This side stock is Maybe... Thank you. Just a memory. in memory in the memory yes I think for For general companies, they maybe have 1K. They maybe have 1K rooms. 1k physical rooms so memory is enough to hold off it okay so let's let's actually think about some scenario here it's gonna be it's gonna be fun okay so okay let's say you know let's take the first example as QS example So in this part, you actually, you know, you basically use Q to implement, you know, it's like a, People come to start for your service, so you use a queue. to to to say you know people line up right so what if uh what if scott want to book this room and after long delay, whatever reason. The delay could be network delay, It could be network delay, it could be, you know, Okay. somehow you have this process pause and it could be, you know, you basically go garbage collection on your node suddenly. Whatever is the reason. So you delay for a few minutes, right? Could be one minute. And then about it. What are you going to do for the rest of the request? So first of all, I will talk about the where where's the delay it hates. So let me Hold on one second. Let me drop something. How about draw something at here? Hmm. Make it bigger. If some delay is from client to the web servers, I know. So, Rui will come to the web servers first than Scott. No, no. Scott already arrived first. So, my request already arrived first. Yeah. Okay. But if Scott is delayed, Scott also arrived first. Yeah. Assume that Scott [50:00] But there is a long pulse. Let's not call it. Lung pulse from the from the web server to the database or to the memory cache. Long pause from your starting right to committed. Thank you. Okay, okay. Thank you. Um. So in this case, I will send a a time. So such like if Scout do all of do all of this all of this process longer than One second. We will... just click scout out. and other things. Let's redo these things. Okay, so you mentioned something very interesting here. So you basically say, you know, we can time out, right? So we can time out at one second. So because of this, so I think this max throughput you can process is... this kind of request per day. Thank you. Thank you. uh but for one room is not Bye. - So I think you're locked in the whole thing, right? You locked the whole database, right? In the second one, it's locked the whole database. In the second, in the In the section one, lock the whole database. In the section two, we just lock the room Each room has one locker. - Oh, okay. So each room has one locker and each room is gonna be its own queue, is Right? Yes, correct. Okay, yeah, this is gonna be a little bit challenging, challenge to for me to challenge you. Let me think about some fun case here. Thank you. Thank you. Thank you. First of all, this solution is not working, don't be a bad one. This solution is not working, this is a database transaction. This is a database transaction. You can do two different transactions, this is a problem. Uh, Usually, you cross-theta system for transaction. And you can use memory queue to do any problem. I think I have a way to think about the way I would like to think of edge cases, counter cases, I don't like to think of edge. And this system will fill. I feel the more precise, I think. If you have any ideas, you can ask. Now, we can put the room ID on memory. and then the room ID is in memory and then the queue is in queue. for the inclusive request. Thank you. Thank you. Thank you. Thank you. Thank you. Thank you. Thank you. Thank you. Thank you. Thank you. Thank you. I'll put it in the middle of the screen. . Okay, so I think fundamentally the solution to and solution one are same. You just break down the granularity. Previously, the granularity is a whole database. Now the granularity is per rule. Correct. Yeah. So you basically just reduce the granularity to per room. So still, if we if we view from room perspective, so each room you're going to have a queue. And you're going to use this in memory queue. to help you to understand who is coming first and who is coming second. Thank you. Yes. Thank you. As well, we can use the calm time to do it. We cannot use calm time, right? Who is going to provide the calm time? Once the server gets a request, the server will add a time in the package. the server at PyTag, but you'll request them. So, for example, let's give an example. So you may have a book room request. You may have a book room request, and you may have a request one, and you have a request two, right? You have two book room requests, but it's all book room A. Thank you. Thank you. There is no guarantee. It's almost impossible, right? There's two requests. There is no guarantee these two requests will go to the same server. Oh, yes. So this may go to server one and this may [55:00] go to a server tool. And if your server, somehow the server tool may be clock drift. Clock drift a lot. So it means that the clock maybe drift a lot like the B and Two hours. Two hours is slower than It means that all the requests go to server 2.0, you'll never be able to book a room. Request to survey two, we will never be able to book a rule. Yes, but I will talk about if If the request want to book room one, so the request should go to the server one. So OK, OK, request go to serverline is fine. Let's assume that normally you're going to You're going to to root request to server. What if the server crash? Server one crash, it means we will lose all of the requests. We need another server. go to server one's position and do the work for server one. Yeah, so you need to You need to root request, right? Uh... I don't want to request, I want to move also request to another server. I'm not sure the root request is split to different server or root all of the requests to another server. I will select the second one. Thank you. Okay, so still assume this, I think this assumption may not be doable. The reason is that, for example, request from China, so you want to do a business trip. and request to me come from us so are you saying that's no because you want to because you want to make sure the same room always go to the same server so no matter where your request come from you're always going to surveyed using a fixed server. Thank you. So you're not going to take the nearest server, you're going to just fix it to the server. Okay, we want to fix it for the server. Thank you. So basically, the Zoom request partition is based on the Room ID. Partition is based on the Room ID. That's what you're saying? Yes. Thank you. Thank you. Podession based on ROM ID. Thank you. Thank you. Thank you. So... I need to think about what case you need. I think it's a bit of a problem. This board Thank you. Thank you. Thank you. Thank you. Thank you. Thank you. - Okay. So, you're also going to assume that if server ground crash, If server one crash. then all the requests will be asked to plan to retry again, right? Thank you. Yes. Maybe not retry. We just We just delete all the requests if the request already in the server. If the requests do not arrive at the server, we just go to the new server. Thank you. So I need to think through this. So basically, let's assume that you have room booking your class. - Booking a request. So what you'll do is that first server accepts the request. Accept the request, and then, sorry, first is routing to, routing to, uh, routing to server partition. by rule This might be fine. Honestly speaking, I think this might be fine. Okay. Okay. So, I mean, say, just rotate the base on the room ID might be fine. So then Then basically, server accepts your request. and then put into in memory. Putting tool in memory. Q. Okay, so once it's put into in-memory queue, and then you can claim log to the database. which contains room ID. roommate. data. Okay. Mm-hmm. [60:00] your turn, if it is your turn. Okay. Thank you. then you put Aim your jaw. Thank you. Thank you. Mm-hmm. OK, what about read? Let's see. What about read request? In this case, you're not going to-- how are you going to search read requests here? I still need to sync through some write-requests here. In my photo, we have two read requests. So one read request is from, could you see the, I share the screen? One is this one and another is this one. So which read request you ask? No, not a read request before the write, because you lock the whole database. You lock the database on your room, so you don't -- you're not going to have any -- you're not going to have this right screw issue. So this problem in transaction is called a right screw. or phantom write. So you're not going to have phantom write or write school issue, because your local database is very pessimistic locking, but you still need to read, right? You need to get a list of room which is available, so you can see which room you can send a request. I will say, so this locker is just a lock. This locker is just a lock, the bondage. I mean, if I bond it together, it's just a use for Let me write down something. Hmm. It's just the work at here and the work at here. Thank you. So they do not care about the read request if it is a single read. Thank you. Sorry, could you repeat? Yes. So could you see my Google, Google dryer? Yeah. Yes. So we have to to read it here. Why read it read? no right and another one is read and write together. Okay. Okay. So the locker is just the work at the read and write together. OK, so are you going to block rates there? I just block this bond. If you just a single read, it's okay, doesn't matter. So I'm asking when you execute this bundled operation, are you going to block it? Yes, I will block it together. Not... It's a little hard to say. In this couple of time, no one can access database based on this room ID. Okay. Thank you. Okay, let's say this. I think I got something very interesting. I finally come up with something very interesting here. Okay, so, I finally came up with something very interesting here. This is a pessimistic lock. You are very pessimistic lock. Pessimistic lock. There is a problem. You put it in memory, I just used to say it in the middle, because it's a bit complicated. You will put it in memory. You put it in memory, right? First, you said I have a request is claim in memory, is it right? I said request is claim, so I want to write I'll go to the server, right? I'll go to the room. Room A, and then in memory, you can actually lock it in the room A. Right? You can do Room A logs for it. If your server crashed, what happened? My first thought is that these requests are not required. That is the room A, or is it not being locked? Because the lock is in the server. That if it's broken, But if you have room A [65:00] Hvað er hvað kalla? Make sense, OK, this room A be locked, so room A be released, is it? This is okay. That we can see this. Pessimistic locking is the most common problem. That we now room A be locked. 然后呢,你去写database og við átabase. 然后database crash 了,然后你怎么 handle Það er það er það er það. Optimism, luck, eðað er sér. Það er það er það er það. - Ja. - Það er að Timemort og Db Request. Um. Það er það er það. Það er það er það. Það það það það það það það það það það það. Speedness, just your speed, or efficiency. Data base is a lock. Einstund er to-facelocking. Ígæði það er það er að ræst-cum problem. Material conflict var material conflict. Ítri er lokk on index er hættið hættið hætti. Ítri er lokk on room. Ítos þú er a loom á loom. Ítos þú er loom á loom. Háða. Ígert er dyrt að er að hæðað er að hæðað. Ígert er að hæðað er að hæðað. það erum það erum konflikta. það erum það erum konflikta. To face locking的话,是 read和 write 相互 block 的 就是 read and run 是 block 的 [70:00] 影响着,就两个之间是不影响的谢谢大家所以你在写的时候就是好你在写的时候这块的话你实际上是你的performance是two-phase locking 的solution performance 然后甚至比two-phase locking performance 还要糟糕 因为你是用了一个外面的系统来进行优化的但是你的这个实践Solution有点像Lock on Room A这种感觉嗯,所以就是从performance来讲的话是肯定会比较糟糕的,但是crackness角度我现在还真没想到一个方法说,就是crackness 是有问题的。谢谢大家谢谢大家谢谢大家好我再花一分钟时间,我再花一分钟,因为这个时间也花了挺久了,我看我能不能想到一些crackness方面是有问题的地方,如果不能的话,我们就继续看了,路往好了KracknessFormation谢谢大家好我想到一个crackness有问题的地方这个应该是个valid case好 你现在是这样子的 你有两个server server 1和server 2Server 1 和Server Backup 叫 Server 1 Prime这个是 Application Server,你有两个 Application Server这个可以吧然后你第一个request呢,是request呢,是到server1的。然后你要求book的什么呢我们把刚才的上面的也copy下来就是入目万吧啊就是我们吧嗯对第一个request的start server也是bookroom match吧然后呢你这个request呢删到db了然后database开始处理了就是database开始清理什么呢then write 是吧可以吧?然后这时候发生了一个server一 crash 掉了然后这时候就变成AppServer二是主要的server room 1的什么了,就是AppServer eCrime 了然后这时候你有第二个request 来第二个request来谢谢大家谢谢大家谢谢然后呢这个是你自己book同一个room的,但是这个呢,因为你去这个prime外,然后没有你信息了,所以这个也过去了。这个时间可以很短比如说一毫秒之内嗯那你这样的话这两个还是conflict谢谢大家这两个还是conflict的时间不是观念,这个时间可能因为你这个write这个可能这个performance有degradation的话很糟糕,这个可能会花几十毫秒但是这个Server Recover找Prime 1的话,这个可能一毫秒就转化了你这两个会conflict?首先我想问,我没太懂您这个例子,我想知道这个database, read then write,我有写进去吗?你在写的过程中,我们画一个时间图,可能更好理解一点。我们把上面这个叫requestRequest 1好了,下面叫 E' 是吧?嗯。然后 1 是这样子, 1 到了,不能叫 1, 1 的话重复,我们叫什么呢?叫爱好了Request i 然后 i 到了 e-server 是吧?然后开始了这个 db transaction 整个这个是 db transaction谢谢大家DB transaction好可以吧? 嗯,然后Transaction,Transaction,Transaction,Transaction,Transaction,然后Commit谢谢大家是吧可以吧好Commit以后就返回了那我们就是假设这样子好了然后呢这个时候呢这个第二个request就要按按好了这种情况下到1的时候1已经挂了,只能去1'了他去 E' 的时候他是没有这个 lock information 的。所以它会什么呢?它已经transaction三三十二然后你同时两个transaction就可以发生各种各样的事情谢谢观看就是在这两个时间点之间这两个时间点之间E挂掉了,然后E'作为它的backup server好的 我好像明白了好 [75:00] шутку. Ну И в уже в прошлом году, и в прошлом году, и в прошлом году. И второе request就到 E''''. E''''没有 E' информation, и то есть. И мы это называемый таймлайн. Продолжение следует... Продолжение следует... Продолжение следует... Ух. Окей, хорошо. Это крактность, и перформансность. И я наконец-то получил пример. Продолжение следует... Субтитры сделал DimaTorzok Это еще более улучшительный. У меня был в мебельце. Я в исследовании этого строительства, я увидел такой вопрос. Мы в... В этом разнообразии, Вы говорите, что 10КБ уже достаточно. Но я хочу спросить, если мы начали 10КБ, но если вдруг появится значение, то это действительно больше 10КБ. Или в будущем, когда 10КБ не будет больше, то это будет больше 10КБ. Это будет больше, чем 10КБ. Это будет больше, чем это будет больше. Это будет больше, чем это будет больше. Это будет больше, чем это будет больше. Средиземный крышек не будет. Сейчас мы строим большую большую решение, это скалабильность обычно очень скалабильность. Это 15 кг пппо, это действительно очень страшно. Если 15 кг пппо, то можно будет 3 миллиона север. 30000000 сервер, это очень много сервер, это очень страшно. Поэтому это учитывая, что это учитывая, что у нас есть более широкая. Но обычно, на самом деле, на самом деле, все системы строительства, 基本上, на самом деле, на самом деле, на самом деле, на самом деле, на самом деле, Так что это не важно. То есть 3,000,000 заверий тоже можно сделать. Только это, как бы, чтобы сохранить, но это еще не так. Я видел, что Лисия уже встал. И он просто... Это мантаин 4,000-двыксервера. Ну, я хочу спросить, если я начинал 10КБ, и после этого 10КБ не используется? та То есть, если я хочу стоить 100 кб, то я как-то так делаю? Это выгоднее, это выгоднее. Выгоднее, это выгоднее. Это выгоднее, например, 1 мегабайт. Но выгоднее, это выгоднее. - Ты может быть 1 мегабайт, а в большинстве есть 1 кб, и 1 мегабайт, и вы 1 мегабайт, и вы 1 мегабайт, и вы 1 кб. О, хорошо, я понял. Продолжение следует... - Хорошо. Но это не проблема, потому что лок не было никаких проблем. Это не было никаких проблем. Поэтому, если вы хотите использовать лог, то есть это создание системы лог. Не нужно найти создание внешне лог. И также, в этом случае, в этом направлении, что все это все серверы, которые все это сделано. Апп сервер, это можно было бы убрать. Датабаз сервер, это может быть более более详细. И еще больше нужно гарантии, например, датабайс сервер, можно не расширить. И если не нужно, то можно сделать это вверх, то можно сделать это вверх, чтобы сделать это вверх. Примеры. Но в обычном случае Датабейс сервер, вы можете видеть его выводы более высокого. И в транзакции, вы можете использовать его датабейс, который выводит в транзакции. И вот, чтобы повторить все это, потому что мы говорим о трансляция. Это было повторно, как вы создали один лунг, что вы создали один лунг? Локон индекс, и локон room индекс. И это снова создавая локон. В этом месте будет много проблем. Я не думаю, что это не проблема. Я не думаю, что это проблема. Я думаю, что это проблема. Я думаю, что это проблема. Ум. И еще раз, я думаю, что это было очень хорошо. Я думаю, что это было так, что я думаю, что это было так. Я думаю, что это было так. Я думаю, что это было так. Я думаю, что это было так. Я думаю, что это было так. Я думаю, что это было так. Поэтому я думаю, как бы это было, как бы это было. Семьян, он [80:00] 我这个想法确实不对的好的然后我现在的话再go over 一下这个题好了我再go over 一下这个题你建议往上scroll一下我现在的话只有五分钟时间go over 一下这个题好了这个题的话我觉得开始的时候是没有问题的包括这个constraint design计算也是没有问题我计划constraint给的过于大了但是无所谓很多时候就希望给你个大概300个server然后1000个server能藏下的data然后这块的话我们看到这个QPS本身其实是不大的QPS本身是不大的但是这个大的是Data Size这个Data Size过于大了QPS本身很小2K的话或者20K的话你一个Server就处理下来了但是你这个Data Size15个PB的话你如果用MySQL存的话你可能需要3000多个Server一个MySQL500GB的话0.5TB的话你可能需要3000个甚至3000到3000个Server这个conference我给的不太好给的太随便了但是我们去上面看这个题的话我们看这上面这个题的话这个schedule是正常的clude的操作就是正常的create, read, update, delete 操作因为这个transaction要求比较高所以可能可能你还是要用 MySQL database或 SQL database因为它 transaction 要求确实有点高然后如果这个 event 的话其实其他好fills 都比较好 populate就是 meeting room 这个确实比较难然后 meeting room 这块它有一个非常难的地方就是你怎么样去像 Uber 的 meeting room 永远是满的就是我任何时候看基本都没有 meeting room available这个是比较复杂的那Meteorome这块的话有几种方式像Two-Face Locking可以解决然后或者是Material of Conflict也可以解决我周四的时候都会讲一下然后其次的话你可以就是Lock on Index你可以Lock on Room A或者Lock on Time Slot这块的Lock 所有Lock一定要Database的Lock你不要自己搞一个Lock 问题很多就一定要DatabaseLock因为它本来就为这个优化的然后DatabaseLock 比如说SelectRow什么ForUpdate的话你就把那个Role update,就是Log 住了然后就是用Data Base Lock然后最后的话,其实这个Schedule 这块还是有问题的你比如说你Schedule的时候,你Schedule 是Schedule Weekly 是吧然后你这个Meeting Room第一次的Meeting 是没有Conflict那第二次Meeting 有Conflict了那你这块怎么处理,是有一些Business的Challenge比如说你这个Schedule这个meeting是weekly在周一下午weekly at 6点是吧周二下午weekly at 6点你第一周是没有conflict然后到第二周的时候就有人跟你competing这个room然后把这个room已经提前站好了那你应该return什么是return一个partial failure呢还是return一个success但是第二周被fuel掉了是吧这个是有点需要考虑的地方我看google这个calendar处理是只要你第一周的这个room能book到之后不管有多少conflict都是reject你会收到很多邮件说week2roomreject,week3roomreject,week4roomreject所以它会有个这样的邮件或者它可以reserve几周就当你看room的时候会reserve几周这个可能会reject然后这块还有一个非常潜在的问题其实没有讨历到是你这块的readrequest是远远大于writerequest所以你这个read和database的选择的时候你的read和read不应该相互block你需要做non-blocking的readread所以你可能需要一些你的transaction level就是你的isolation level可能需要一些snapshot isolation所以two-face locking基本上这个系统就挂了你如果用two-face locking的话你这个系统基本上performance很糟糕所以很多时候你可能更多的是要material conflict或者是lock on indedex这个话我周四会讲更多一点就是 Two-Face Lock-In 是 Read 和 Read 相互 Block 的你这块的话 Read 很多那你可能 performance 会很糟糕好 这就是这个问题的话几个比较难的考点几个比较难的地方大家有什么想法或者什么 comments我们还有几分钟可以稍微拖持几分钟讨论一下今天讲的是非常难的今天讲的你需要对database有很深的了解然后至少这个什么two-faced login怎么样子transact是怎么样子是要比较深的了解的这个是如果是new-grads或者没有学过database的话还是比较难的我觉得确实的话是有点难的需要很多那个就是我对于可能google calendar不是很了解但是我之前在linkedin这边我们做过Outlook calendar的这些东西我们当时 calendar就是room service我们其实做的是一个non-blocking call如果你add guest的话你是一个blocking call但是你的add room是一个non-blocking他之后具体怎么做不是很清楚但是我想可以比如说我们sending一个卡卡以外然后我们按照那个room来做一个那个hash [85:00] Kafka partition shop. It can ensure the request is a good thing And then it's in DBs. Then there are problems, first person already wrote, second person will go back to the file, then I can just reject it. And And then... - Yes, I think you should use a non-blocking account. I think you should use optimistic locking. You will resolve it. You will reject one. You will reject one. You will reject one. You will reject one. You will reject one. You will reject one. You will reject one. You will reject one. Optimistic Locking is a bit more than that. Google Calendar is Google Calendar is pessimistic lock, you claim it is very little. You will be rejected. You will be rejected. You will be rejected. You will be rejected. And we will be rejected. And we will be rejected. It is a good thing. If you have rejected, you will be rejected. You will be rejected. If you accept the room, you will be accepted. If you accept the room, you will be accepted. It's a little bit too No, they're too busy. You will be able to do the same thing. So in a while, it can be done with the data. If you read it, you prefetched it, this would be more difficult. You prefetched it, you can lock the room or not lock it? - The user is now in which time it is already booked, and you can choose to choose the room. Because now the Outlook is not so much. - Yes, in which time the book is not a problem? But how do you deal with the available room? You read it after all, you can do two people with the same time and book. And then you can reject who? How will reject it? Oh. Right? You have any time. The idea of the计算机 is a relative. You can do one minute. You can do one minute. One minute. You can do one minute. In any time you can add the amount of time to the same time. It's like a common sense of the same thing. You have the amount of time to the same time. You have the amount of time to the same time. When you read it, it's not the same time. When you book it, you can do this. How do you deal with conflict? Actually, you can see that the case is not crash. So we have two-phase locking or lock on index to solve this problem. Thank you. Thank you. So there are other ways to be able to solve this. It's just that you can do this rate and rate is the same operation. Actually, the idea of the thought is that you can do the same operation. Thank you. Okay, this is the problem. Room lock is low, but the volume is very small. So every room... And then that way, it can be. on. Room lock is small, but you have to book today, there are people who book today This is also a good thing. It's just like room and lock. This is also a good thing. It's just like index. This is a good thing. This solution is okay. But from the general perspective, Okay. This is the index. What other questions to do to discuss today? We may need to discuss the transaction. This is a difficult question. Thank you. Thank you. If we haven't, we'll just end this. And then - Okay. - Thank you Scott. You're getting a lot of money. - Okay. Thank you. - Thank you. - I think the design is quite a creative. I'm more confident. I'm more confident in the experience of the experience. But there are some experience I think I need to add some more. [90:00] 对就是比如说这个有一些这个比如说这个怎么样选mojo不应该通过API来想是吧应该通过他这个这个database的性质来学。然后其他的确实有一些经验的东西,比如说我真的在今天面试之前,我真的不知道数据库里面还有这么一个锁的东西。对的,数据库锁,database里面其实它中间经过了几十年的发展,从1970年到现在经过了50年的发展,然后它其实这开Base里面有很多很强大的functionality只不过它这个 MySQL 的 database, SQL 的 database 它发展都是单机发展,就是 single node 的发展,所以它这个在这个 cross node 的时候,或者是 cross distributed system 里面,这个就是很多地方是不适用的,但是它 single node 的这个 transaction是做得非常非常好的,Single Node Transaction Support做得非常非常好,有很多 functionality做得很好。那好那如果这道题我在面试的时候我就是直接跟老师说直接跟面试官说那个就从这个数据库里边有个锁啊就调用这个锁就行了就是这样是吗?对,但是首先你说这个Database是什么样的isolation level,是吧?你是Snapshot isolation read 还是这个问你这道题的面试官其实就想考察你这个database,其他的简单的crude你可以先来解决了,就是这种简单的什么entity的data model design,这种crude design你可以先解决了,这个花不了多久的,就是20分钟就解决了,就是你设计一个什么event crude,设计一个user crude这个就是C-R-U-D,就是Create,Read,Update,Delete,这种end point,这个可以很快解决了,然后,但是它其实考察你主要难点就是你怎么样,看到这些 transaction 所以问你这条题的用户他面试官还是想考察一下你怎么样然后另一方面来讲这个面试题确实对三天工作经验是有点难了点这个需要很久的这个对他Base很深的了解所以比较难一点好的谢谢谢谢拜拜谢谢大家谢谢大家