Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ch01.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@

1. **吞吐量(throughput)**:每秒可以处理的单位数据量,通常记为 QPS。
2. **响应时间(response time)**:从用户侧观察到的发出请求到收到回复的时间。
3. **延迟(latency)**:日常中,延迟经常和响应时间混用指代响应时间;但严格来说,延迟只是指请求过程中排队等休眠时间,虽然其在响应时间中一般占大头;但只有我们把请求真正处理耗时认为是瞬时,延迟才能等同于响应时间。
3. **延迟(latency)**:日常中,延迟经常和响应时间混用指代响应时间;但严格来说,延迟只是指请求过程中排队等待时间,虽然其在响应时间中一般占大头;但只有我们把请求真正处理耗时认为是瞬时,延迟才能等同于响应时间。

响应时间通常以百分位点来衡量,比如 p95,p99 和 p999,它们意味着 95%,99%或 99.9% 的请求都能在该阈值内完成。在实际中,通常使用滑动窗口滚动计算最近一段时间的响应时间分布,并通常以折线图或者柱状图进行呈现。

Expand Down Expand Up @@ -270,7 +270,7 @@

否则,需求一定是不断在变,引起变化的原因多种多样:

1. 对问题阈了解更全面
1. 对问题域了解更全面
2. 出现了之前未考虑到的用例
3. 商业策略的改变
4. 客户爸爸要求新功能
Expand Down
8 changes: 4 additions & 4 deletions ch02.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ network model 是 hierarchical model 的一种扩展:允许一个节点有多

在关系模型中,数据被组织成**元组(tuples)**,进而集合成**关系(relations)**;在 SQL 中分别对应行(rows)和表(tables)。

- 不知道大家好奇过没,明明看起来更像表模型,为什叫**关系模型**?
- 不知道大家好奇过没,明明看起来更像表模型,为什么叫**关系模型**?
表只是一种实现。
关系(relation)的说法来自集合论,指的是几个集合的笛卡尔积的子集。
R ⊆ (D1×D2×D3 ··· ×Dn)
Expand Down Expand Up @@ -419,7 +419,7 @@ db.observations.aggregate([
| 知识图谱 | 概念是点,关联关系是边 | 启发式问答 |

> 同构(homogeneous)数据和异构数据
> 图模型中的点变可以像关系中的表一样都具有相同类型;但是,一张图中的点和变也可以具有不同类型,能够容纳异构数据是图模型善于处理多对多关系的一大原因。
> 图模型中的点边可以像关系中的表一样都具有相同类型;但是,一张图中的点和边也可以具有不同类型,能够容纳异构数据是图模型善于处理多对多关系的一大原因。

本节都会以下图为例,它表示了一对夫妇,来自美国爱达荷州的 Lucy 和来自法国 的 Alain:他们已婚,住在伦敦。

Expand Down Expand Up @@ -663,7 +663,7 @@ SELECT ?personName WHERE {
}
```

他是 Cypher 的前驱,因此结构看起来很像:
它是 Cypher 的前驱,因此结构看起来很像:

```
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (location) # Cypher
Expand All @@ -683,7 +683,7 @@ SELECT ?personName WHERE {

图模型是网络模型旧瓶装新酒吗?

否,他们在很多重要的方面都不一样
否,它们在很多重要的方面都不一样

| 模型 | 图模型(Graph Model) | 网络模型(Network Model) |
| -------- | --------------------------------------------------------- | ------------------------------------------------------ |
Expand Down
18 changes: 9 additions & 9 deletions ch03.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

# 驱动数据库的底层数据结构

本节由一个 shell 脚本出发,到一个相当简单但可用的存储引擎 Bitcask,然后引出 LSM-tree,他们都属于日志流范畴。之后转向存储引擎另一流派——B 族树,之后对其做了简单对比。最后探讨了存储中离不开的结构——索引。
本节由一个 shell 脚本出发,到一个相当简单但可用的存储引擎 Bitcask,然后引出 LSM-tree,它们都属于日志流范畴。之后转向存储引擎另一流派——B 族树,之后对其做了简单对比。最后探讨了存储中离不开的结构——索引。

首先来看,世界上“最简单”的数据库,由两个 Bash 函数构成:

Expand Down Expand Up @@ -76,7 +76,7 @@ $ db_get 42

![ddia-3-1-hash-map-csv.png](img/ch03-fig01.png)

看来很简单,但这正是 [Bitcask](https://docs.riak.com/riak/kv/2.2.3/setup/planning/backend/bitcask/index.html 'Bitcask') 的基本设计,但关键是, Work(在小数据量时,即所有 key 都能存到内存中时):能提供很高的读写性能:
看来很简单,但这正是 [Bitcask](https://docs.riak.com/riak/kv/2.2.3/setup/planning/backend/bitcask/index.html 'Bitcask') 的基本设计,但关键是, Work(在小数据量时,即所有 key 都能存到内存中时):能提供很高的读写性能:

1. 写:文件追加写。
2. 读:一次内存查询,一次磁盘 seek;如果数据已经被缓存,则 seek 也可以省掉。
Expand All @@ -97,7 +97,7 @@ $ db_get 42
4. **记录写坏、少写**。系统任何时候都有可能宕机,由此会造成记录写坏、少写。为了识别错误记录,我们需要增加一些校验字段,以识别并跳过这种数据。为了跳过写了部分的数据,还要用一些特殊字符来标识记录间的边界。
5. **并发控制**。由于只有一个活动(追加)文件,因此写只有一个天然并发度。但其他的文件都是不可变的(compact 时会读取然后生成新的),因此读取和紧缩可以并发执行。

乍一看,基于日志的存储结构存在折不少浪费:需要以追加进行更新和删除。但日志结构有几个原地更新结构无法做的优点:
乍一看,基于日志的存储结构存在不少浪费:需要以追加进行更新和删除。但日志结构有几个原地更新结构无法做的优点:

1. **以顺序写代替随机写**。对于磁盘和 SSD,顺序写都要比随机写快几个数量级。
2. **简易的并发控制**。由于大部分的文件都是**不可变(immutable)** 的,因此更容易做并发读取和紧缩。也不用担心原地更新会造成新老数据交替。
Expand Down Expand Up @@ -278,7 +278,7 @@ SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079

前述索引只提供全字段的精确匹配,而不提供类似搜索引擎的功能。比如,按字符串中包含的单词查询,针对笔误的单词查询。

在工程中常用 [Apace Lucene](https://lucene.apache.org/ 'Apace Lucene') 库,和其包装出来的服务:[Elasticsearch](https://www.elastic.co/cn/ 'Elasticsearch')。他也使用类似 LSM-tree 的日志存储结构,但使用其索引进行模糊匹配的过程,本质上是一个有限状态自动机,在行为上类似 Trie 树。
在工程中常用 [Apace Lucene](https://lucene.apache.org/ 'Apace Lucene') 库,和其包装出来的服务:[Elasticsearch](https://www.elastic.co/cn/ 'Elasticsearch')。它也使用类似 LSM-tree 的日志存储结构,但使用其索引进行模糊匹配的过程,本质上是一个有限状态自动机,在行为上类似 Trie 树。

### 全内存数据结构

Expand All @@ -291,7 +291,7 @@ SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079

> VoltDB, MemSQL, and Oracle TimesTen 是提供关系模型的内存数据库。RAMCloud 是提供持久化保证的 KV 数据库。Redis and Couchbase 仅提供弱持久化保证。

内存数据库存在优势的原因不仅在于不需要读取磁盘,而在更于不需要对数据结构进行**序列化、编码**后以适应磁盘所带来的**额外开销**。
内存数据库存在优势的原因不仅在于不需要读取磁盘,而更在于不需要对数据结构进行**序列化、编码**后以适应磁盘所带来的**额外开销**。

当然,内存数据库还有以下优点:

Expand Down Expand Up @@ -348,7 +348,7 @@ AP 中的处理模型相对较少,比较常用的有**星状模型**,也称

如上图所示,星状模型通常包含一张**事件表(_fact table_)** 和多张**维度表(_dimension tables_)**。事件表以事件流的方式将数据组织起来,然后通过外键指向不同的维度。

星状模型的一个变种是雪花模型,可以类比雪花(❄️)图案,其特点是在维度表中会进一步进行二次细分,讲一个维度分解为几个子维度。比如品牌和产品类别可能有单独的表格。星状模型更简单,雪花模型更精细,具体应用中会做不同取舍。
星状模型的一个变种是雪花模型,可以类比雪花(❄️)图案,其特点是在维度表中会进一步进行二次细分,将一个维度分解为几个子维度。比如品牌和产品类别可能有单独的表格。星状模型更简单,雪花模型更精细,具体应用中会做不同取舍。

在典型的数仓中,事件表可能会非常宽,即有很多的列:一百到数百列。

Expand Down Expand Up @@ -385,7 +385,7 @@ GROUP BY

将所有数据分列存储在一块,带来了一个意外的好处,由于同一属性的数据相似度高,因此更易压缩。

如果每一列中值阈相比行数要小的多,可以用**位图编码(_[bitmap encoding](https://en.wikipedia.org/wiki/Bitmap_index 'bitmap encoding')_)**。举个例子,零售商可能有数十亿的销售交易,但只有 100,000 个不同的产品。
如果每一列中值域相比行数要小的多,可以用**位图编码(_[bitmap encoding](https://en.wikipedia.org/wiki/Bitmap_index 'bitmap encoding')_)**。举个例子,零售商可能有数十亿的销售交易,但只有 100,000 个不同的产品。

![ddia-3-11-compress.png](img/ch03-fig11.png)

Expand Down Expand Up @@ -417,7 +417,7 @@ WHERE product_sk = 31 AND store_sk = 3

### 列族

书中特别提到**列族(column families)**。它是 Cassandra 和 HBase 中的的概念,他们都起源于自谷歌的 [BigTable](https://en.wikipedia.org/wiki/Bigtable 'BigTable') 。注意到他们和**列式(column-oriented)存储**有相似之处,但绝不完全相同:
书中特别提到**列族(column families)**。它是 Cassandra 和 HBase 中的的概念,它们都起源于谷歌的 [BigTable](https://en.wikipedia.org/wiki/Bigtable 'BigTable') 。注意到他们和**列式(column-oriented)存储**有相似之处,但绝不完全相同:

1. 同一个列族中多个列是一块存储的,并且内嵌行键(row key)。
2. 并且列不压缩(存疑?)
Expand All @@ -431,7 +431,7 @@ WHERE product_sk = 31 AND store_sk = 3
1. 内存处理带宽
2. CPU 分支预测错误和[流水线停顿](https://zh.wikipedia.org/wiki/%E6%B5%81%E6%B0%B4%E7%BA%BF%E5%81%9C%E9%A1%BF '流水线停顿')

关于内存的瓶颈可已通过前述的数据压缩来缓解。对于 CPU 的瓶颈可以使用:
关于内存的瓶颈可以通过前述的数据压缩来缓解。对于 CPU 的瓶颈可以使用:

1. 列式存储和压缩可以让数据尽可能多地缓存在 L1 中,结合位图存储进行快速处理。
2. 使用 SIMD 用更少的时钟周期处理更多的数据。
Expand Down
8 changes: 4 additions & 4 deletions ch04.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ CSV(以逗号\TAB、换行符分割)还算紧凑,但是表达能力有限

## Thrift 和 Protocol Buffers

Thrift 最初由 Facebook,ProtoBuf 由 Google 在 07~08 年左右开源。他们都有对应的 RPC 框架和编解码工具。表达能力类似,语法也类似,在编码前都需要由接口定义语言(IDL)来描述模式:
Thrift 最初由 Facebook,ProtoBuf 由 Google 在 07~08 年左右开源。它们都有对应的 RPC 框架和编解码工具。表达能力类似,语法也类似,在编码前都需要由接口定义语言(IDL)来描述模式:

```protobuf
struct Person {
Expand All @@ -117,7 +117,7 @@ IDL 是编程语言无关的,可以利用相关代码生成工具,可以将

这也是不同 service 可以使用不同编码语言,且能够互相通信的基础。

此外,Thrift 还支持多种不同的编码格式,常用的有:Binary、Compact、JSON。可以让用户自行在:编码速度、占用空间、可读性方便进行取舍
此外,Thrift 还支持多种不同的编码格式,常用的有:Binary、Compact、JSON。可以让用户自行在:编码速度、占用空间、可读性方面进行取舍

![ddia4-thrift-binary-enc.png](img/ch04-fig02.png)

Expand Down Expand Up @@ -191,7 +191,7 @@ record Person {
可以看到 Avro 没有使用字段标号。

- 仍是编码之前例子,Avro 只用了 32 个字节,为什么呢?
他没有编入类型
它没有编入类型

![ddia4-avro-enc.png](img/ch04-fig05.png)

Expand Down Expand Up @@ -432,4 +432,4 @@ REST 相比 RPC 的好处在于,它不试图隐去网络,更为显式,让

由于 Actor 和外界交互都是通过消息,因此本身可以并行的,且不需要加锁。

分布式的 Actor 框架,本质上是将消息队列和 actor 编程模型集成到一块。自然,在 Actor 滚动升级是,也需要考虑前后向兼容问题。
分布式的 Actor 框架,本质上是将消息队列和 actor 编程模型集成到一块。自然,在 Actor 滚动升级时,也需要考虑前后向兼容问题。
14 changes: 7 additions & 7 deletions ch05.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@

## 同步复制和异步复制

**同步(synchronously)复制**和**异步(asynchronously)复制**和关键区别在于:请求何时返回给客户端。
**同步(synchronously)复制**和**异步(asynchronously)复制**的关键区别在于:请求何时返回给客户端。

1. 如果等待某副本写完成后,则该副本为同步复制。
2. 如果不等待某副本写完成,则该副本为异步复制。
Expand Down Expand Up @@ -107,7 +107,7 @@

1. **新老主副本数据冲突**。新主副本在上位前没有同步完所有日志,旧主副本恢复后,可能会发现和新主副本数据冲突。
2. **相关外部系统冲突**。即新主副本,和使用该副本数据的外部系统冲突。书中举了 github 数据库 MySQL 和缓存系统 redis 冲突的例子。
3. **新老主副本角色冲突**。即新老主副本都以为自己才是主副本,称为**脑裂(split brain)**。如果他们两个都能接受写入,且没有冲突解决机制,数据会丢失或者损坏。有的系统会在检测到脑裂后,关闭其中一个副本,但设计的不好可能将两个主副本都关闭调
3. **新老主副本角色冲突**。即新老主副本都以为自己才是主副本,称为**脑裂(split brain)**。如果他们两个都能接受写入,且没有冲突解决机制,数据会丢失或者损坏。有的系统会在检测到脑裂后,关闭其中一个副本,但设计的不好可能将两个主副本都关闭掉
4. **超时阈值选取**。如果超时阈值选取的过小,在不稳定的网络环境中(或者主副本负载过高)可能会造成主副本频繁的切换;如果选取过大,则不能及时进行故障切换,且恢复时间也增长,从而造成服务长时间不可用。

所有上述问题,在不同需求、不同环境、不同时间点,都可能会有不同的解决方案。因此在系统上线初期,不少运维团队更愿意手动进行切换;等积累一定经验后,再进行逐步自动化。
Expand Down Expand Up @@ -400,7 +400,7 @@ git 也是一个类似的协议。

### 自定义解决

由于只有用户知道数据本身的信息,因此较好的方式是,将如何解决冲突交给用户。即,允许用户编写回调代码,提供冲突解决逻。该回调可以在:
由于只有用户知道数据本身的信息,因此较好的方式是,将如何解决冲突交给用户。即,允许用户编写回调代码,提供冲突解决逻辑。该回调可以在:

1. **写时执行**。在写入时发现冲突,调用回调代码,解决冲突后写入。这些代码通常在后台执行,并且不能阻塞,因此不能在调用时同步的通知用户。但打个日志之类的还是可以的。
2. **读时执行**。在写入冲突时,所有冲突都会被保留(如使用多版本)。下次读取时,系统会将所有数据本版本返回给用户,进行交互式的或者自动的解决冲突,并将结果写回系统。
Expand Down Expand Up @@ -491,7 +491,7 @@ Dynamo 流派的存储中通常有两种机制:
在 Dynamo 流派的存储中,n、r 和 w 通常是可以配置的:

1. n 越大冗余度就越高,也就越可靠。
2. r 和 w 都常都选择超过半数,如 `(n+1)/2`
2. r 和 w 通常都选择超过半数,如 `(n+1)/2`
3. w = n 时,可以让 r = 1。此时是牺牲写入性能换来读取性能。

考量满足 w+r > n 系统对节点故障的容忍性:
Expand Down Expand Up @@ -587,8 +587,8 @@ LWW 有一个问题,就是多个并发写入的客户端,可能都认为自

考虑之前的两个图:

1. 在 5-9 中,由于 client B 的更新依赖于 client A 的插入,因此他们是因果关系
2. 在 5-12 中,set X = A 和 set X = B 是并发的,因为他们都互相不知道对方存在,也不存在因果关系。
1. 在 5-9 中,由于 client B 的更新依赖于 client A 的插入,因此它们是因果关系
2. 在 5-12 中,set X = A 和 set X = B 是并发的,因为它们都互相不知道对方存在,也不存在因果关系。

系统中任意的两个写入 A 和 B,只可能存在三种关系:

Expand Down Expand Up @@ -640,7 +640,7 @@ A 和 B 并发 < === > A 不 happens-before B && B 不 happens-before A
因此需要根据实际情况,选择一些策略来解决冲突,合并数据。

1. 对于上述购物车中只增加物品的例子,可以使用“并集”来合并冲突数据。
2. 如果购物车汇总还有删除操作,就不能简单并了,但是可以将删除变为增加(写一个 tombstone 标记)。
2. 如果购物车中还有删除操作,就不能简单并了,但是可以将删除变为增加(写一个 tombstone 标记)。

### 版本向量

Expand Down
Loading