Uber与DynamoDB弃用哈希取模 架构师借固定分片防御网络过载

[Chinese] Chapter 6: partition — Scott Shi

Scott Shi

分布式系统中最昂贵的瓶颈是网络带宽,强求跨分区强一致性会导致延迟飙升百倍,工程落地必须依靠异步索引与局部有序换取高吞吐与可用性。

AI-assisted summary of [Chinese] Chapter 6: partition — original source. Check the original for full context.

Source published:

Key ideas

  • 数据分区的根本驱动力唯一在于容量瓶颈与横向可扩展性(Scalability),Replication 则解决可用性与延迟;若数据规模未达临界点盲目分区毫无意义,如 Airbnb 截至 2017 年核心数据仍未落地水平分区。 (0:00)
  • 在分布式系统硬件成本排序中,网络带宽与 Socket 连接最脆弱昂贵,因此重平衡(Rebalance)首要铁律是最小化数据迁移;严禁采用取模算法(Mod N),否则节点微调会打乱几乎所有数据映射并引发灾难性网络瘫痪。 (45:00)
  • 面对名人推文单 Key 暴击等极端热点,Twitter 曾通过在推文 ID 后追加后缀强行展开为 100 个副本(写放大 100 倍),使海量读请求被模运算均匀平摊;该方案成立的业务前提是数据具备不可变性(Immutable)。 (20:00)
  • 智能胖客户端(Fat Client)将路由逻辑内置于调用方,端到端延迟较独立路由代理层(Routing Tier)减少一半,但该模式严禁对公网终端开放,仅能运行在数据中心内部受控服务以防拓扑暴露与定向渗透攻击。 (57:35)

Detailed notes

## Background 本视频由作者 Scott Shi 于 2020 年 11 月 29 日发布在 YouTube 平台。根据页面简介,该内容主要围绕《Designing Data-Intensive Applications》(DDIA)第六章的数据分区(Partition)展开深入探讨,并附带了讲座所使用的演示文稿链接。

## TL;DR 讲座系统性地剖析了分布式数据分区的四大核心模块:分区键设计(范围键、哈希键及复合键)、二级索引的权衡(本地索引与全局索引)、分区重平衡(Repartitioning/Rebalancing)以及服务发现与路由机制。讲师深入对比了固定分区、动态分区与按节点比例分区的工程适用场景,并着重警示了静态哈希取模、Rebalance Storm 等常见设计反模式。同时,内容结合了 Kafka、DynamoDB、Uber Schemaless 及 Twitter 等工业界实战案例,阐述了在网络带宽瓶颈与极小概率异常面前,系统设计如何进行务实的性能与可用性妥协。

## Atmosphere & style 分享展现出极为硬核且务实的工业界工程师技术底色,语调沉稳、逻辑严密,带着资深从业者看透架构本质的透彻与犀利。讲师不拘泥于学术概念的教条,频繁穿插对冲基金、大型互联网基础设施的真实轶事与“吐槽”,在通俗生动的比喻中解构复杂的分布式机制。听众互动时针对 Corner Case 的解答充满实操智慧与工程哲思,整场技术讲座给人一种在数据中心一线复盘踩坑实况的沉浸感与通透感。

## Bulletin - [00:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=0s) 数据分区的根本驱动力唯一在于容量瓶颈与横向可扩展性(Scalability),Replication 则解决可用性与延迟;若数据规模未达临界点盲目分区毫无意义,如 Airbnb 截至 2017 年核心数据仍未落地水平分区。 - [45:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=2700s) 在分布式系统硬件成本排序中,网络带宽与 Socket 连接最脆弱昂贵,因此重平衡(Rebalance)首要铁律是最小化数据迁移;严禁采用取模算法(Mod N),否则节点微调会打乱几乎所有数据映射并引发灾难性网络瘫痪。 - [20:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=1200s) 面对名人推文单 Key 暴击等极端热点,Twitter 曾通过在推文 ID 后追加后缀强行展开为 100 个副本(写放大 100 倍),使海量读请求被模运算均匀平摊;该方案成立的业务前提是数据具备不可变性(Immutable)。 - [57:35](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=3455s) 智能胖客户端(Fat Client)将路由逻辑内置于调用方,端到端延迟较独立路由代理层(Routing Tier)减少一半,但该模式严禁对公网终端开放,仅能运行在数据中心内部受控服务以防拓扑暴露与定向渗透攻击。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=0s) 分区让每个 Partition 在单机视角下独立自治并突破单机存储容量极限;相较于单机维度,引入副本(Replication)才会带来网络延迟、进程暂停与多数派共识等极高复杂性。 - [05:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=300s) 现实中“数据生而不平等”,长尾与爆款分布极度不均;若采用静态均分或单纯除以节点数,极易产生单点被瞬间打爆的热点(Hotspot)问题,而纯随机存储又会导致查询退化为昂贵的全集群 Scatter/Gather。 - [10:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=600s) 范围分区(Key-Range)天然支持高效的范围扫描,但高度依赖先验数据分布;若采用单调递增的时间戳(Timestamp)作为分区键,最新写入和高频读取流量将全数砸向最新的单一节点并导致雪崩。 - [10:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=600s) 哈希分区(Hash Partition)通过打碎物理键顺序实现极佳的数据均匀分布,但彻底牺牲了范围查询(Range Query)能力。 - [15:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=900s) Cassandra 与 DynamoDB 采用复合主键(Compound Key)方案,以 Partition Key 经 Hash 路由精准定位物理节点,再以 Sort Key 在单节点内部维持物理有序存储,兼顾了跨机打散与局部范围检索。 - [15:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=900s) 工业工程哲学强调整体吞吐与可用性优先,绝不应为了防御千万分之一甚至极低概率出现的 Corner Case(如 NTP 微秒级时钟漂移、系统死锁)而牺牲常态化系统执行效率。 - [25:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=1500s) 将数据建模为不可变数据(Immutable Data)可消除复杂的并发更新锁与状态竞争;处理推文修改历史时,通过生成新记录追加而非覆写旧记录,对外提供可变语义的同时简化系统底层控制。 - [25:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=1500s) Kafka 本质是专精复制(Replication)与分区(Partitioning)的流系统而非通用数据库;单节点 Topic 写入吞吐上限在 64~128 MB/s 之间,实质是触碰了底层物理硬件介质的吞吐极限。 - [30:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=1800s) 本地次级索引(LSI)仅索引当前分区数据,单点写入极快,但查询必须向全集群发起广播扫描(Scatter/Gather);全局次级索引(GSI)索引跨分区聚合,读取极为精准高效,但会引发严重的写放大。 - [35:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=2100s) 工业界主流系统对全局次级索引普遍采用异步更新(Asynchronous Update),宁可接受短暂的脏读/旧数据窗口,也绝不引入跨分区的两阶段提交(2PC),因为 2PC 会导致请求延迟从 1ms 飙升两个数量级至 100ms 以上。 - [40:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=2400s) 分区再平衡必须同时满足三项严苛硬性指标:数据与负载均匀分布、迁移过程系统持续对外可用,以及网络资源受限下的最小化数据搬迁。 - [47:18](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=2838s) Uber Schemaless 等系统采用固定分区数量(Fixed Partitions)设计(如预设 4096 个独立的 MySQL 分区实例),集群扩容时仅需将部分分区搬迁到新物理节点,但该方案高度依赖架构师对业务生命周期容量曲线的精准预判。 - [50:02](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=3002s) 无法预知体量的通用数据库宜采用动态分区(Dynamic Partitions),数据触碰容量阈值即自动裂变;建表时应引入预分区(Pre-splitting)机制,避免系统冷启动时全量流量集中打在单节点触发频繁分裂。 - [53:30](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=3210s) 全自动重平衡极易在网络短暂抖动或瞬断时引发“重平衡风暴(Rebalance Storm)”,频繁的来回数据搬迁会耗尽宝贵的网络带宽并拖垮整个集群,生产环境需引入抑制机制或人工干预权衡。 - [57:35](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=3455s) 服务发现路由呈现三层演进:P2P 节点自转发导致业务与寻址算力争抢资源;独立代理层解耦了存储与路由逻辑;智能胖客户端性能最优但需维护内网拓扑缓存同步。 - [01:05:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=3900s) 分布式路由知识存在分层解耦:高频变动的“键范围到分区”以及“分区到物理节点”由 ZooKeeper/etcd 集中维护作为单一真理源(Source of Truth),低频变动的“节点到 IP”直接委托给成熟的 DNS 解析。 - [01:07:05](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=4025s) 在拥有中心化协调服务(如 ZooKeeper)的分布式集群中无需引入 Gossip 协议;Gossip 周期性随机心跳收敛速度较慢,而集中式协调服务能实现元数据变更的极速全局收敛。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=0s) 引言与数据分区的核心背景

本讲探讨的主题是数据分区(Partition)。在课程体系的设计上,讲师特意调换了讲解次序,选择先讲 Partition 再讲 Replication,核心原因在于 Partition 的技术逻辑相对简单直观,而 Replication 则是分布式系统中绝大多数复杂问题的根本源泉。Replication 紧密贴合数据本身,一旦引入多副本,就必然伴随数据不一致(Inconsistency)的各种隐患;加之分布式网络固有的 Network Delay、进程暂停(Process Pause)以及不存在全局唯一的真理源头("Truth is defined by majority"),使其成为全书内容最多、工程实现难度最高的一部分。本部分内容对应经典著作《Designing Data-Intensive Applications》(DDIA)的第六章,系统性地涵盖了四个核心技术命题:如何选择分区方式与 Partition Key(例如 Key-Range 分区还是 Hash 分区)、二级索引的设计(Global Secondary Index 与 Local Secondary Index)、集群再平衡(Rebalancing,极易在自主设计时踩坑出错),以及请求路由(Request Routing)。

在业界术语中,Partition 也常被称为 Sharding。例如在 SQL、MySQL、MongoDB、Elasticsearch、SolrCloud 等技术生态中,工程师普遍习惯称其为 Sharding,而在 HBase 中又有各自专门的术语体系,但两者的本质完全相同。讲师特别强调,分区在系统设计中绝对不是必须的,它唯一的根本驱动力是可扩展性(Scalability)。如果一家公司的数据规模不够大,过早进行分区毫无意义。讲师分享了一个真实的行业轶事:在 2017 年与 Airbnb 的基础架构工程师(Infra Engineer)交流时得知,当时的 Airbnb 内部居然还没有对核心数据做 Partition。以此体量的公司在 2017 年尚且如此,足以印证很多场景下数据量并未达到必须分区的临界点。

从逻辑抽象来看,每一个 Partition 本质上都是一个独立的单机数据库。从局部视角而言,每个分区根本没有意识到外部其他分区的存在,完全"活在自己的世界里",认为自己掌控的就是对应表里的全部数据;唯有站在全局视角下,它才只是整体数据的一个切片(例如承担 User Table 的 10%)。分区在架构上可分为两种维度:一种是按表进行拆分的垂直分区(Vertical Partitioning),例如将 User Table 独立置于一个数据库实例,而将 Transaction Table 或 Payment Table 剥离到另一个实例;另一种则是水平分区(Horizontal Partitioning),即单张 User Table 规模膨胀到单台机器承载不下时,将行记录横向切分成若干较小的子集散列存放在不同节点中,本章的核心讨论均聚焦于水平分区。

- **核心讨论主题**:数据分区机制(Key-Range vs. Hash)、二级索引(GSI vs. LSI)、集群再平衡(Rebalancing)与请求路由(Request Routing)。 - **术语对齐**:Partition 与 Sharding 在工业界指代同一概念,MySQL、MongoDB、Elasticsearch 等多称 Sharding。 - **Airbnb 案例**:截至 2017 年其底层数据架构尚未落地 Partition,证明分区并非通用前提,必须由 Scalability 强力驱动。 - **垂直分区 vs. 水平分区**:垂直分区按业务表拆分实例,水平分区将超大单表切分为子集分散到多节点。

```mermaid flowchart TD accTitle: Horizontal Partitioning Concept accDescr: Diagram illustrating how a large database table is sliced into multiple independent partitions across nodes. subgraph GlobalView["全局视角 (Global View)"] Table["用户总表 (User Table)"] end subgraph Partitions["单分区视角 (活在自己的世界里)"] P1["Partition 1: User 1 ~ 1,000,000"] P2["Partition 2: User 1,000,001 ~ 2,000,000"] P3["Partition 3: User 2,000,001 ~ 3,000,000"] end Table -->|"水平切分 1/N"| P1 Table -->|"水平切分 1/N"| P2 Table -->|"水平切分 1/N"| P3 ```

---

[05:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=300s) Partition 与 Replication 的区别与 Hotspot 挑战

在技术架构选型中,很多初学者在选择 SQL 还是 NoSQL 时往往会言必称可扩展性(Scalability),但实际上 Scalability 并非选择数据模型的主要驱动因素,它真正主导的决策是系统是否必须引入 Partition。为了彻底澄清概念,讲师明确对比了 Replication 与 Partition 的差异:Replication 解决的是可靠性与低延迟,它将同一份数据(例如 Key 为 858、Value 为 "We hold these truths to be self-evident" 的记录)完整复制并存放在多个物理 Node 上;即便其中某个节点宕机,服务依然可以通过其他副本对外提供查询,甚至副本可以分散部署在地理位置靠近用户的不同数据中心。而 Partition 解决的是容量瓶颈,当单台物理机无法容纳上亿条或更多的大表时,必须将整体数据集切分为较小的子集(Smaller Subsets),分别安置在不同的 Node 上。在工业界分布式组件中,Apache Kafka 堪称将 Partition 与 Replication 融会贯通的典范,其绝大部分工程难度与设计重心都在于如何精细化地处理两者的协同。

假定系统拥有十亿级别(Billion records)的用户数据,该如何设计分区方案?一种极其典型但绝对错误的做法,就是单纯拿待拆分的目标数量来做粗暴的均分(例如直接除以 10)。讲师指出这种静态数值切分在分布式环境下必然崩溃,后续会展开详细分析其缺陷。除了切分算法,数据分区面临的另一大严峻挑战是热点问题(Hotspot)。所谓 Hotspot,就是数据分片后,大量的读写流量突然集中打向了某一个特定的分区节点,导致单节点被打爆,而其余节点却处于闲置状态。

产生 Hotspot 的根本规律在于“数据生而不平等”("Data is created non-equal"),就如同现实社会中人与人不同一样,数据与数据之间有着天壤之别:某些特定数据极度流行(Extremely popular),而绝大多数长尾数据则几乎无人问津。如果系统不幸将这些超高热度的数据划分到了同一个物理节点,该节点就会瞬时沦陷。虽然理论上采用完全随机分配记录(Randomly assign)的方式能够实现全局流量的理想均衡、防止 Hotspot,但这种做法同样存在致命缺陷——当随机存储的记录被查询时,系统由于缺乏任何路由先验逻辑,只能被迫向集群的所有物理节点发起广播查询(Scatter/Gather),开销难以承受。

- **Replication 的核心目标**:同份数据多副本冗余,增强 Reliability,结合跨机房部署优化客户端访问 Latency。 - **Partition 的核心目标**:突破单机存储上限,切分子集分治管理。 - **Kafka 的典型性**:Kafka 核心系统难点在于高吞吐场景下同时协调 Partition 分片与副本 Replication。 - **Hotspot 成因**:数据访问具有极端倾斜性,直接按数量取除数或静态切分必然导致热点。

---

[10:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=600s) Key-Range 分区与 UUID Hash 分区

面对单表定位与打散流量的需求,第一种经典方案是范围键分区(Partition by Key Range)。这种方式在现实生活中的对应物就是各类多卷成套的百科全书、字典或《十万个为什么》。整套百科全书在编纂时,会将所有词条按照字母或首字顺序排列,例如将词条 A 到 B 编入第一卷,Hodernese 到 Crest North 编入另一卷,每一卷就相当于一个独立的物理分区节点。该方案的优势极为突出:执行范围扫描(Range Scan)或检索相邻键(Adjacent Keys)时效率极高,查询客户端只需根据边界范围锁定 Node 1、2 或 3,便可在局部节点直接顺序拉取目标片段。

然而,范围键分区的软肋在于它必须依赖高度精确的先验知识(Pre-knowledge)。百科全书可以静态统计所有词条的前缀分布,并据此均匀地切出 12 卷;但是在真实的线上数据库中,面对动态注册的用户表,工程师在系统启动前根本无法预测以字母 A 开头和以字母 C 开头的用户究竟会有多少。一旦盲目按照固定字母跨度划分节点,极易引发严重的数据倾斜(Imbalance)进而诱发热点。不仅如此,很多系统会尝试采用连续递增的值(如时间戳 Timestamp)来做 Key Range 分区,这往往会引发灾难性后果:现实业务普遍存在强烈的时间局部性(Recency access pattern),最新写入的数据会被最高频次地读取,而最新读过的数据极可能被反复重读;若按 Timestamp 切分分区,集群所有实时的写流量和高频读流量都会被瞬时砸向承载最新时间范围的那台特定机器,使其瞬间过载。因此,Key Range 分区仅适用于具备充分先验统计信息且访问特征平稳的场景。

为了解决数据不均与倾斜问题,第二种业界推崇的方式是哈希键分区(Partition by Hash Key),常配合 UUID 使用。UUID 即通用唯一识别码(Universally Unique Identifier),其设计初衷是无论在世界的哪台机器上独立生成,都能在数学概率上保证绝对唯一。以经典的 UUID Version 1 为例,它的生成逻辑是抓取物理网卡的全球唯一硬件地址 MAC ID,再加上自 1970 年 1 月 1 日(Unix Epoch)以来的高精度时间戳字符串拼接而成。基于 UUID 进行 Hash 分区时,数据能够以极高的一致性呈均匀分布(Uniformly distributed),例如需要将数据散列到 1024 个分区,只需抽取 UUID 前 10 个 Bit 进行计算即可快速映射。然而这种方式也是一把双刃剑:由于哈希算法彻底打碎了物理键的原生排列顺序,数据完全随机离散分布,系统便再也无法支持高效的范围查询(Range Query)。

- **Key Range 分区优势**:天然支持高效的 Range Scan 与 Adjacent Keys 局部读取。 - **Key Range 分区劣势**:缺少先验分布认知时易倾斜;使用时间戳分区会导致当前节点产生写/读热点集中击穿。 - **UUID v1 生成机制**:底层由网卡唯一标识 MAC ID 结合自 1970-01-01 起算的时间戳拼装保证全球唯一。 - **Hash 分区权衡**:实现数据均匀分布(Uniform distribution),彻底打散存储倾斜,但牺牲了范围查询(Range Query)能力。

---

[15:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=900s) Cassandra/DynamoDB 的复合主键与工程妥协哲学

为了兼顾 Hash 分区的均匀打散与 Key Range 分区的范围检索,工业级分布式数据库如 Apache Cassandra 和 Amazon DynamoDB 采用了精妙的复合主键(Compound Primary Key)架构设计。在此模型下,Primary Key 被拆解为 Partition Key 与 Sort Key 两部分。讲师以此前讨论过的 YouTube 视频互动或 Slack 用户行为表(User Activity Table)为例:系统将 UserID 设为 Partition Key,将 Timestamp 设为 Sort Key。如此一来,同一个用户产生的所有操作事件(无论是观看视频、暂停播放、点赞或踩)都会因为相同的 UserID 被严格 Hash 路由到同一个物理 Partition 节点;而在该节点内部,数据则按照 Timestamp 这一 Sort Key 的物理顺序连续存储。这一设计巧妙地将两项功能解耦:利用 Partition Key 规避了多节点倾斜并精准定位机器,利用 Sort Key 完美保留了范围扫描能力,用户想要拉取“过去 24 小时内的所有行为记录”时,只需单点定位到该 Partition,并在内部快速扫描对应时间切片即可。

在讲授过程中,针对弹幕观众 Kathryn 提出的“时间戳可能会发生 NTP 前跳或后跳进而引发一致性错乱”的技术疑问,讲师借题展开了一场关于系统工程哲学的深度讨论。讲师指出,虽然在底层理论上时间戳前跳或后跳确实存在,但作为日常业务服务端程序员,在编码阶段通常不需要把这类几千万分之一甚至上亿分之一概率的极端情况(Counter Case)过度放在心上,更不应为此牺牲系统常规运转的吞吐与效率。现代操作系统(Modern Operating System)的设计亦是如此:所有主流商业级操作系统在内核调度中几乎都不主动去探测和解决死锁(Deadlock),因为从发生概率与执行效率的权衡(Trade-off)来看,为极小概率事件付出过高的常态化开销是得不偿失的。

讲师随后提及社群内一位攻读分布式系统博士学位(Distributed System PhD)的学员所经历的真实案例:该博士此前为一家顶级对冲基金(Hedge Fund)设计了一套严密周全的容错系统(Fault-tolerant System),但最终因为多层容错机制带来的高延迟(High Latency)而遭到基金公司的彻底弃用;对冲基金最终的解决之道非常粗暴直接——花巨资采购极为昂贵的顶级冗余路由器,一旦坏了就直接用钱换新,直接用充沛的资本消解掉概率问题。讲师感叹,新手程序员("New guys")往往缺乏实战经验,容易过度纠结于几千万分之一的极端 Corner Case 该如何编码防御;而资深工程师深谙工程妥协的精髓,这些理论上的极端边界只会在系统真正发生异常停机(Outage)后,作为事后复盘与排查(Debug)的潜在可能性切入,绝不会在最初编码时就无限牺牲系统设计去迎合它们。随后,讲师也顺便澄清了观众关于 Partition 与 Sharding 差别的疑问:两者完全是同一概念在不同语境下的词汇替换。

- **复合主键(Compound Key)结构**:由 Partition Key 负责将数据路由至特定物理节点,Sort Key 负责在节点单机内部建立有序索引。 - **业务场景拟合**:以 UserID 作为 Partition Key、Timestamp 作为 Sort Key,既能打散不同用户的并发读写,又能实现单用户行为的范围检索(如获取过去 24 小时活跃数据)。 - **工程设计权衡哲学**:对冲千万分之一的极端异常(如时间戳微秒级漂移、死锁),不能以牺牲常态化系统执行效率为代价;操作系统不主动解决死锁、高频金融系统买硬件规避网络中断皆属此理。

```mermaid flowchart LR accTitle: Compound Primary Key Mechanism accDescr: Flowchart showing how Cassandra and DynamoDB use Partition Key to locate nodes and Sort Key to order data within a node. subgraph ClientRequest["客户端请求"] Req["User Activity 数据<br/>(UserID: Scott, Time: 10:00:00)"] end subgraph RoutingStage["分区路由阶段"] PK["Partition Key: UserID<br/>(Hash 计算)"] end subgraph NodeStorage["目标 Partition 内部 (Node 1)"] SK["Sort Key: Timestamp<br/>(内部顺序排列)"] D1["Record 09:59:00"] D2["Record 10:00:00"] D3["Record 10:01:00"] end Req --> PK PK -->|"定位至 Node 1"| SK SK --> D1 SK --> D2 SK --> D3 ```

---

[20:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=1200s) 极端热点应对与 Twitter 的不可变数据 Hack

在超大规模并发场景中,系统往往会遭遇更为极端的 Hotspot。例如在社交平台 Twitter 上,前美国总统川普(Trump)或奥巴马等顶流名人的账号一旦突发热点推文,抑或遇到名人账号集体被黑客篡改的突发事件,全球范围内的读写请求瞬间都会聚集在同一条记录(Same Key)上,瞬时读写压力直接拉满。面对这种单 Key 级别的极端流量倾斜,传统 Hash 分片也无能为力。

对此,讲师介绍了 Twitter 内部曾实践过的一种巧妙的工程变通方案(Hacky approach):通过主动引入写放大(Write Amplification)来换取读流量的高度分散。假设川普发布了一条编号为 1 的推文,系统在其推文 ID 后面追加两位数字(例如补充从 00 到 99 的后缀),在内部物理切片中将这一条推文强行复制(Copy)成 100 份独立的记录,分别标为 100、101、102 一直延展至 199。当川普在系统前端发推时,内部虽然付出了将数据同步写成 100 份的写放大代价;但是全球数亿用户后续读取该推文时,系统便可通过客户端 User ID 取模(Mod 100),将海量读流量均匀分流至 100 到 199 这 100 个不同的物理分区中任意读取一份,从而极其有效地平摊热点压力。

讲师特别指出,Twitter 能够推行此方案的核心技术前提,在于推文在业务形态上属于典型的不可变数据(Immutable Data)——在当时的产品设计逻辑中,推文发出后即不可被二次修改。工程师在架构选型中天然极其偏爱 Immutable Data("Engineers 是非常喜欢 immutable data 的"),因为不可变性消除了绝大部分并发更新一致性层面的复杂死锁与状态竞争。试想如果数据允许随意更改(Mutable),川普将推文内容从 A 修改为 B,为了维护数据的一致性,系统必须同步修改分散在集群各处的全部 100 个副本;一旦更新出现局部失败,普通用户极易读到过期数据(Stale data),客户端为了确认最新状态甚至被迫把 100 到 199 全部拉取扫描一遍,设计便彻底破产。因此这种依赖写放大打散读热点的机制是一次高度受限的产品权衡,不适用于绝大多数常规的数据可修改业务,而在推文这种具备绝对读热点且支持不可变性的极度倾斜场景下,它配合精细的缓存机制(Caching)取得了极高的工程收益。

- **名人单 Key 热点应对**:读写诉求极度集中在极少数 Celebrity 实体上,常规 Hash 分区仍会导致单个 Node 承载超高读负荷。 - **Twitter 写放大分流策略**:将原始推文 ID 后追加 2 位数字,强行展开为 100 个散列副本(写放大系数 100),使读请求通过用户 ID 模 100 均匀摊平。 - **不可变性(Immutability)的红利**:该方案能平稳运转全仰赖数据发推后不可修改的业务特性,避免了多副本更新不一致与产生 Stale Data 的复杂维护问题。

[25:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=1500s) Twitter 修改历史的不可变设计与 Kafka 的吞吐量本质

在处理类似微博或 Twitter 的修改历史(Edit History)时,面对“用户修改原推该如何处理”的问题,最优雅的解法是保证底层数据的不可变性(Immutable)。举个生动的例子,假设特朗普发了一条推文 $E$,当他对该推文进行修改时,系统在可视化层面看起来是更新了原有内容,但在底层实现中绝不要直接修改 $E$,而是创建一条全新的推文记录 $E'$。读取时系统再去判断应该展示原推 $E$ 还是更新后的 $E'$。通过这种将所有数据视作不可变(Immutable Data)的做法,对外呈现出可变(Mutable)的修改语义,既简化了系统并发控制,也天然保留了完整的修改轨迹。

针对“Kafka 是否扮演数据库 Master 角色来控制所有节点”的疑问,讲师明确指出:Kafka 绝对不是数据库,它本质上是一个消息队列(Message Queue)、发布-订阅系统(Pub/Sub)或流处理系统(Streaming System)。Kafka 的核心设计极其聚焦,它在工业界的立足之本就是只做两件事:复制(Replication)与分区(Partitioning)。如果想要彻底理解分布式系统中 Replication 和 Partition 的概念,深入研究 Kafka 是最好的途径。讲师提到自己参加 Kafka 峰会时的见闻,吐槽官方的市场推广与销售团队一直试图把 Kafka 包装(Branding)成一个真正的数据库,但他认为任何真正做过分布式系统的人都会明白,Kafka 在底层定位上根本不可能成为一个通用数据库。

至于 Kafka 单节点的极限处理能力,业界通常关注吞吐量(Throughput)而非单纯的 QPS,因为消息大小和 Topic 分区情况差异极大。Dropbox 曾做过专门的基准测试研究: - 一个单节点的 Kafka Topic,其写入吞吐量大约在 64 MB/s 到 128 MB/s 之间。 - 这个数值其实非常好理解,因为 Kafka 本身的额外开销(Overhead)极低,64~128 MB/s 本质上就是机械硬盘或特定存储介质的物理吞吐量上限。 - 随后讲师总结了前序内容:数据分区主要依赖 Hash Partition 和 Range Partition 两种路由策略。

---

[30:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=1800s) 次级索引(Secondary Index):Local 与 Global 的架构取舍

在分布式存储系统中,DynamoDB 和 Cassandra 的次级索引(Secondary Index)设计极具代表性。讲师特别推崇 DynamoDB,认为它是关系型数据库之外最具革命性创新的系统之一,其精巧的设计理念深刻影响了后续无数 NoSQL 系统的演进。相比之下,GraphDB 或 Document DB 在底层理念创新上并没有达到 DynamoDB 的高度。在 DynamoDB 中,通常基于 Partition Key 进行 Key-Value 维度的点查(如通过主键 191 获取车辆的 JSON 详情),但业务往往需要依据非主键字段(如颜色 `color` 或地区 `location`)检索车辆。为了支持非主键的高效检索,次级索引不可或缺。

```mermaid flowchart TD accTitle: 本地与全局次级索引的架构对比 accDescr: 对比展示 Local Secondary Index 基于单分区索引与 Global Secondary Index 跨分区统一汇聚索引的结构差异

subgraph LSI["本地次级索引(Local Secondary Index / Document Index)"] direction TB P0["Partition 0<br/>数据: [191: Red], [209: Red]<br/>索引: Red -> 191, 209"] P1["Partition 1<br/>数据: [305: Silver], [407: Silver]<br/>索引: Silver -> 305, 407"] end

subgraph GSI["全局次级索引(Global Secondary Index)"] direction TB GP0["GSI Partition (按颜色聚合)<br/>Red -> Partition 0 的 191, 209<br/>Silver -> Partition 1 的 305, 407"] DataP["独立的数据 Partitions<br/>存有原始数据行"] GP0 -.-> DataP end ```

分布式系统构建次级索引主要有两种截然不同的流派: 1. **本地次级索引(Local Secondary Index,亦称 Document Index)**: - **实现机制**:每个 Partition 仅对自身内部持有的数据构建索引,完全不感知其他分区的存在。例如在 Partition 0 中新增一辆红色丰田车(ID 209),只需在本地追加索引项。 - **读写表现**:写入效率极高(只需在当前分区单点更新);但查询代价高昂,若要检索全系统所有的红车,客户端必须向所有分区发起“分散-聚集”(Scatter/Gather)查询,依次把每个 Partition 中的匹配项捞出再汇总,读取性能极低。 2. **全局次级索引(Global Secondary Index)**: - **实现机制**:索引数据独立于主数据分区存储,通常自成一张独立的表或按索引字段本身重新进行全局分区(如将所有红车的索引统一汇聚在某个特定的 Partition 0 中)。 - **读写表现**:读取极其高效,检索红车只需直奔存放红色索引的单点 Partition 即可一次性拿到全局匹配列表;但写入性能较差,在任意数据分区插入一条记录,都必须跨网络跨节点去远端索引分区进行二次更新。

---

[35:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=2100s) 写放大、异步更新与放弃强一致性的工业取舍

当数据表包含多个非主键字段时,全局次级索引会带来严重的**写放大(Write Amplification)**。如果一条记录定义了 10 个次级索引字段,插入一条数据不仅要写入数据本身所在的分区,还必须同时跨网络更新分散在不同分区中的 10 个次级索引节点。写放大幅度与全局次级索引的数量成正比。正因如此,早期的 DynamoDB 对表上的次级索引数量做出了严格限制(如最多允许 5 个),防止过多的次级索引将整个集群的写入性能拖垮。

在更新一致性上,如果数据写入与 5 个全局次级索引更新全部采用强同步方案,单次写入就需要跨 6 个分区做分布式协调,系统延迟将无法接受。因此,DynamoDB 对全局次级索引采用了**异步更新(Asynchronous Update)**策略:主记录写入成功后立即响应客户端,后台异步同步至各索引分区。这一设计直接导致了短暂的“时间差(Stale Data)”窗口: - 记录显示写入成功,但在短时间内通过 Global Secondary Index 可能根本搜不到该记录。 - 若将原本红色的本田车修改为黑色,主键直接点查 191 会立即返回最新的黑色版本;但在此瞬间,通过索引搜索“红车”可能依然能命中它,而搜索“黑车”却还查不到,甚至在索引不同步的间隙用红或黑能同时搜出同一辆车。

针对“次级索引读写是否也走 Quorum 共识”以及“为什么不用强同步两阶段提交(2PC)彻底消除脏读”的提问,讲师给出了深刻的工程剖析:索引读写并不依赖 Quorum,读取次级索引必须接受短暂的旧数据(Stale Data)。若强行要求全局强一致,就必须引入跨分区的两阶段提交(Two-Phase Commit, 2PC)。普通的单分区操作只需要 1 毫秒,一旦套上 2PC,延迟会直接飙升至 100 毫秒以上,性能暴跌两个数量级。在真实工业界场景下,面对千万分之一甚至极低概率出现的瞬时脏读边缘情况,绝不可能为了学术上的完美而牺牲系统整体的吞吐量与可用性。

---

[40:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=2400s) 学术与工业的差异,以及分区再平衡(Rebalance Partition)的硬性指标

谈到系统设计哲学,讲师强调了工业界与学术界的巨大鸿沟。学术界极其偏爱 Haskell 或 OCaml 这类优雅的函数式编程语言,但在整个工业界中,除了 Jane Street 这类数百人规模的量化对冲基金使用 OCaml 之外,几乎鲜有大规模采用者。学术界热衷于穷尽每一个极端 Corner Case,而在工业界的真实工程落地中,“性能和可用性远大于小概率 Corner Case”。

关于“使用次级索引读取数据是否需要读两次”的问题,答案取决于读取需求:若查询只需拿到主键 ID,从次级索引分区读取一次即可结束;但若要获取整行详情进行数据补全(Enrichment),就必须拿着查到的主键再去原始数据分区读取第二次。这与关系型数据库(SQL)中的索引回表机制如出一辙,性能损耗完全在预期范围内。

随后,讨论过渡到数据库的核心运维机制——**分区再平衡(Partition Rebalancing)**。需要对分区进行再平衡的原因主要有两类: 1. **业务规模扩张(Scale)**: - **QPS 激增**:需要增加新分区以调度更多 CPU 算力进行分流。 - **数据量膨胀**:单节点存储告急,需要新分区引入更多磁盘和内存。 2. **硬件节点故障(Failure)**: - 现代分布式系统普遍搭建在廉价商用节点(Commodity Nodes)之上。根据生日悖论(Birthday Paradox)的统计规律,在大规模数据中心里,服务器宕机是常态,“基本上每一秒钟都会有机器挂掉”,系统必须时刻准备将故障节点的负载转移至健康节点。

在设计 Partition Rebalancing 时,系统必须严格满足以下三项核心要求: - **均匀分布(Evenly Distributed)**:再平衡后,各节点的数据与负载必须相对均衡,绝不能出现部分节点倾斜撑爆、部分节点完全空置的情况。 - **服务持续可用(High Availability)**:在数据迁移和重平衡的整个过程中,数据库必须始终保持在线,不可中断读写请求。 - **最小化数据搬迁(Minimize Data Movement)**:绝不能移动超出必要范围的数据。例如集群原先有 9 个或 10 个节点,新加入 1 个节点时,理想情况下只需迁移约十分之一的数据,严禁为了扩容而将全量数据全盘重写一遍。因为在数据中心与分布式数据库中,CPU 算力最廉价,内存尚算昂贵(尤其在做 Spark 计算时),但**网络带宽始终是最宝贵、最脆弱的瓶颈资源**。

[45:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=2700s) 硬件资源成本排序与 Repartition 的大忌:为何 Mod N 是绝对错误的

在数据密集型(data-intensive)系统设计中,各种硬件资源的价值是完全不对等的。如果给系统的硬件资源排个序,最不值钱的其实是磁盘(disk),其次是 CPU,再往上是内存(memory),而最昂贵、最值钱的资源当属网络(network)。在网络层面上,最宝贵的资源就是网络 Socket 和网络带宽(bandwidth)。因此,当系统进行数据重平衡(partition rebalance)将数据从 Node A 搬迁到 Node B 时,首要铁律就是绝不能搬移不必要的数据(unnecessary data),因为不必要的数据迁移会给昂贵的网络带来极其高昂的额外开销。

在探讨如何正确做分区之前,必须先明确什么做法是“绝对错误的”。很多初学者容易想当然地按分区的数量直接取模,例如一共有 10 个分区就执行 `hash(key) % 10`,ID 为 1 就进分区 1,ID 为 12 就进分区 2。这种做法在分布式存储中是致命的。如果分区数量每次翻倍(乘 2、乘 4)或许还能接受,但如果分区数量从 9 变成 10,由于 9 和 10 是互质的,取模的结果会导致几乎所有现存数据的位置全部被打乱并重新映射。这意味着一旦增加分区,绝大多数数据都不得不通过网络重新挪动一遍("move data more than necessary"),这在生产环境下会引发灾难性的网络过载。

[47:18](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=2838s) 固定分区数量设计(Fixed Number of Partitions)与 Uber Schemaless 的架构实践

业界应对这一问题的成熟方案之一是“固定分区数量(fixed number of partitions)”,像 Riak、Elasticsearch、Voldemort 以及 Uber 自研的 Schemaless 数据库均采用这种设计。以 Uber 的 Schemaless 为例,系统一上线就将整个数据库预先切分为固定的 4096 个分区(partition)。在架构实现上,这 4096 个分区的每一个本质上都是一个独立的 MySQL 数据库实例。初始阶段业务的读写负载可能没有那么庞大,集群可能只需要 1000 个物理节点,平均每个节点承载大约 4 个分区实例。

在这种模式下,分区的总数(4096)始终恒定,变化的是物理节点的数量。当集群需要扩容,比如从 4 个节点扩充到 5 个节点时,只需要从原先每个节点上各挑出 1 个分区迁移到新加入的节点上,其余大部分分区在原节点上纹丝不动。随着分区被稀释到新节点,单机共享资源的压力降低,系统便能容纳更大的数据体量,并增加 CPU 算力来承载更高的 QPS。然而,这种设计的难点在于架构师必须对业务用例(use case)有极深、极精准的理解。Uber 在其官方博客中明确指出,他们基于对数据量和流量增长曲线的深度评估,认定 4096 个分区足以支撑 Uber 未来 20 年甚至 30 年的业务扩展。他们甚至精确预估了初期的资源利用率,例如每个分区起步时平均承载 200MB 到 1GB 的数据,从而在设计之初就清晰规划出十年后的容量版图。

[50:02](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=3002s) 固定分区的局限性与动态分区(Dynamic Partitions)

固定分区虽然省心,但它高度依赖“先验知识”(pre-knowledge)。如果你面临的场景是像 AWS RDS 这样的通用云数据库服务,你根本无法预知最终客户的业务形态、数据规模以及 QPS 峰值。如果盲目将分区数定得过高,例如设成 10 万甚至 100 万个分区,由于每个分区背后都是一个独立的 MySQL 实例,其元数据与系统开销将不堪重负;而如果预设过少,未来业务爆发时再去缩减或拆分分区的工程代价极高,往往需要工程师花费一两个月甚至更久去介入修复。

为了解决未知容量的问题,另一类策略是“动态分区(dynamic partitions)”,其原理与 AVL 树或 B 树的分裂合并机制高度相似。在这类系统(例如 HBase)中,系统初期可能只分配少数几个分区,随着数据不断写入膨胀,一旦某个分区的数据量跨过了设定的阈值(例如 HBase 早期的 10GB 阈值),该分区就会自动分裂成两个新的分区。基于 Key Range 的分区设计,在逻辑结构上非常类似于线段树(Segment Tree)。在实际建表时,为了防止最初仅有 1 个分区导致写入全部堆死在单节点上,通常会结合“预分区(pre-splitting)”机制,一上来先划出 3 个、5 个或 10 个分区,从而避免数据刚导入时出现剧烈的频繁分裂与低效重分配。

```mermaid flowchart TD accTitle: Dynamic Partitioning Split Flow accDescr: Diagram showing how a partition splits into two once it exceeds the size threshold.

subgraph Grow["数据增长阶段"] A["初始分区 (Partition 1)<br/>Key Range: [A - Z)"] -->|"数据不断写入"| B{"容量是否超出阈值?<br/>(例如 > 10GB)"} end

subgraph Split["动态分裂阶段"] B --"未超出"--> A B --"超出阈值"--> C["分裂为两个新分区"] C --> D["Partition 1a: [A - M)"] C --> E["Partition 1b: [M - Z)"] end ```

[53:30](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=3210s) 按节点比例分区与自动化运维陷阱:Kafka 的 Rebalance Storm

除了固定分区和动态分区,第三类分区方式是让分区数量与节点数量成比例(Proportional to nodes),Cassandra 以及采用 Ketama 一致性哈希算法的系统常采用此方案。在这种设计下,每个节点分配固定数量的分区(例如每台节点 3 个分区),1000 个节点就有 3000 个分区。当集群新增节点时,系统会随机挑选现有的若干个分区进行分裂,并将分裂出来的一部分数据挪到新节点上。

在分区重平衡的具体落地中,DevOps 领域有一个非常经典的技术抉择:重平衡究竟该做成全自动的、全手动的,还是折衷方案?全自动平衡看似优雅,却极其容易引发所谓的“Rebalance Storm(重平衡风暴)”。以 Kafka 为例,其内部采用全自动的 Rebalance 机制。在分布式网络环境中,某个节点的网络出现短时间抖动或抖断(例如断开 3 秒,1 分钟后恢复)是极为常见的现象。当该节点失联 3 秒后,集群误以为它已经宕机,立刻触发自动化 Rebalance,将挂载在其上的分区数据重平衡分配给其他存活节点;然而一分钟后该节点网络恢复、重新上线,集群又不得不把数据重新迁回该节点。这种反复震荡不仅耗尽后台 CPU 资源,更持续侵占了最昂贵的网络带宽,导致整个系统的吞吐性能瞬间崩塌。去年的 Kafka Conference 上,针对 Rebalance Storm 的优化是最核心、讨论最火热的话题之一。因此,系统往往需要在全自动与全手动之间寻求权衡:简单的网络闪烁可能需要引入抑制机制或人工干预介入,避免系统陷入死循环而无法提供正常服务。

[57:35](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=3455s) 服务发现(Service Discovery)的三种架构演进与权衡

当数据被切分并在多节点间完成平衡后,系统迎来了最后一个核心挑战——服务发现(Service Discovery):当一个包含用户数据的表被打散在 3 个节点上时,客户端如何知道某个特定用户(例如 Scott)的记录究竟存在哪一个节点上?在微服务和分布式系统中,解决这一问题主要有三种经典路由架构:

```mermaid flowchart LR accTitle: Three Architectures for Service Discovery and Routing accDescr: Shows Peer-to-Peer routing, dedicated routing tier, and smart fat client routing.

subgraph Option1["方案一:P2P 节点内省 (Peer-to-Peer)"] direction TB C1["Client"] --> N0["Node 0<br/>(处理请求/转发)"] N0 -.->|"转发至目标"| N2["Node 2<br/>(数据实际所在节点)"] end

subgraph Option2["方案二:独立路由层 (Routing Tier)"] direction TB C2["Client"] --> RT["Routing Layer<br/>(从 ZooKeeper 读取元数据)"] RT --> N_Target["Node 2<br/>(存储节点)"] end

subgraph Option3["方案三:智能胖客户端 (Fat Client)"] direction TB C3["Internal Fat Client<br/>(缓存全量元数据)"] -->|"直接单跳请求"| N_Dest["Node 2<br/>(目标数据节点)"] end ```

- **方案一:Peer-to-Peer(去中心化节点自路由)** 每个数据节点本身都掌握全局其他节点的映射信息。客户端可以随机访问任意节点(例如 Node 0),Node 0 查询本地映射发现数据在 Node 2 上,便代为转发或指示重定向。这种方式类似一致性哈希在 Dynamo 中的 P2P 实践,缺点在于数据节点的业务处理资源(CPU/QPS)必须和元数据寻址(Lookup)逻辑相互竞争抢占资源。

- **方案二:独立路由层(Routing Tier / Proxy Layer)** 在客户端与存储节点之间架设一层独立的路由代理(Routing Layer),客户端统一把请求发给路由层,由路由层判断分发至目标节点。这种架构将 Lookup 算力与底层数据存储彻底解耦,两层可以各自独立扩缩容与优化。在工业级数据中心内,该路由层通常通过 ZooKeeper 或 etcd 这类全局配置中心(Global Configuration Service)来监听并维护最新的 `Key Range -> Partition -> Node` 映射关系。

- **方案三:智能胖客户端(Smart / Fat Client)** 将路由寻址的逻辑直接内置到客户端内部,客户端在发起请求前就已经计算出目标节点,直接跨网络打到对应节点的端口上。在亚马逊 DynamoDB 的经典论文中曾深入对比过方案二与方案三:方案三省去了中间路由代理的跳转开销,其端到端网络延迟(Latency)仅有方案二的二分之一。

然而,演讲者特别强调,方案三中的“Client”绝不能是外网普通的公网终端用户或 Consumer,它必须是运行在自身数据中心内部的受控服务(例如内部 Payment Service 或 Login Service)。如果让外部不可信的公网客户端拿到全量存储节点拓扑,无异于暴露系统的安全漏洞(Security Loophole),黑客可借此直接对特定底层节点发起定向攻击(例如黑入特定社交账户)。此外,胖客户端本地缓存的分区拓扑必须付出额外的同步成本,确保持续与元数据服务保持同步(In-Sync),一旦系统发生 Rebalance 导致缓存过时(Out-of-Sync),就必须依靠即时的元数据补偿更新机制来修正。

[01:05:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=3900s) 服务发现与 ZooKeeper / etcd 的元数据管理

在分布式系统分区寻址的实现中,ZooKeeper 承担着核心的元数据存储职责。如果将路由知识(Routing Knowledge)进行细分,可以拆解为两个层级的映射:首先是从 Key Range(键范围)到具体的 Partition(分区),其次是从 Partition 到实际承载它的 Node(节点)。这两个动态变化的对应关系,都是保存在 ZooKeeper 这种分布式协调系统之中的。与之相对的是从 Node 到具体 IP 地址的映射,由于物理或虚拟节点的 IP 地址变动频率远没有分区和键范围分裂、迁移来得频繁,因此这部分映射通常直接委托给成熟的底层 DNS(Domain Name Service)来解析处理。

```mermaid flowchart LR accTitle: 分布式分区寻址与元数据存储架构 accDescr: 示意键范围到分区、分区到节点由ZooKeeper维护,而节点到IP由DNS解析

subgraph ZK["ZooKeeper / etcd (Source of Truth)"] KR["Key Range"] --> P["Partition"] P --> N["Node ID"] end subgraph Net["底层网络解析"] N -->|"DNS 解析"| IP["IP Address"] end ```

这种机制实际上构成了服务发现(Service Discovery)的核心底座,其唯一的“真理来源”(Source of Truth)就是 ZooKeeper。不论是路由元信息的初始化还是实时更新,系统内的所有节点与路由组件都会向 ZooKeeper 进行同步。一旦某个数据节点宕机(Down),集群监控便会将最新的状态变更迅速刷写至 ZooKeeper,确保它始终掌握最新、最准确的节点分布全局视图。

类似的设计思想在云原生生态中同样广泛存在。例如 Kubernetes 自身也提供了完整的 Service Discovery 功能,而深入其底层实现可以发现,Kubernetes 本质上是在 etcd 的基础上封装了一层扩展功能的 Wrapper,底层依然重度依赖 etcd。etcd 与 ZooKeeper 类似,同属于分布式一致性协调服务(Consensus Service),也是分布式架构中用于直接替代 ZooKeeper 的常见工业级方案。

- **动态映射与静态映射分层**:Key Range 到 Partition,以及 Partition 到 Node 的映射属于高频变更,由 ZooKeeper 集中存储;Node 到 IP 地址相对低频,交由 DNS 负责。 - **Source of Truth**:ZooKeeper 充当路由知识的单一信任源,节点状态变更或故障下线第一时间向其同步更新。 - **一致性协调服务选型**:Kubernetes 的服务发现本质是基于 etcd 的封装扩展,etcd 是 ZooKeeper 的经典直接替代品。

---

[01:06:20](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=3980s) 第六章 Partition 核心内容回顾与总结

在进入答疑前,讲者对本讲“分区(Partition)”的整体脉络进行了全面复盘。整堂课主要围绕四大核心板块层层展开:

第一块是分区键(Partition Key)的设计与表现形式。系统介绍了业界常用的几种划分策略,包括基于键范围的 Range Key、基于哈希散列的 Hash Key,以及将两者结合使用的 Compound / Composite Key(复合键),分析了它们各自适用读写模式的优劣。

第二块是二级索引(Secondary Index)在分区架构下的构建模式。重点剖析了全局二级索引(Global Secondary Index)与本地二级索引(Local Secondary Index)的运作逻辑,对比了两者的跨分区查询开销与写入更新代价。

第三块聚焦于复杂的分区重平衡(Repartitioning)。很多工程师在刚开始独立设计分布式系统时,“基本上大家刚开始自己设计的话都会把 Repartition 设计错”,这是业界最容易踩坑的重灾区。课上特别对比了哪些重平衡做法是正确的、哪些做法属于典型反模式及其带来的灾难性后果。

第四块则是服务发现与路由(Service Discovery)。系统拆解了客户端直接寻址、经由路由层代理、由数据节点重定向转发等多种服务发现架构及元数据的一致性保障。

- **Partition Key**:Range Key、Hash Key 与 Composite/Compound Key 的选型与考量。 - **Secondary Index**:对比 Global Secondary Index 与 Local Secondary Index 的写入与跨分区查询开销。 - **Repartition**:强调架构设计的常见陷阱,剖析正确与错误的重分区实现方案。 - **Service Discovery**:解析客户端路由、代理层转发与协调中心驱动的四大服务发现范式。

---

[01:07:05](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=4025s) 答疑:Gossip 协议与强一致性协调服务在路由上的对比

在问答互动环节,有听众提出疑问:第一种由节点自身做路由转发的方法是否可以通过 Gossip 协议来实现?

针对这一问题,讲者明确指出,在中心化或具备协调集群的架构中,“没有必要用 Gossip 实现”。只有在纯粹无中心(Peer-to-Peer)的网络拓扑中,Gossip 协议才是必需品;而在拥有 ZooKeeper 等协调服务的集群中,节点完全可以直接与 ZooKeeper 交互更新视图。

讲者举了一个具体的交互示例:假设 Node 0 需要查找名为 `"foo"` 的数据,它首先向 Node 2 发起请求询问数据是否在此。如果 Node 2 上刚好存在,则请求直接成功返回;如果 Node 2 发现数据已经因迁移不在本地了,Node 2 会告知 Node 0 数据已失效并建议其更新本地缓存路由,此时 Node 0 直接向 ZooKeeper 查询最新的元数据即可完成更新,流程简洁明了。

如果硬要引入 Gossip 协议,从可行性上讲系统当然也能跑通,但其致命缺陷在于“数据收敛(Data Convergence)就会慢一点”。Gossip 依赖节点间随机的周期性心跳探测,元数据更新在集群各节点间的收敛存在明显的网络延迟窗口;而借助 ZooKeeper 这类集中式一致性组件,元数据变更能够以极快的速度完成全局扩散与收敛。随后,在确认大家没有进一步的技术疑问后,讲者向全场听众致谢,正式结束了本次分享。

## Notable quotes > [00:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=0s) Truth is defined by majority — Scott Shi

> [05:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=300s) Data is created non-equal — Scott Shi

> [20:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=1200s) Engineers 是非常喜欢 immutable data 的 — Scott Shi

> [40:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=2400s) 基本上每一秒钟都会有机器挂掉 — Scott Shi

> [40:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=2400s) 网络带宽始终是最宝贵、最脆弱的瓶颈资源 — Scott Shi

> [45:00](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=2700s) move data more than necessary — Scott Shi

> [01:06:20](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=3980s) 基本上大家刚开始自己设计的话都会把 Repartition 设计错 — Scott Shi

> [01:07:05](https://www.youtube.com/watch?v=XVYBN9v3ml4&t=4025s) 没有必要用 Gossip 实现 — Scott Shi

## Mentioned Designing Data-Intensive Applications, MySQL, MongoDB, Elasticsearch, SolrCloud, HBase, Airbnb, Apache Kafka, Apache Cassandra, Amazon DynamoDB, Slack, YouTube, Twitter, Dropbox, Riak, Voldemort, Uber, Uber Schemaless, AWS RDS, ZooKeeper, etcd, Kubernetes, Jane Street, OCaml, Haskell

## Video description In this video, partition is discussed. the link to the slide could be found:

https://docs.google.com/presentation/d/11vxwuIQ9-rU-6npXLQ_1i4__sPyGyCdWoKy9YN9jUus/edit#slide=id.p

please subscribe and leave some commends if you like it.

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

中文简报

Uber与DynamoDB弃用哈希取模,架构师借固定分片防御网络过载 分布式系统中最昂贵的瓶颈是网络带宽,强求跨分区强一致性会导致延迟飙升百倍,工程落地必须依靠异步索引与局部有序换取高吞吐与可用性。 讲座系统性地剖析了分布式数据分区的四大核心模块:分区键设计(范围键、哈希键及复合键)、二级索引的权衡(本地索引与全局索引)、分区重平衡(Repartitioning/Rebalancing)以及服务发现与路由机制。讲师深入对比了固定分区、动态分区与按节点比例分区的工程适用场景,并着重警示了静态哈希取模、Rebalance Storm等常见设计反模式。同时,内容结合了Kafka、DynamoDB、Uber Schemaless及Twitter等工业界实战案例,阐述了在网络带宽瓶颈与极小概率异常面前,系统设计如何进行务实的性能与可用性妥协。 数据分区的根本驱动力唯一在于容量瓶颈与横向可扩展性(Scalability),Replication则解决可用性与延迟;若数据规模未达临界点盲目分区毫无意义,如Airbnb截至2017年核心数据仍未落地水平分区。 (0:00) 在分布式系统硬件成本排序中,网络带宽与Socket连接最脆弱昂贵,因此重平衡(Rebalance)首要铁律是最小化数据迁移;严禁采用取模算法(Mod N),否则节点微调会打乱几乎所有数据映射并引发灾难性网络瘫痪。 (45:00) 面对名人推文单Key暴击等极端热点,Twitter曾通过在推文ID后追加后缀强行展开为100个副本(写放大100倍),使海量读请求被模运算均匀平摊;该方案成立的业务前提是数据具备不可变性(Immutable)。 (20:00) 智能胖客户端(Fat Client)将路由逻辑内置于调用方,端到端延迟较独立路由代理层(Routing Tier)减少一半,但该模式严禁对公网终端开放,仅能运行在数据中心内部受控服务以防拓扑暴露与定向渗透攻击。 (57:35)

Reading summary

讲师 Scott Shi 指出,数据分区(Sharding)的唯一驱动力是突破单机容量瓶颈的可扩展性,其实践精髓在于结合访问模式在数据均匀打散与范围检索之间做权衡,并以最小化网络开销为原则设计系统。 分区键选择与极端热点的工程折中 (10:00) 范围键分区(Key-Range)利于范围扫描,但缺乏先验分布认知时极易倾斜,若使用递增时间戳会导致最新写入节点被打爆;哈希分区(Hash Key)配合 UUID 能实现均匀分布,却彻底丧失了范围检索能力。Cassandra 与 DynamoDB 采用复合主键机制,将主键解耦为用于跨节点路由打散的 Partition Key 与用于单机节点内连续物理排序的 Sort Key,兼顾了路由均衡与局部范围查询。 面对顶流名人的单 Key 极端读写倾斜,常规 Hash 依然会击穿单节点。Twitter 曾通过主动引入写放大来换取读负载均衡:在发推时将推文复制 100 份并追加后缀,读请求则按用户 ID 取模分流至不同分区读取。该方案的成立严格依赖于推文业务的不可变性(Immutable Data),避免了多副本并发更新引发的脏读与复杂并发控制。 次级索引设计:本地与全局的读写权衡 (30:00) 在非主键检索场景下,本地次级索引(Local Secondary Index)由各分区独立维护内部数据的索引项,写入成本极低,但查询时必须向全部节点发起开销高昂的“分散-聚集”(Scatter/Gather)广播;全局次级索引(Global Secondary Index)将索引集中重新分区,单点查询极快,但写入时会带来跨节点的网络写放大。 为了保障高吞吐与低延迟,工业级系统普遍放弃强一致性。DynamoDB 对全局次级索引采取异步更新策略,容忍短暂的脏读或旧数据(Stale Data)窗口。讲师强调,强行使用两阶段提交(2PC)维护强一致会将原本约 1 毫秒的操作延迟拉升至 100 毫秒以上;在真实工程落地中,系统绝不会为了学术上的极端边界而牺牲整体吞吐与可用性。 再平衡策略与规避重平衡风暴 (45:00) 分布式系统硬件资源中网络带宽最为昂贵,因此分区再平衡(Rebalancing)的首要铁律是最小化数据搬迁。初学者常犯的取模算法(如 hash(key) % N)在节点增减时会导致几乎全量数据重新映射,引发灾难性网络过载。成熟方案包括固定分区数(如 Uber Schemaless 预设 4096 个独立 MySQL 实例,仅迁移节点间的分区归属)、动态分裂分区(如 HBase 达到 10GB 阈值后分裂并结合预分区),以及按节点比例划分。 运维层面需警惕全自动再平衡的陷阱。Kafka 在节点网络发生数秒短时间抖动时,自动化机制容易误判宕机而触发全量数据迁移,待节点恢复后又重新迁回,从而诱发吞吐暴跌的“重平衡风暴(Rebalance Storm)”,因此生产环境往往需要引入抑制机制或人工干预。 请求路由范式与元数据一致性协调 (57:35) 数据切分后的请求路由包含三种范式:P2P 节点内省重定向、独立路由代理层,以及内部智能客户端(Fat Client)。智能客户端因省去代理层单跳使得端到端延迟降低约二分之一,但仅限于数据中心内受控服务使用,若暴露给公网会带来安全隐患且面临拓扑失效的同步成本。 在路由元数据管理上,系统进行了动静分层:从 Key Range 到 Partition、再从 Partition 到 Node 的高频动态映射保存在 ZooKeeper 或 etcd 这类强一致协调服务中,作为全局唯一真理源(Source of Truth);Node 到物理 IP 的低频解析则交给 DNS 处理。相比收敛延迟较长的 Gossip 协议,基于中心化协调服务能显著加速路由视图变更的全局同步。 分布式分区的架构落地并非追求学术上的无缺陷模型,而是持续在硬件成本(特别是网络开销)、可用性与业务模式之间进行妥协。工程师应当立足于数据规模的真实临界点,善用不可变性与弱一致性来消解高并发瓶颈,避免为了小概率异常过度牺牲常态性能。

Source quotes

[0:00] Scott Shi: Truth is defined by majority [5:00] Scott Shi: Data is created non-equal [20:00] Scott Shi: Engineers 是非常喜欢 immutable data 的 [40:00] Scott Shi: 基本上每一秒钟都会有机器挂掉 [40:00] Scott Shi: 网络带宽始终是最宝贵、最脆弱的瓶颈资源 [45:00] Scott Shi: move data more than necessary [66:20] Scott Shi: 基本上大家刚开始自己设计的话都会把 Repartition 设计错 [67:05] Scott Shi: 没有必要用 Gossip 实现

Original transcript

[0:00] 今天讲这个replication今天讲replication Sorry,今天讲 partition.我是把这个顺序改了一下我是把这个顺序改了一下,就是先讲partition,再讲replication,因为partition相对简单。Partition 相对比较简单的一个问题,Replication 是所有,我觉得是基本上所有distributed system 相对比较出现各种问题的源泉之一啊实际上就是replication 基本上是主要源泉了,因为这个replication 是和data相关的。你data会有inconsistency,然后data会有各种各样的问题,当你有replication 的时候。然后其他的问题就是distribute system,其他的问题我们之前也讲过,就比如说那个network delay然后你出现了 process 、pause 啊然后你没有 single point to truth 啊,truth is defined by majority这也是distribute system中起到其他比较大的问题,但replication这个实际上是和data相关的计划是distribute system有各种各样的原因之一,也是最难的一部分,效率比较难的一部分,内容比较多。但是partition的话比较简单,所以我就先讲partition这部分。到时候我们讲完 partition 以后再讲 replication然后这个呢是Partition是这个Design Data Intensive Application的第六章然后他这块呢主要讲的是就是这里面这张其实涵盖了三个问题第一个问题是怎么样的partition 是吧?你可以用Krange partition 还是用Hash partition不同的 service 也实践了不同的方式第二个的话就是 secondary indexSecondary Index是有这个Global Secondary Index和这个Local Secondary Index,这是其中很大的一部分然后rebalancing也是一个问题,就是这个rebalancing很容易,如果你从来没有想过rebalancing的话,自己设计rebalancing非常容易设计错最后的话是Request Routing,所以这个partition是有四个问题啊,就是怎么样做partition,怎么样选partition k,怎么样做index,rebalancing,还有这个request routing,四个问题。这块我们叫Partition的,但是有时候也叫Sharding,有时候在这个SQL和MySQL叫Sharding,是吧?然后这个MongoDB也叫Sharding,然后在Elasticsearch,SolarCloud都叫Sharding然后在HBase里面有不同的 terminology,但是总体来说呢,partition是相对来说比较没有问题的,sharding其实本身也没有什么问题。嗯Partition不是必须的,但Partition通常原因呢,为什么要做Partition呢?是跟Scalability相关的。这个很多时候,如果你的公司的Data不是很大的话,你没必要做Partition。比如说最最常见的例子就是Airbnb啊我记得我17年的时候跟一个Airbnb的Infra Engineer 聊天的时候他们还没有 Partition2017年,2017年,Airbnb没有parkation你可以想到它的数据量有多小是吧你就看另一方面可以看到这个partition不是必须的partation 是就是scalability,就是driven,就是你因为有scalability的意思,所以它要partition其次的话,每个partition,任何一个partition,其实是自己的一个database。你可以认为每一个partition是没有意识到有其他partition的存在的是吧,就是从,如果我们自己做partition的话,它是没有意识到其他的partition存在的,它其实就活在自己的世界里,是吧,我们就说活在自己的世界里,它认为它自己管的就是这个data里面的所有data,所以就 partition 来说,但是从全局来说,这个 partition 只是它 data 的一部分,是吧,比如说你对 user table 进行 partition 的话,它只是 user table 的一部分就是这个 partition 值 user table 的一部分它具体怎么partation呢?可能是这个user table的1/10%这样子其次的话,partition这块是有多种partition方式的。有一种partition方式就是根据table来partition,是吧?比如说你把user table放上自己的partition里面然后把这个比如说你把这个就是Transaction table或Payment table放到另一个partition data里面partition 里面这个也是一种就是partition的方式好像叫这个叫Vertical partition,有一个Word叫Vertical partition然后我们今天讲的主要是Horizontal partition,什么意思呢?就是User table已经grow太大了,就是User table自己放一个partition已经放不下来,那我们必须把User table break down下来,就是放到不同的这个table里面这个是讲这个Horizontal partition为主的,就是把一个User table break成若干份,然后把它存起来,然后我再重复一下partition不是必须的 [5:00] 要求 partition 的原因是 scalability所以scalability 是你要 partition 的原因然后对吧,我们之前也讲过这个就是选database的时候,到底选SQL还是noseql?然后SQL和NoSQL的选择不应该根据scalability的选择但是 scalability 是决定你要不要 partition 的原因 是吧?所以如果你回答的时候就说我这次选 SQL 和 no SQL你可以说用scatability 但是很多时候scatability不是driven factorScalability-driven factor是说partition的,就是Scalability-driven factor,就是Scalability不是做SQL和NoSQL选择的那个driven factor然后这块的话就是给了一个例子就是partition和replication的不同Replication 是你把一个Datacopy 在不同的机器上,就是copy 多个地方,比如说我们举一个例子,就比如说这个858这个Record,就是KValue,是吧?它K是858,然后Value是Vhold the Truth to be Self-evident,是吧?所以这个replication就是同一份data在不同的node上这个里面这个方框表示node,同一份的两个不同的node上。所以这种情况下它就是node1, replica1和 replica2就是同一份代表不同的node,你知道吗?partition是什么呢?partition是把一个大的table转化成subsid, smaller subset为小table。是 partition在这个partition里面然后后面的话这个KeyRange比较大出于在第二个partition里面然后对于它其中每一份data,然后再replicate一下copy 一下,replicate,copy 一下,这样的话保证其中的一个node,任何一个node,比如说这里面随便任何找一个node,当掉以后这个 service 还是可以继续 serve 这些 document 的因为它首先 replicate 过一遍是吧然后其次的话就是 replicate 过一遍是吧把这个reliability,然后其次的话,这个replicate一般也是在不同的datacenter,这个replicate data center可能也比较close to you,所以你的lentency可能要improve但partition的话就是说一个node存不下来,所以把它切分成不同的小块,smaller subset,存在不同的node上就是replication和partition的区别最好的replication和partition的应用是kafkaKafka是相对来说主要就是handle replication and partition如果看到技术难点的话它大部分时间都在handle怎么样做replication, 什么样做partition所以 Kafka 这个是 replication partition 处理比较多的。好然后说如果你有一个大的,就是一个 big amount data, let's say we use user data,是吧? 我有 billion records,有 user data, 因为是 billion records,所以可能是有那你打算怎么样partition呢?那一个错误的做法是什么呢?就是把这个partition呢根据你要partition的数量来除以10来比如说你要排这层十分你要除以十来做partition 这是错的做法这个做法呢是错的啊,我们稍后会讲为什么是错的,但是这块的话就是说我们partition是需要有一些approach的是吧,就是比如说常见错误的话你就出于10这个是肯定是错的。然后其次的话,partation中可能常见了另一个问题你可能会有 hotspot,什么是 hotspot?就是你partition了以后收到data的read和read都跑到一个一个record里面一个replica里面你把data分成了十份,是吧?分成十份存在十个node里面但你所有的 read 和 write 都跑到其中一个 node 因为这个 data 是 create non-equal 是吧?这个 data 是 create non-equal 就是每个 data 是 data 和 data 是不一样的嘛 就和人和人不一样嘛Data 和Data 不一样的,有一份Data就 extremely popular,有一份Data就 extremely no one want to access所以如果你把 popular data 全部放在一起的话,对于这个 replica 来说只有其中一个replica就非常耗宝,其他replica就没有人访问。那这个是很大的一个问题所以呢,最好的呢,防止hostbot的话,就是这个record是random来防的,就是random来assign但是这个也是有问题的也是有 [10:00] 就是可能也有问题,就可能你即使只有一个record在一个notes上,可能也非常也就是 record 只在一个路上,那个 record 也可能非常 hot我们稍微看一个其中比较hacky的位置,就是Twitter是怎么样用比较hacky的位置来解决这种 hot spot好,然后其次的话就是,嗯,其次的话,当你把这个data进行partition的时候,比如说你现在是user table,你把它partition了100分,那我随便来一个user,比如说user scott,那我怎么知道我的data在哪一个record上,在哪一个弄得上这个的话是需要一些 logic 的所以你如果没有这个 knowledge 的话,你没有这个 pre-knowledge 的话,你可能需要把所有的 node 都 core 一遍所以这个是需要单独处理一下。我们来看第一个partition,就是proposing partition,就是partition by range key,这个最常见的就是百科宝典,就是这个十万个为什么,或者这种英语词条大百科是吧?然后你看的话,它其实就是根据TESC所有的词条,然后根据吃条的range来进行partition的然后它这种情况下比如说A到B是一个第一本书然后从什么从这个什么Hodernese,然后到Crest North是一本书是吧,它就是根据这个来进行partition的,这就是partition by wrench key,然后这个的话是有是有好处的,好处是什么呢?当你 range scan 的时候就非常容易,比如说你要找从 A 到 D 的这个 record,那你就知道从书里面,从 partition node 里面,从 node 1, 2, 3,和 4 的一部分找就可以了,就是当你要认识scan的时候非常容易然后其次的话,当你要fetch一些adjacent key的时候相对比较容易,就是也是rend scan 也是非常容易。但是呢,它难点是什么?就是它难点的话,你需要有 pre-knowledge 像这里面他编书的时候 他可能编的时候是全部编好了 然后根据这个根据这个词的这个前面有多少个字是吧根据它字的数量然后给它把它 chop 成了 12 分但是这十二份我们想做一个database里面,做一个user table进行partition。假设你user table进行partition的时候也是按username进行partition的首先你是没有 pre-knowledge的,你不知道你不知道以 A 开头的人和以 C 开头的人在你的 database 里面到底有多少是吧如果你这个你盲目的说这个我就把C作为一个node,然后剩下来什么,那就把自己人为的划分node,你的node很有可能是imbalanced,是吧?如果是imbalanced的话,那你有可能会还是会有hot spot然后这一点,然后其次的话如果你不用User ID的话,你需要用一个Consistent,就是一个连续不断的值进行value,你可能比如说用timestamp,是吧? 假设另一种可能是用timestamp但如果有time stand的话你会可能会发生另一种情况是什么的就是你所有的read和write都是在最新的那个timestamp node上因为你这个timestamp一般来说常见的data access pattern就是非常常见的data access pattern就是最新写进去的data会被马上读,然后最近读过的data会有可能再被读,就是这个data是有一种recency的read和read access的这个pattern的。所以你最新写进去data你可能会马上被读,然后你在这个data就是有这种,就是这个时间的这个,从时间方面是有一种一定的read和read是有一定的关联的,是吧。所以如果你用timestamp进行partition的话,你还是会有house spot。所以这个range key partition 是缺点和优点是比较明显的所以它比较适合,其实比较适合你有一些pre-knowledge,比如说这样的词作你有 pre-knowledge 的话,完全可以按它 pre-knowledge 按进磁组来进行 partition 这个是还是可以的。然后其次第二种 partition 方式就是用 hash key 来进行 partition 。Hashkate的话就可以用UOID来进行partition如果这块大家不懂的UOID的话要search一下UOIDUOID可以这么认为就是Unique Collegiate, Universal Unique Ideas这个ID generate 出来的时候是能 guarantee 它不管在哪里 generate 的,它能 guarantee uniqueness叫 Universal Unique Ideas为什么可以 Guarantee Uniqueness 呢?你可以这么讲就是把世界上所有的机器就是你 UUID 都是用机器在能把设置上所有的机器好全部拿出来,把摄像所有机器拿出来,然后把摄像所有机器拿出来以后,它机器有一个ID,Unical Identifier ID,叫macID,把那个 mac id 拿出来然后再把所有的timestamp拿出来,比如说,我们知道现在的uniquetimestamp是从1970年1月1号来进行计时的把所有的器材拿出来,然后在Concordinate Time Stamp, 然后这个值是Guarantee Unique所以这个值呢就是UOID version 1的来源 [15:00] 所以这个UID是,你可以想是不同,不管是在哪一个机器上generate,它是guarantee unique的。因为它可以非常简单地 map 到世界上所有机体组的任何一台,然后再加上从1970年以后到现在的时间到现在的时间 是吧 它可以guarantee uniqueness另一种常见方法就是,比如说对任何一个Record 来说,我生成一个 UOID, Universal Unique ID,然后把这个 UOID 作为 Hashtag 的这个作为hash key 是吧,因为它是个unioid嘛,所以它我们可以比如说,我要hash层4000分,我就看前面的,比如说我要hash层1024分,我就看前面10个bit 是吧然后跟他10个bit决定他具体在哪个partition上就可以这么简单的来做是吧这个UID hash有好处是什么呢?就是它是非常uniform distributed,但是它就不能像Krange partition一样,就是不能做efficient range query了,因为现在data就非常random distributed了。所以这个UID是常见的一种做法,也是现在比较鼓励做的一种做法然后这里面我再想提第三种partition的就是Cassandra的partition在Casanzar里面,它的primary可以定义是partition k加sout kpartition k加sadk我可以举个例子比如说我们在几周前谈讨论YouTube视频的时候,包括我们谈论slack的时候,当我们是用dynamo的时候,我们就说我们可以把partitionKate,就是纯userActivityRecord,我们可以把partitionKate作为userID,所以你所有的user record 因为它partition case 就是user id 是一致的所以你所有的user record 会到同一个partition然后在这个partition里面我们可以把这个sought key作为timestamp因为我们谈论的是user activity table 是吧我们可以把这个sought key作为timestamp所以在这个table里面你可以想象就是所有的user record 比如说scot user record 全部在那个partition 就是全部在那个node1里面然后我所有的user activity不管是看了一个新的video啊,还是我那个中断了一个video,还是readup,还是readdown是吧还是做了不管什么action那我说的Action 都是被SortKate的TimeStamp在这个table里面的所以这是CompoundKate,这种CompoundKate就是搬仓的这种作用吧它是partition key和sort key然后可以加起来作为Primary,可以Guarantee,Unique但是你决定 partition 的那部分是咱们的 partition case 是吧你决定Sought那部分可以用不同的来那个什么这样的话你不是啊它实际上是把两个functionality 给分开了嘛就是兼顾了partition的部分也兼顾了这个Range Search的部分是吧因为你有partition可以和Sought可以Range Search是吧比如说我可以找见我所有的Activity在昨天过去24小时的Activity 是吧我通过的UserID找见我在哪个Partition再通过Time Stamp来找见我过去24小时的Activity但是你这样的话用hash case达到不了的所以可以达到这个功能,所以这个Cassandra和Denymal是有这么一个compound的Primary Kit的设计。好然后我们接着谈这个housebot谢谢大家有两个问题,Kathryn说,Timestamp是不准,可能会前跳或后跳会麻烦,对Timestamp是有可能会前跳和后跳的,但是作为程序员来说你是不考虑那么多的就是很多时候前跳后跳就是非常非常counter case非常非常 counter case,可能千万分之一或一亿分之一。很多时候,程序员是不解决这个问题。那我们知道这个现在所有的操作系统,所有的Modern Operating System是不解决deadlock如果你有Deadlock的话就死进那里,就是Deadlock就是operating system and deadlock是说我不解决的,这实际上是一种你发生的概率和这个efficiency之间这个权衡是吧如果你这个一个事情发生概率是几百万分之一和几千万分之一你是不考虑的基本上是不考虑的然后你更多的是focus on 怎么样是你的机器运算的更 efficient我和刚才就是群里面不是有一个在那边读那个我还不知道他名字,在读那个Distribute System PhD 了,然后他还说他也说他当时给Hedge Fund设计了一个设计了一个 fault tolerant 的一个Hedge Fund的公司设计了一个Fall Tolerant的一个系统但是因为Lintensive太高了所以不用所以他们最后就开始放着买了一个非常贵的router 是吧反正roter坏了以后再换一个roter用钱解决这个问题了所以我觉得new guides来说相对来说new guides [20:00] 经验比较欠缺的后想那我们千万分之一的比如说Devlog会怎么处理那我们是不是应该处理,那怎么处理?我觉得相对来说嗯有更多经验的话,你会知道,如果这个事情发生的概率是千万分之一,很多时候是不处理的。就是你不会牺牲你的一分身谁去处理这个千万分之一或上亿分之一发生的概率的事情所以这是为什么所有的operating system都不考虑所有的operating system是不考虑deadlock你自己写server code的时候是不考虑timestam会前跳或后跳的但这个事情发生了以后,你可以把这个timestamp向前跳或向后跳作为debug的一种就是一种可能性但你写扣的时候是不考虑的还有一种常见的不考虑的东西是我刚刚想到已经忘掉了好谢谢大家谢谢大家好好我一下忘掉了,就是很多东西你是不会考虑的,就是因为它发生的概率太低了,它是理论上的可能性这种东西就是只是说你发生了以后只是说哎这个事情发生了,导致出现了一个 exception,出现了一个 outage,然后我们分析 outage的时候分析可能的原因是用自己来分析的然后第二个问题是partition和sharding有什么区别partition和sharding指是同一个东西啊它只是terminology 不一样只是word 不一样好,那我们就接著聊。在Extreme case的话,你说的Red和Red可能是一个SIM case,是吧?比如说你这个就是Twitter account,说的Red和Red可能是,就是说川普突然发了一个新的Twitter,是吧?前两天不是这样,不管是川普还是奥巴马的Twitter全部被hack掉了,但是假设说所有名人全部发了一个Twitter,是吧?你的这个Rate和Rate都是CMK 你所有的人都要看他的Twitter那你就这个data的volume就非常非常高那这种情况下怎么办呢这个做法应该是Twitter这么搞过的就是Twitter为了这个就是compensate their highest quality workload呢他是怎么做呢?他是把所有的这个K呢,后面加了两个digit,比如说这个Trump,现在Trump是作为一个这个这个Twitter是吧,Trump发了Twitter一页川普发了一个Twitter,就是1是吧,这叫Twitter number是1是吧,那我们在这个1后面加两个0,这边是100,然后系统加两个0,叫100那你读的时候你可以从这个100到199之间读任何一个Twitter 是吧?因为这个因为这个Twitter读了,你发出去的时候不可以modify的,所以这个Twitter是我们叫这个设计叫immute form,就是你发出去Twitter以后你是不能modify 是吧?所以你读100到199任何一个Twitter都可以但是写的时候,川普写的时候,在推特写的时候,在系统内部的时候,他其实是把一copy 到 100, 101, 102 到 199所以它这个写的 amplification 是 100但是他读的时候你从任何一个100到199之间读都可以所以这种方法是可以有效的减少这个case good的那你怎么决定,可能会有人问,那你怎么决定从100到199,哪一个partition里面读,从哪一个Twitter里面读,你可以把,比如说你有User有UID是吧,你可以把UID的最后100个数,然后磨100是吧,然后看从哪一个读,反正100和1999是完全没有任何区别是吧任何100个里面都没有区别它这个有一个前提是这个data是不能mute如果你这个datamodify了那你如果 modify 的话,那你就就麻烦了。比如说你 modify 了。比如说你这个 data 之前 Tom Twitter 一写了一个 A 是吧? 然后把这个 A 改成 B 了。把A改成B了以后,因为你是这个推特还是同一个推特,所以你这个A改成B必须在这100个里面全部改了那你对他谈 consistent如果你只改了其中一部分,那你作为reader的话,你读的data可能是steal data如果你想出最新的电厂,你可能要把100到199全部都读一遍然后找见最新的那个Data,才是可能最新的Data所以这也是一些原因,比如说这个Twitter做成Inmutable,我觉得纯粹是一个工程学的角度,从工程学的角度真的是非常方便,因为Engineers是非常喜欢Inmutable data的,能减轻很多很多的困难和麻烦。谢谢大家所以是这样子,然后这是一种trade-off,因为它我觉得Twitter能做这个事也是因为它这个Functionality就不支持Modify Twitter 是吧,它Data是Inuitable的,所以它可以做这个事。但是大部分时候是语问,大部分Service and data都是可以更改的,可能做这个的话就没有必要。其次的话,Twitter的,它确实这个Twits的这个read量就是,它有非常明显的 hotspot 所以它这个推特的cashing 和这个推特的这个Twitter的caching和Twitter的 [25:00] 这个你都得不做了Twitter caching和Twitter的这个houseborder,怎么样把houseborders翻到的数据也做得不错然后这块有人问的话说微博的Edit History的话,Modify原坡的话觉得这块可以怎么处理我觉得这个处理其实也比较简单你就是Twitter1 比如说川普发了一个Twitter e,当发Twitter,Twitter在把e-modify的时候,不要把它当成e嘛,你就直接,你看起来,visualize的时候好像是e-modify了,但是实际上就create一个新的Twitter,是吧?叫 Twitter E' 是吧?或者 E' 是吧?然后你再做进来到时候读的时候你看是读 E 还是读 E'那你可以做一些东西让它不能就是实践的是immutable的,所有的data是immutable,但是别人看起来好像是multable,就modified了一个data,但是你其实可以把它做成immutable data也是可以的好然后有一个问题说, Kafka 是作为Database的Master控制所有的Node吗?Cafa 不是一个 database 然后一个 node 最大的 QPS 是多少呢?嗯Jobbox做了一个 Kafka 的 throughput我们先一个问题回答:Kafka 不是一个 database, Kafka 是一个Message Queue 是吧 或者是一个Publisher Subscriber System 或者是一个叫Streaming System它实际上实践的时候它是做了replication和partition它是做了replication和partition,它就是实践了这两部分,replication和partition所以 Kafka,如果你只想了解replication和partition,就是系统中的两个概念你最好的就是研究 Kafka,所以 Kafka 的 replication partition 做的是不错的,因为他只做这两个方法。它只是把所有的Message replicate 一下,然后 partition 一下,它不是一个 database我去年参加 Kafka Conference,他们一直在Branding,他们做这个Marketing,销售想把它们变成一个Database,但它们不是Database。我觉得任何想把 Kafka作为Database的人我觉得应该是没有在这个distribute system 里面去过确认我觉得 Kafka 我个人觉得是不可能做成一个database的他们想做成database 但他不可能做database 我个人觉得那 Kafka Node QPS 做啥呢? Kafka Node QPS Job Box 做过一个research它这个QPS 它其实不是做QPS 它做了throughput per secondSuper per second 我记得Jobox当时做的是64MB/s就是如果一个 Kafka Single Node一个 Kafka Topic Single Node大概 throughput是64到128MB/s这个我觉得非常好理解 因为这64到128MB/s你可以认为就是一个Disk的throughput的最大限因为 Kafka指出replication and partition 其实其实它overhead非常低,所以它这个Kafka的QPS,其实没有QPS是说,因为你毕竟和Kafka的topic size和message size有关,所以Kafka的这个throughput per second就是约等于disk的suport per second 那就是大概100多MB/sJobbox有一个文章,专门写这个,如果感兴趣的话可以搜一下,花了很多时间做research,然后最后在这个threshold,其实就是和这个disk的threshold相关。像一只啊 我觉得挺有趣的好然后这个第一个问题就讲完了,就是partition里面怎么样选k,然后我们有两种partition的方式,一个是hash partition,一个是range krange partition然后讲第二个问题,Secondary Index。Secondary Index对于DenmoDB或者CassandraDB,Secondary Index是很有趣的一部分。我这块会不停的提DenomDB,因为我觉得个人觉得这个DenomDB确实是在MySQL,就是SQLDB里面,就是Relational Database里面,非常具有革命性的一个创新。DynamoDB 非常具有革命性的创新它就是有很多设计思念理念都非常好,就是非常适合distribute system包括看别人写distribute system,DynamoDB和Dynamo-like DB都是重点研究的对象,因为它这里面确实有很多很好的概念包括以后很多DB也是集去DenemoDB设计里面更改了一些地方所以这个DenableDB可以认为是NodeSQL中非常值得研究的一部分但是你会看到我非常很少提什么GraphDBDocument DBL 算是会比较耍题,我觉得这个其实它这个理念创新上来说,我觉得是没有DenimalDB这个理念创新更多的。所以DenimalDB,包括你看其他人写的这个文章写了很多科普的文章,不然科普的还是比较深的文章的话,Dynamo确实是非常值得研究的一个database,就除了relationals [30:00] 以外,是非常值得研究的因为在DenableDB里面,很多时候是一个partition key你可以认为是个KValue Lookup,是吧?这个里面K就是191,然后这个record就是一个JSON File作为JSON File的时候,你可能就比如说这种情况下,我们就是存车的record有Honda,Fold,Audi,不同车record,不同color,不同location在你读的时候的话,很多时候你是不知道它的kid,是吧?你只有知道191才能读到这个record但是如果你不知道191的话,就读不到这个record所以你希望能search一下,就像sql 样的search一下比如说你空间color来search一下哪些车是红色的,是吧?哪些车是黑色的当你要search的时候,你不是用primarykid search你不是用Primary可以search的话,就需要有个SecondaryKit来做Index,Index就是保证你以后search可以更快一点。那你要用SecondaryKit index的话,它现在有两种Index,因为它是Distribute database,所以你有两种方式做SecondaryIndex,一种是叫LocalSecondaryIndex,或者它书里面有个字叫document secondary index一般叫local secondary index 都可以什么意思呢?就是说如果我们只看partition0的话你可以认为假设你想把partition0想成一个MySQL的话它这里面所有的record它的local secondary index其实就是用了MySQL的index就是非常简单用MySQL的index就是说它不care其他的partition它只对它自己内部的partition的data进行index所以这种情况下,当你insert一个比如说当你insert一个rad新加一个rad加一个新的radtoyota 是吧那你只在这个rad里面增加一毫就是比如说新的radtoyota是209 是吧你这块就加一个新的209你不需要去其他地方进行index然后这块也是一致的,比如说你这块先加了一个siver,siver,比如说posh是吧,你加个siverposh,然后你这块的话就把siver加一个posh是吧,比如说407啊什么siverposh,你index的时候只有local index,你是没有global index,这是local index,就是你写的时候是非常快嘛你加一个Record,你只要在自己的partition进行update就可以了你比如不管你在哪个partition,只要update自己的partition就行了你这个写是非常快的但你读的时候,你需要去把所有的地方都读到你不能只读一个partition你需要去读所有的partition比如说你要找所有的红色的card那你需要把所有的partition里面然后Red的那个Record全部读出来才能读到你所有的Data才能找到所有的红色的卡所以你这个Performance的效率读是很低的但是你写是很快的另一种相似的呢另一种不一样的叫Global Secondary IndexGlobal Secondary Index是不一样的 是吧它实际上呢它很多时候它那个Secondary Index是有在一个完全不同的Table上它有的时候是和这个Partition绑在一起的甚至有的时候它自己的一个这个Global Secondary Index是自己的一个table有可能就是看你怎么实践了看系统看怎么实践了它的想法是什么呢就把所有的Secondary Index放一起你比如说你不管如果你只用Global Secondary Index你不管在哪一个partition里面insert了一个新的data比如说你是Red Toyota你加了一个新的Red Toyota你都要去同一个地方一个Unified的地方去Insert Record这种情况下你比如说就到这个Partition 0里面在这个Red的Index下边加一个新的这个Toyota这个车的这个Key加到这里面这是Global Secondary IndexGlobal Secondary Index就是直观的看你读的时候你只要到一个地方读就可以了比如说你要找点说颜色为红的车那你只要到这个partition 0就是存颜色为红的这个partition里面就可以读到所有的record就知道它record在哪了一次读就可以了但你写的时候你比如说你这个写的时候大部分partition都上千个partition所以10个到上千个partition都是很常见的上万个partition也有的但你写的时候你上去在任何其他地方就是任何其他999或者9999个partition写的record你都必须到partition 0 里面去update 一下这个red 是吧那你这个效率就比较低嘛就是相当于是你要做两个transaction一个transaction在partition x 上另一个transaction在partition 0 上这是一个其次的话这是只是一个secondary index 是吧如果你这个row里面有10个secondary index 是吧你不仅对color进行index 对location进行index [35:00] - Yes. You can do make index, do 10 fields index Your blood will be in different places So you insert a record, you can insert a partition in but you can also update the 10 secondary index in the different partition. You have a right, you have a right amplification. It is proportional to the secondary. proportional to your global secondary index of the number, is it? So this index is too much, this is written. Thank you. You can write any record in the 10th partage. The effect will be very low. So, because of this reason, I remember DynamoDB in Secondary Index was 5. You can't have 5. When you set up a 5th secondary index, you might want to break the table more quickly. preconceited. Thank you. - Huh? And then the Global Secretary Index is It's more efficient, but it's more efficient. Because, for example, if you have a record, you have a record, you have a record, you have a record, you have a record, you have a record. 5个 global secondary index, so you have to write 6 partitions. When the record insert is synchronized insert, HighRecord insert完了以后,返回给Client,说:我成功了 But you can't search for it. Why? Because it's global. Second index Samuel update. I would say this database has been divided into DynamoDB. The global secondary index is asynchronous updated. It usually is not a minute to update it, but it still has the time to do it. If this DenimalDB performance is degraded, That's the time for you. When you insert a record, you record insert is a success. not just the global secondary index Inserted 成功, global secondary index insert 成功的话,可能它是 asynchronous 写的,这样的话, Uh, It is just a little bit, just when global secondary index is written in the same way, you can use the index to read and write. If it's not written, it's not going to read. Huh? There are people who say why not to make it synchronized. You can't change the record, but you can change the global secondary index. If you have a record, you can change the record of six nodes, and your effect is very low. This is a perfect decision. Then we have a question, "DynamoLacDB Global Secondary Index Read and Write are also Quorum? It's not Coral, because you have an index You read and write corum are different versions. You are an index, so you don't have to do corals, you don't have to do that. uh This is the case. When you read Global Secondary Index, you have to do well and prepare your data for a certain style. This style means you have a certain time. You have a certain time for a while. I'll give you a example 假设我们把刚才这个值 Thank you. and the color from red to red is black. and then red becomes black. If you read the 191, you will see the latest data. Black and Honda. But if you search, you can click Global Secondary Index search red. If this Global Second Index doesn't update the RAD, you can also read the RAD from the RAD. I can still get from the RAID. Because the global secondary index is the first to move from the RAID. And then to the black. It's two steps. Red and black are not in the cartridge. You may have a similar situation. You can read the HONDA version of the latest book "Black" And if you use red to search on it, you can search on it. If you use black to search on it, you can search on it. Because the global secondary index is the same as red and black. So this is the potential. uh But it's not a radial red quorum, the radial red quorum is too high. I think this is not a need. So, there are people who ask, "Do you read secondary index? Will there be a past-time data?" Yes, of course, there will be a past-time data. You write it, especially the global secondary index, there is still data. If you don't have Steal Data, you must use Cross-Chart Transaction, called Two-Face Commit. Two-phase commit, right? Two-phase commit, you know, performance is just 100. So you said you need to use 1ms, now you need to use 100ms. You need to use 10ms to 100ms, at least 100ms. from 100, that you... ...and you back to the question. Many times when you have performance, you have to handle 1/1000 of the things that you have to handle. [40:00] 不handle的就是没必要你不是证明一个数学公式啊做工程的时候做共产不是这么一个数学公式,如果它出现的概率是一千万分之一,是不handle的,你尤其不会把牺牲efficiency去handle这个事情,你是不会做这个事情的。所以你读 Global Secondary Index 社会过时的数据好这个Global和这个Local和这个Global Secondary Index就讲完了,大家有什么额外的问题吗?谢谢大家然后这块很多想法就是很多设计理念和观点的就是和学术界是不一样的就是嗯因为工业界和学术界是有很大的不同的,像学术界里面特别喜欢Haskell这种语言,我当时学Programming Language的时候特别喜欢用Haskell,是吧,OKMOL这种语言,但是工业界除了Gentstreet用OKMOL,Gentstreet是六七百人的一个公司用OKMOL以外,是没有人用的,这个是没有人用的,然后虽然这个Functional Language是非常好的一个概念,这个Okemo Haskell写过来非常好的概念,但是它确实没有人用。就是工业界的学术界不一样。然后学术界的话比较喜欢考虑一些 corner case,但工业界很多时候 performance 是远大于 corner case好然后我最后一个问题说是用secondary index读数据, 岂不是要读两次?如果你这个 partition 不在一个地方的话 你是要读两次很多时候也看你这个读的时候你就要读什么了你如果只是读ID的话,你就一次读完了,从partition里面读ID一次又读完了。但如果你是要读record的话,你要enrich这个record的话,你是要读两次。这个和这个SQL是一样的,SQL做enrichment的时候很多时候一个record要读好几次,这是正常的,这个performance不会很糟糕的好然后我们再来看这个rebalance partitionRebalance Partition 在database 里面你很多时候要Rebalance Partition 为什么呢? 一个是一方面原因是Steel 原因一方面原因是费力原因然后从skill的角度来讲有两种Skill,一种是QPS,你的QPS过高的话,你可能需要更多的CPU去处理,那你需要加新的Partition另一种Skill是从Data角度来讲如果你datasets太多的话,你可能需要加新的partition,那从加新的partition,你有更多的disk和memory去存储它。是吧从skill的时候我们就可以想一下以后问的时候 skill 也是有两种 skill 是吧 一种 skill 是 QPSQPS可以加更多CPU来解决,一种Skill是Data,Data可以加更多Disk和Memory来处理。还有一种原因是,你说的Node都是Commercial Node,其实不是Commercial,就是说的Node叫Chief Commercial Node,它比较便宜,它会Fill。并且Fill的概率不是太低是吧如果你知道那个birthday paradox的话就知道在一个大的Data Center你基本上每一秒钟都有机器会Fill每一秒钟都有机器会Fill所以当一个机器fill的时候,你需要用其他机器来代替它,take over 它的 responsibility所以这种情况下也是要repartition所以不管是skill的原因还是fillure的原因,你都需要把data从一个node到1移到另一个node,然后他把这个过程叫rebalancing,就是partition rebalancing所以说这个Pretitioner Balancing的话需要有一些东西你要满足,就是有一些Requirements,就是你设计的时候相当于有一些Requirements要满足。在rebalancing 之后,你相对来说你的data是比较even and distributed 是吧?这个是很重要的,因为你如果是skill的原因,data size 不够的话,你不能balance 以后所有data在一个node上,另一个data是空空如也的这个不是一个合理的设计然后在rebalancing发生的时候,你的database still should be available,就是你database还是可以接受read和write的,这个也是要支持的,就是你不能rebalance,你自己做rebalance,然后就service就响应了,这个是不可以的。其次的话,就是你不需要移动,就是,more than necessary data,啊,你不需要移动more than necessary data 去rebalance,比如说,啊,你比如说你是久不弄的,你加了一个新的node,那你最多需要移动1/9的数据是吧,或者是1/10的数据。你不能说你先加了一个node,然后所有的data全部移动了一遍,那这样是非常不Efficient的。因为这个里面 在 Data Center 里面 在 Database 里面Database和Datacenter 里面,Network是最宝贵的资源,就是所有的资源里面,Network是最宝贵的资源。就是在这个我们可以想一下说资源里面有什么资源,CPU非常不值钱,memory相对还可以吧,看什么你要做spark了,memory比较值钱 [45:00] 有时候你如果是 data intensive memory 就那么回事儿disk是非常非常不值钱的 CPU, memory, disk,还有什么network, network是非常非常值钱的Network 是最值钱的资源 是吧如果让我排序的话 当然就有一些主观考虑Network 是最值钱的,然后其次可能是memory,然后其次是CPU,最不值钱是disk,所以是你希望是以后当你做这个 partition rebalance 的时候当你做partition rebalance 的时候谢谢大家你不希望move unnecessary data, 因为你move unnecessary data, 换就有 extra cost on the network, 是吧?所以Network是最保隔期的。Network是资源什么?就是Socket,就是NetworkSocket,就是当你把资源从,当你把资源从,当你把资源从NodeA,Transfer到NodeB的时候,你要占用的这个就是带宽Banbase你是要再用 socket 是吧?你是要去再用 bandwidth 去传输这个资源,这个资源是最宝贵的怎么样做partition,我们现在不说怎么对的,先说怎么样做repartition是错的怎么样做这partition错的呢?是根据你partition的数量磨一个数比如说你是十个partition就磨十那你data比如说是1ID是1你就在partition1上ID12就在partition2上这个做法是绝对错的为什么这个做法是绝对错的呢是因为当你的partition 如果你是每次层2 层4还好说在你partation数量如果从10变成9了或者从9变成10了你所有的data全部要摸一遍你可以想一下你比如说之前是磨9的话,9和10是互制的嘛,这个9和10是互制的,所以基本上所有data全部要录一遍。就是多少要摸一遍啊就不考虑这个Number Series的事了,就是9个事故,当你increase的时候,你所有的这个data,特别是你超过多少以后,基本上全部要目一遍。你比如说超过久以后,你全部要募一遍。这个是做法是绝对错的,因为你要募data on necessary data,You move the data on the repartition and move data more than necessary.这个是绝对错的所以如果我们看正确的partition的方式的话,它有什么呢?就是首先你固定fixed number of partitions这个常做的是 React, Elasticsearch, Votemole, Uber的SchemelessUber Stimulus 相对来说,我觉得大家在Uber工作的话比较熟的,它实际上把它分成了4096分。所以你所有的data就是你上来的时候就这个schemeless这个database就4096个partition然后,因为这个可能开始的时候,这个read和read的load没有那么大,是吧?所以4096个,可能它只有一千个node,是吧?每个node上放四个partition就可以了。这块每一个partition都是一个独立的MySQL Database基本上每一个partition就是独立的MySQL database所以4096个partition你可以认为是有4096个MySQL Database Instance Serum好 这种情况下的话这个node的数量是变化在partition数量是不变的比如说这种情况下,就举了一个例子是,当你把这个cluster从四个node变成五个node的时候,你实际上是把其中的四个Node中,就是四个Node,每个Node有五个partition,其中的一个partition,引到新加的Node上。所以很多时候它剩下的四个没有变,只有其中的一个排队神到了新加弄的时候然后这样的话进行了re-partition这个是这个的话因为你这个每个partition是一个MySQL database instance 吧你当你到了一个新的node上的时候你这种情况下比如说你share的resource比较少当你到新的上面share的resource比较多了就可以做一些这个你可以增加这个可以处理更多的datasize 是吧或者是增加了一个新的CPU来帮你处理QPS 是吧所以这是 partition 的一种方式这个方式有一个难点是什么呢?就是你必须对你的 use case 才理解Uber 如果我们看那个Stimulus当时的block的时候他实际上对于所有的traffic 是有非常大的、非常深的一个理解的就是他知道他traffic增长多少,他知道他data size增长多少他arguese说我把它变成4096个partition是足够20年甚至30年的使用的,就是20年30年有就是是足够使用的是吧,就是所以一上来就设计成4096并且他对它对于这个就是用Schemless,40096个partition,一开始的utilization rate它也是有估计的,是吧?那比如说一开始的时候,它知道每个partition里面大概平均下来有200MB data,是吧?或者是一个Gigabyte data,它大概有个平均估计的。他知道自己一开始会在什么状态然后在十年后会在什么状态所以他估计这次 [50:00] with no partition. is good. But this is often You have no pre-knowledge. For example, you are setting partition database for Amazon. For example, you are setting a Amazon RDS, which is Amazon Relational Database. You don't know your client will have a request You don't know how to use your database So you can't consider QPS and data size In this situation You can't do the partition of the number of the number and control If you have a partition number over high, for example, you have 100 partition, you have 100,000 partition, That every partition is a MySQL instance, it's overhead is very high. because it has a million partitions, a million MySQL instances. But if you need 100% of the party, you only need 10% of the party. You need to cut the partitions in the future. Of course, this is also possible, but it's very difficult. From an engineer's perspective, you need to take a month or two months to fix it. tracing a partition number just right. It is to be able to do your use case and business to be able to do it. This is the limitation. Thank you. And the other partitions is dynamic partitions. This dynamic partitions, if you had a data structure called AVL trade, or we talked about B trade, When you do B-trade, this is a lot of similar features of AVL-tree and B-trade. In the database of B-trade and in the data structure of AVL-tree, it has a lot of similar features. So When you start partitioning, you can only do one partition. But for the database, you can set up a 3, 5, 10. It's about 10 partitioning. But if we don't do some optimization, we have a partition. And the partner will grow and grow and grow. Then when you need to go over a threshold, like 10 gigabytes, in HBase, it's used to be a threshold. When it's over the threshold, it will be divided into two partitions. And it will be divided into five parts of the data. It's like AVLTRI or Btree. It's like AVLTRI or Btree. It's a bit like AVLTRI. It's a dynamic partition. It's a bit like AVLTRI or Btree. It's a dynamic partition. It's a bit like AVLTRI. Key Range is a little like Segmentary. If you're learning your sharenui visa I'm going to add There is a segment tree called Segment, which is very suitable for a I think it's a good thing to do. It's a good thing to do. It's a good thing to do. It's a good thing to do. This is So you can use the key range partition as a segment tree. So this is a dynamic partition. It is a dynamic partition. It is a particular particular part. But it is a particular part. is also possible to fix it. This is also possible to fix it. We have a question about why you need several parts of the heat. Because you set up a database, you have to put a lot of data in the system. Partition is to bring production database back to the production database. If you go to the partition, you have a partition. The data is a half, the data is a half, the data is a half, and it starts to do partition. that performance is not very bad. So, when you start to start the initial request, you will have to go back to the initial request. If you have 10 parts, you can just take 10 parts That way, the data will not need to do the repartition. The effect will be higher. Okay. Thank you. Thank you. This is the dynamic partition, a little like AVL trade or Btrade partition. The last partitions are called Cassandra, or the other one is called Katma. I don't know what it means. I don't know what it means. Cassandra is what is called? It is called number of nodes. The partition of the number is proportional to number of nodes. For example, if you have a node, you have 3 partitions. If you have 1000 nodes, you have 3000 partitions. This is also a way to do it. When you add a node, for example, you have 100 nodes You add a node You may be able to do this 100 partitions in the partitions. 早见其中的几个partition 把它split一下 然后把那个 [55:00] 找这三个partition把它split一下,然后把这三个partition split的partition放到新的node里面这也是一种方式,就是这个partition数量是proportional to known number这是一种方式好Partition 这块说完的话,这时候要说一个DevOps 相关的,也是经验的一部分。当你做这个Balance的时候,Automatic Balance和Rebalance的时候,你想做全自动的还是全手动的,还是中间的一种折衷像这个 Kafka 是做全自动的 Automatic Balancing 和 Rebalance 的所以它会有那种 Rebalance Stop 你可以search 一下 Rebalance Stop 什么意思呢比如说你的Covca topic因为卡夫特的topic是user topic 是吧?然后你当前有三个partition你上线有三个partition其中有个partition网络不太好他经常动不动的就连不上了比如说三秒钟连不上然后过一分钟后连上了三秒钟连不上过一分钟就连上了这是非常常见的你Network就连接不好是吧你三秒钟连不上然后过一分钟又连上了那这种情况下当它连不上的时候那其他Node就认为这个这个 partition 带掉了那它死掉了以后呢,它必须必须把它的 record 啊就是rebalance到另外两个nodes上然后结果过一分钟又连上了,那你因为他就活蹦乱跳伙的,是吧?那他必须把那两个record再rebalance到他上面。他就不停的重复这个balance和rebalance的过程,叫rebalance storm。这个在kafka conference上是在backend的讨论,就是kafka conference,去年参加的kafka conference上是讨论最多的一个copy。就是这个rebalance到他们做了很多优化去优化这个rebalance down,因为这个automatically的话,就是你很常见的一分钟突然disconnect,然后再connect,disconnect,然后connect,然后整个CAPA系统的performance就直接倒掉了因为你 show the backend resource, show the background resource基本上一直在做 partition 和 repartition 其次的话, repartition 这部分会占用最宝贵的 network 资源,是吧?所以说你这个 performance 是直接很惨的那死亨菜呢这是这个automatic rebalance 的不好的一点但是有的是完全做成这个manually rebalance的话也很糟糕就是你想你系统可能如果你的 data elastic很大的话,就是这个弹性很大的话你不停的就几秒钟就有人告诉你说要不要rebalance 这个效率也很糟糕所以这个是有一个取道符也有的System是作为这个就是着重方案比较简单的可能就有automatic rebalance,但是比较困难的可能就manual rebalance。你要是这种机器它不停的一会儿disconnect,一会儿connect的话,它可能就需要人为的干预了。否则的话他就不停的balance or rebalance 什么都处理不了了谢谢大家Balance和Rebalance 这块就讲完了,最后一个topic就是Service Discovery,我们在这个Balance和Rebalance这块,就是Rebalance Partition 这块有什么额外的问题呢?谢谢大家谢谢大家谢谢大家没有的话我们就到最后一个,这也是最后一个sector,就是service discovery。我们想一下,我们现在是一个User Table,分在三个Node上,拿这个例子举例,你是User Table,现在是三个Node上,所以你User Table,任何一个User Record,能在三个Node中的任何一个,是吧?那我怎么知道,比如说Scot的这个User Record 在哪一个Node上那么你必须有一个component必须有一个component,需要这个pre-knowledge,知道scot的这个record在哪一个node上所以我们这个有更broder的这个problem叫service discovery,在这个micro service里也很常见比如说我现在做一个Micro Service我要靠 user service, motivate user profile,那我怎么知道哪个 node 是哪一个Node的哪一个POD number是User Service 是吧我得首先知道是哪个node,然后再知道它ponum去访问它是吧其次的话,它可能user service 有几百个node,我怎么知道我要去哪个node?是吧?这是一个service discovery 过程,service discovery 和 load balancing 的一个问题。所以常见的呢有三种做法,我觉得好像也是唯一的三种做法一种做法是所有的node是知道其他node的存在的也就是说这个knowledge就是这一层这层 knowledge 是在每个 [60:00] 每个partition 字里面,就是每个node是知道其他 Node 在哪所以它这样的话比如说我说去get-那Node0就知道这个负在Node2上另一种是加一个额外的Layer叫routingLayer那这种情况下呢就是routing layer 知道你在哪里所以你去你去比如说Gate node2的话,周庭那样就到Node2上,Gate2了这是这样的做法第三种做法就是Client就有这个Sever KnowledgeClient自己就知道这个node在哪儿,我们也把这个叫FightClient,然后Client直接就到node2上是吧这三种做法各个的好处这三种做法各个的好处我们先点评一下,这种一的做法很多时候,如果我们看peer-to-peer system的话,这就有点像这个这个 consistent hashingEASY很像Consistent HASHING,就是PEER-TO-PEER SYSTEM它是合什么?就是它没有 centralized knowledge,它是合了 knowledge,它其实还是要一个Centerlizer,但是它说的Node,它不仅帮你Lookup同时也帮你处理service 这个是P2E的做法TR的做法是你实际上routing layer就是routingTR是一个单独的module那你这个routing tier就不和这个node争夺这个lookup资源了QPS就不和routing layer就不和node争夺这个资源了如果你用tier1的话,你这个node其实你lookup和这个service是要相互争夺资源的同一个同一个机器们相互增多资源但你用第二个方式的话就不相互增多资源了你roting是roting,你这个node是node,你两个可以单独的进行skill,单独的进行优化然后用T23的这个Lentense是最好的DynamoDB那篇paper里面写过它当时是有三种做法一种是只用T23一种是T22TR3的Lookup时间是TR2的二分之一也非常简单 因为这个TR相当于是两个request这个 tier 3 是直接 request 的嘛,所以这个 tier 3 的这个 latency 是 tier 2 的二分之一,所以这 tier 3从performance的角度来讲是最好的但是这个TRS有个问题这个client 你是知道所有的server node在哪儿所以这个client其实不是普通的user这个Client是你Data Center里面那个Service我重复一下,这个Client不是普通的User,不是Consumer,这个Client是你的Data Center里面另一个Service。因为你有data在哪一个node的knowledge你如果让其他人知道的话,这其实是Security Loophole,就可以攻击了是吧就可以hack他 Twitter account所以这个Client不是普通的User,这Client是VetaCenter 里面其他Service比如说这个可能是这个node可能是server user node这个Client可能是比如说Payment Service 是吧去的Login Service,所以它不是一个普通的Service,并且第一点,这个Client不能是任何一个Client,必须是Internal Client然后第二点这个Client data是必须不停的updated的,就是你实际上这个,如果这个更新很快的话,这个Client很,可能的时候他就,Auto-sync了 就是它的数据就过时了那你不需要不停的去更新它,保证它对它永远insync的所以他需要额外的cost,让client data 阴性不能 auto-think好,这里面就是拿其中的一种做法就是说,拿单独的routing layer解释了它在电纳生产里面什么实践的是吧在Data Center里面,比如说拿这个Look Aside,或者是Routing Layer,额外Routing Layer,这个Rooting Learning Knowledge其实是从Rootkeeper或者是ETCD里面读到的,它是有个从一个Global Configuration Service这里面是拿Rootkeeper做这个Global Configuration Service,然后从这里面读到的,所以它永远有最新的最新的没有任何问题的这个routing information所以这个Rootkeeper是保留什么呢Rulekeeper 是保留从 k-range 到 partition 然后从 partition 到 node 这个信息的从K range 到 partition,K range 和 partition 是怎样 map 的?当然K range 和 partition 这个 map 也有可能会变,是吧? 如果你比如说这个 partition 不是不是一上来赛的好的话,Clear and Partition 这个关系是有可能改变的然后从partition到node的信息也是不停的会变的,这也是为什么要the service discovery to 12 [65:00] 所以这个rootcaper是保留从k-range到node之间的这个knowledge的如果我们分细分开的话,就是这种减任的partition,我们见到没见过。谢谢大家我们细分开的话就是从 k-range 到 partition 和从 partition 到 node 。这个信息的然后这个是用ROOKEIPER存的。这里面还有另一个信息是从Node叫IP-address,因为这个Node的IP-address不像这个partition和这个QRange变化这么快,所以它经常的话这个IP-address是用DNS来处理的,Domain Name Service。超越了好所以就是这个就是service discovery,就是它实际上它sauce-to-choose是rootkeeper,用rootkeeper来作为sauce-to-choose,然后这个不管是这个routine knowledge全部去rootkeeper上来进行update,然后这个node down掉以后,它也更新入kaper,然后入kaper知道它最新的node在什么地方然后 Kubernetes 也是提供 Service Discovery 功能的,因为 Kubernetes库姆乃茨 我记得好像前两天跟大家分享 请你分享一个文章 库姆乃茨其实在ETCD上包装了一层 wrapper 它其实是它用 et/cd 然后增加了一些其他的 functionality所以它这个底下还是用ETCD的,ETCD是Rootkeeper的另一种,也是一种Consensus Service是RuleCaper的一种直接的替代品好,这就全部讲完了,今天主要讲的是partition讲了 partition 就是 partition k 怎么显示吧然后有多种 partition k 的显示方式,有 range k 有 hash k然后还有 composite case,或是 compound case,然后其次的话讲了Secondary Index, Global Secondary Index, Local Secondary Index然后呢,然后又讲了这个repartition这一部分,怎么样做repartition是对的,怎么样做repartition是错的基本上大家刚开始自己设计的话都会把reparation设计错,就是常见的错误。然后最后的话讲service discovery讲了四个大的方面,关于partition这部分有什么问题吗?如果有问题的话回答一下,如果没有的话,今天就到这里好了。谢谢第一种routing的方法是用gossip来实现的没有必要用Gossip实现如果是peer to peer的话你可以用Gossip来实现否则的话你这个node可以直接跟do paper来talk你弄的比如说我给你举个例子node说我要找foo,然后我去node2上说foo在不在那里,那在的话就request成功了是吧然后如果不在的话,Node2返回Node0说,如果不在我这里了,你是不是需要更新一下你的这个 knowledge那漏的零直接去入Cabular上,更加knowledge就可以了,没必要用GallSafe时间。谢谢大家高系统当然也可以使用,但是你的data convergence 就会慢一点,就是你data怎么样就是convert到最新的data会慢一点你用Rookable的话可能很快就convert了有什么问题的话也可以直接问啊直接说一下谢谢大家好 那就这样吧谢谢大家今天就到这里谢谢大家好