# 性能评测

## 理解性能

etcd 提供稳定的，持续的高性能。两个定义性能的因素：延迟(latency)和吞吐量(throughput)。延迟是完成操作的时间。吞吐量是在某个时间期间之内完成操作的总数量。当 etcd 接收并发客户端请求时，通常平均延迟随着总体吞吐量增加而增加。在通常的云环境，比如 Google Compute Engine (GCE) 标准的 `n-4` 或者 AWS 上相当的机器类型，一个三成员 etcd 集群在轻负载下可以在低于1毫秒内完成一个请求，并在重负载下可以每秒完成超过 30000 个请求。

etcd 使用 Raft 一致性算法来在成员之间复制请求并达成一致。一致性性能，特别是提交延迟，受限于两个物理约束：网络IO延迟和磁盘IO延迟。完成一个etcd请求的最小时间是成员之间的网络往返时延(Round Trip Time / RTT)，加需要提交数据到持久化存储的 `fdatasync` 时间。在一个数据中心内的 RTT 可能有数百毫秒。在美国典型的 RTT 是大概 50ms, 而在大陆之间可以慢到400ms. 旋转硬盘(注：指传统机械硬盘)的典型 fdatasync 延迟是大概 10ms。对于 SSD 硬盘, 延迟通常低于 1ms. 为了提高吞吐量, etcd 将多个请求打包在一起并提交给 Raft。这个批量策略让 etcd 在重负载试获得高吞吐量.

有其他子系统影响到 etcd 的整体性能。每个序列化的 etcd 请求必须通过 etcd 的 boltdb支持的(boltdb-backed) MVCC 存储引擎,它通常需要10微秒来完成。etcd 定期递增快照它最近实施的请求，将他们和之前在磁盘上的快照合并。这个过程可能导致延迟尖峰(latency spike)。虽然在SSD上这通常不是问题，在HDD上它可能加倍可观察到的延迟。而且，进行中的压缩可以影响 etcd 的性能。幸运的是，压缩通常无足轻重，因为压缩是错开的，因此它不和常规请求竞争资源。RPC 系统，gRPC，为 etcd 提供定义良好，可扩展的 API，但是它也引入了额外的延迟，尤其是本地读取。

## 评测

可以通过 etcd 自带的 [benchmark](https://github.com/coreos/etcd/tree/master/tools/benchmark) CLI 工具来评测 etcd 的性能.

对于某些基线性能数字，我们考虑的是3成员 etcd 集群，带有下列硬件配置：

* Google Cloud Compute Engine
* 3 台机器， 8 vCPUs + 16GB Memory + 50GB SSD
* 1 台机器(客户端)，16 vCPUs + 30GB Memory + 50GB SSD
* Ubuntu 15.10
* etcd v3 master 分支 (commit SHA d8f325d), Go 1.6.2

使用这些配置，etcd 能近似的写入：

| key的数量  | Key的大小 | Value的大小 | 连接数量 | 客户端数量 | 目标 etcd 服务器 | 平均写入 QPS | 每请求平均延迟 | 内存    |
| ------- | ------ | -------- | ---- | ----- | ----------- | -------- | ------- | ----- |
| 10,000  | 8      | 256      | 1    | 1     | leader only | 525      | 2ms     | 35 MB |
| 100,000 | 8      | 256      | 100  | 1000  | leader only | 25,000   | 30ms    | 35 MB |
| 100,000 | 8      | 256      | 100  | 1000  | all members | 33,000   | 25ms    | 35 MB |

> 注：key和value的大小单位是 字节 / bytes

采样命令是:

```bash
# 假定 IP_1 是 leader, 写入请求发到 leader
benchmark --endpoints={IP_1} --conns=1 --clients=1 \
    put --key-size=8 --sequential-keys --total=10000 --val-size=256
benchmark --endpoints={IP_1} --conns=100 --clients=1000 \
    put --key-size=8 --sequential-keys --total=100000 --val-size=256

# 写入发到所有成员
benchmark --endpoints={IP_1},{IP_2},{IP_3} --conns=100 --clients=1000 \
    put --key-size=8 --sequential-keys --total=100000 --val-size=256
```

为了一致性，线性化(Linearizable)读取请求要通过集群成员的法定人数来获取最新的数据。串行化(Serializable)读取请求比线性化读取要廉价一些，因为他们是通过任意单台 etcd 服务器来提供服务，而不是成员的法定人数，代价是可能提供过期数据。etcd 可以读取：

| 请求数量    | Key 大小 | Value 大小 | 连接数量 | 客户端数量 | 一致性          | 每请求平均延迟 | 平均读取 QPS |
| ------- | ------ | -------- | ---- | ----- | ------------ | ------- | -------- |
| 10,000  | 8      | 256      | 1    | 1     | Linearizable | 2ms     | 560      |
| 10,000  | 8      | 256      | 1    | 1     | Serializable | 0.4ms   | 7,500    |
| 100,000 | 8      | 256      | 100  | 1000  | Linearizable | 15ms    | 43,000   |
| 100,000 | 8      | 256      | 100  | 1000  | Serializable | 9ms     | 93,000   |

采样命令是:

```bash
# Linearizable 读取请求
benchmark --endpoints={IP_1},{IP_2},{IP_3} --conns=1 --clients=1 \
    range YOUR_KEY --consistency=l --total=10000
benchmark --endpoints={IP_1},{IP_2},{IP_3} --conns=100 --clients=1000 \
    range YOUR_KEY --consistency=l --total=100000

# Serializable 读取请求，使用每个成员然后将数字加起来
for endpoint in {IP_1} {IP_2} {IP_3}; do
    benchmark --endpoints=$endpoint --conns=1 --clients=1 \
        range YOUR_KEY --consistency=s --total=10000
done
for endpoint in {IP_1} {IP_2} {IP_3}; do
    benchmark --endpoints=$endpoint --conns=100 --clients=1000 \
        range YOUR_KEY --consistency=s --total=100000
done
```

当在新的环境中第一次搭建 etcd 集群时，我们鼓励运行评测测试来保证集群达到足够的性能;集群延迟和吞吐量对微小的环境差异会很敏感。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doczhcn.gitbook.io/etcd/index/index-1/performance.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
