You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@horaedb.apache.org by ti...@apache.org on 2023/12/26 03:28:57 UTC
(incubator-horaedb-docs) branch main updated: docs: add chinese documentation (#121)
This is an automated email from the ASF dual-hosted git repository.
tison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-horaedb-docs.git
The following commit(s) were added to refs/heads/main by this push:
new e9c6f02 docs: add chinese documentation (#121)
e9c6f02 is described below
commit e9c6f026876264cf6c8acf7cd07a0376ea9a924c
Author: chunshao.rcs <ch...@antgroup.com>
AuthorDate: Tue Dec 26 11:28:52 2023 +0800
docs: add chinese documentation (#121)
---
.github/workflows/deploy.yml | 4 +-
Makefile | 3 +-
docs/book.toml | 11 +-
docs/move.sh | 6 +
docs/src/.asf.yaml | 53 --
docs/src/cn/SUMMARY.md | 66 ++
docs/src/cn/about.md | 22 +
docs/src/cn/cluster_deployment/README.md | 10 +
docs/src/{ => cn}/cluster_deployment/no_meta.md | 64 +-
docs/src/{ => cn}/cluster_deployment/platform.md | 6 +-
docs/src/cn/cluster_deployment/with_meta.md | 269 ++++++++
docs/src/{ => cn}/design/architecture.md | 292 ++++-----
docs/src/cn/design/clustering.md | 182 ++++++
docs/src/cn/design/shared_nothing.md | 98 +++
docs/src/cn/design/storage.md | 63 ++
docs/src/{ => cn}/design/table_partitioning.md | 62 +-
docs/src/{ => cn}/design/wal.md | 2 +-
docs/src/{ => cn}/design/wal_on_kafka.md | 99 ++-
docs/src/cn/design/wal_on_rocksdb.md | 84 +++
docs/src/cn/dev/README.md | 1 +
docs/src/cn/dev/compile_run.md | 60 ++
docs/src/cn/dev/conventional_commit.md | 3 +
docs/src/{ => cn}/dev/platform.md | 0
docs/src/{ => cn}/dev/profiling.md | 26 +-
docs/src/cn/dev/roadmap.md | 52 ++
docs/src/cn/dev/style_guide.md | 3 +
docs/src/cn/ecosystem/README.md | 7 +
docs/src/cn/ecosystem/influxdb.md | 88 +++
docs/src/{ => cn}/ecosystem/opentsdb.md | 17 +-
docs/src/cn/ecosystem/prometheus.md | 49 ++
docs/src/cn/operation/README.md | 9 +
docs/src/{ => cn}/operation/block_list.md | 26 +-
docs/src/{ => cn}/operation/horaemeta.md | 52 +-
docs/src/cn/operation/observability.md | 98 +++
docs/src/{ => cn}/operation/system_table.md | 15 +-
docs/src/{ => cn}/operation/table.md | 28 +-
docs/src/{ => cn}/protocol.md | 0
docs/src/cn/quick_start.md | 115 ++++
docs/src/{ => cn}/sdk/README.md | 2 +-
docs/src/cn/sdk/go.md | 109 ++++
docs/src/{ => cn}/sdk/java.md | 104 ++--
docs/src/cn/sdk/python.md | 156 +++++
docs/src/{ => cn}/sdk/rust.md | 63 +-
docs/src/cn/sql/README.md | 19 +
docs/src/cn/sql/ddl/README.md | 7 +
docs/src/{ => cn}/sql/ddl/alter_table.md | 16 +-
docs/src/cn/sql/ddl/create_table.md | 80 +++
docs/src/cn/sql/ddl/drop_table.md | 11 +
docs/src/cn/sql/dml/README.md | 6 +
docs/src/cn/sql/dml/insert.md | 17 +
docs/src/{ => cn}/sql/dml/select.md | 6 +-
docs/src/cn/sql/engine_options.md | 92 +++
docs/src/cn/sql/functions/aggregate_functions.md | 35 ++
docs/src/cn/sql/functions/scalar_functions.md | 105 ++++
docs/src/cn/sql/identifier.md | 3 +
docs/src/cn/sql/model/README.md | 6 +
docs/src/{ => cn}/sql/model/data_types.md | 8 +-
docs/src/cn/sql/model/special_columns.md | 65 ++
docs/src/{ => cn}/sql/utility.md | 23 +-
docs/src/{ => cn}/trouble_shooting.md | 0
docs/src/{ => en}/SUMMARY.md | 0
docs/src/{ => en}/about.md | 2 +
docs/src/{ => en}/cluster_deployment/README.md | 0
docs/src/{ => en}/cluster_deployment/no_meta.md | 0
docs/src/{ => en}/cluster_deployment/platform.md | 0
docs/src/{ => en}/cluster_deployment/with_meta.md | 6 +-
docs/src/{ => en}/design/architecture.md | 0
docs/src/{ => en}/design/clustering.md | 0
docs/src/{ => en}/design/storage.md | 2 +-
docs/src/{ => en}/design/table_partitioning.md | 0
docs/src/{ => en}/design/wal.md | 0
docs/src/{ => en}/design/wal_on_kafka.md | 0
docs/src/{ => en}/design/wal_on_rocksdb.md | 0
docs/src/{ => en}/dev/README.md | 0
docs/src/{ => en}/dev/compile_run.md | 0
docs/src/{ => en}/dev/conventional_commit.md | 0
docs/src/{ => en}/dev/platform.md | 0
docs/src/{ => en}/dev/profiling.md | 0
docs/src/{ => en}/dev/roadmap.md | 0
docs/src/{ => en}/dev/style_guide.md | 0
docs/src/{ => en}/ecosystem/README.md | 0
docs/src/{ => en}/ecosystem/influxdb.md | 0
docs/src/{ => en}/ecosystem/opentsdb.md | 0
docs/src/{ => en}/ecosystem/prometheus.md | 0
docs/src/{ => en}/operation/README.md | 0
docs/src/{ => en}/operation/block_list.md | 0
docs/src/{ => en}/operation/horaemeta.md | 0
docs/src/{ => en}/operation/observability.md | 8 +-
docs/src/{ => en}/operation/system_table.md | 0
docs/src/{ => en}/operation/table.md | 0
docs/src/{ => en}/protocol.md | 0
docs/src/{ => en}/quick_start.md | 0
docs/src/{ => en}/sdk/README.md | 0
docs/src/{ => en}/sdk/go.md | 0
docs/src/{ => en}/sdk/java.md | 0
docs/src/{ => en}/sdk/python.md | 0
docs/src/{ => en}/sdk/rust.md | 0
docs/src/{ => en}/sql/README.md | 0
docs/src/{ => en}/sql/ddl/README.md | 0
docs/src/{ => en}/sql/ddl/alter_table.md | 0
docs/src/{ => en}/sql/ddl/create_table.md | 0
docs/src/{ => en}/sql/ddl/drop_table.md | 0
docs/src/{ => en}/sql/dml/README.md | 0
docs/src/{ => en}/sql/dml/insert.md | 0
docs/src/{ => en}/sql/dml/select.md | 0
docs/src/{ => en}/sql/engine_options.md | 0
.../{ => en}/sql/functions/aggregate_functions.md | 0
.../src/{ => en}/sql/functions/scalar_functions.md | 2 +-
docs/src/{ => en}/sql/identifier.md | 0
docs/src/{ => en}/sql/model/README.md | 0
docs/src/{ => en}/sql/model/data_types.md | 0
docs/src/{ => en}/sql/model/special_columns.md | 0
docs/src/{ => en}/sql/utility.md | 0
docs/src/{ => en}/trouble_shooting.md | 0
docs/theme/index.hbs | 678 ++++++++++-----------
115 files changed, 2803 insertions(+), 873 deletions(-)
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index d2c755a..0cf1156 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -21,9 +21,11 @@ jobs:
fetch-depth: 0
- run: make install
- run: make build
+ # copy .asf.yaml to docs/book/html which asf site needs
+ - run: cp .asf.yaml docs/book/html
- uses: peaceiris/actions-gh-pages@v3
if: github.event_name != 'pull_request'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./docs/book
+ publish_dir: ./docs/book/html
publish_branch: asf-site
diff --git a/Makefile b/Makefile
index b49d0da..987bd46 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,6 @@
install:
- cargo install mdbook@0.4.36
+ cargo install mdbook@0.4.25
+ cargo install mdbook-i18n --git https://github.com/chunshao90/mdbook-i18n.git --rev ca497cff369e0a5cedcd4024af6e1f05cc5050c5
serve:
cd docs && mdbook serve
diff --git a/docs/book.toml b/docs/book.toml
index 72bbf26..451b7b9 100644
--- a/docs/book.toml
+++ b/docs/book.toml
@@ -2,7 +2,7 @@
authors = ["HoraeDB Authors"]
language = "en"
multilingual = true
-src = "src"
+src = "src/en"
title = "HoraeDB Documentation"
[build]
@@ -10,6 +10,15 @@ use-default-preprocessors = false
[preprocessor.links]
+[[output.i18n.translations]]
+src = "src/cn"
+language = "cn"
+translators = ["HoraeDB Authors"]
+title = "HoraeDB 文档"
+
+[output.random]
+command = "/bin/bash ../../move.sh"
+
[output.html]
git-repository-url = "https://github.com/apache/incubator-horaedb"
edit-url-template = "https://github.com/apache/incubator-horaedb-docs/edit/main/docs/{path}"
diff --git a/docs/move.sh b/docs/move.sh
new file mode 100644
index 0000000..af84258
--- /dev/null
+++ b/docs/move.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+echo "ready to move files"
+cp -R ../i18n/cn ../html/cn
+cp -R ../i18n/en ../html/en
+cp -R ../../src/resources ../../book/html/resources
+echo "copy done"
diff --git a/docs/src/.asf.yaml b/docs/src/.asf.yaml
deleted file mode 100644
index 9f5ac6e..0000000
--- a/docs/src/.asf.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-# For more information, see https://cwiki.apache.org/confluence/display/INFRA/Git+-+.asf.yaml+features.
-
-github:
- description: HoraeDB Website.
- homepage: https://horaedb.apache.org/
- labels:
- - rust
- - sql
- - database
- - distributed-database
- - cloud-native
- - tsdb
- - timeseries-database
- - timeseries-analysis
- - iot-database
- - horaedb
- enabled_merge_buttons:
- squash: true
- merge: false
- rebase: true
- protected_branches:
- main:
- required_pull_request_reviews:
- dismiss_stale_reviews: true
- required_approving_review_count: 1
- asf-site: {}
-
-publish:
- whoami: asf-site
-
-notifications:
- commits: commits@horaedb.apache.org
- issues: commits@horaedb.apache.org
- pullrequests: commits@horaedb.apache.org
- jobs: commits@horaedb.apache.org
- discussions: commits@horaedb.apache.org
diff --git a/docs/src/cn/SUMMARY.md b/docs/src/cn/SUMMARY.md
new file mode 100644
index 0000000..ada44e4
--- /dev/null
+++ b/docs/src/cn/SUMMARY.md
@@ -0,0 +1,66 @@
+# 目录
+
+# 简介
+
+- [什么是 HoraeDB](about.md)
+- [快速开始](quick_start.md)
+
+# 用户手册
+
+- [SQL 语法](sql/README.md)
+ - [数据模型](sql/model/README.md)
+ - [数据类型](sql/model/data_types.md)
+ - [特殊字段](sql/model/special_columns.md)
+ - [标识符](sql/identifier.md)
+ - [表结构操作](sql/ddl/README.md)
+ - [建表](sql/ddl/create_table.md)
+ - [表结构变更](sql/ddl/alter_table.md)
+ - [删除表](sql/ddl/drop_table.md)
+ - [数据操作](sql/dml/README.md)
+ - [数据写入](sql/dml/insert.md)
+ - [数据查询](sql/dml/select.md)
+ - [引擎配置](sql/engine_options.md)
+ - [常见 SQL](sql/utility.md)
+ - [标量函数](sql/functions/scalar_functions.md)
+ - [聚合函数](sql/functions/aggregate_functions.md)
+- [集群部署](cluster_deployment/README.md)
+ - [支持平台](cluster_deployment/platform.md)
+ - [NoMeta 模式](cluster_deployment/no_meta.md)
+ - [WithMeta 模式](cluster_deployment/with_meta.md)
+- [SDK](sdk/README.md)
+ - [Java SDK](sdk/java.md)
+ - [Go SDK](sdk/go.md)
+ - [Python SDK](sdk/python.md)
+ - [Rust SDK](sdk/rust.md)
+- [运维](operation/README.md)
+ - [表](operation/table.md)
+ - [系统表](operation/system_table.md)
+ - [黑名单](operation/block_list.md)
+ - [监控](operation/observability.md)
+ - [HoraeMeta](operation/horaemeta.md)
+- [周边生态](ecosystem/README.md)
+ - [Prometheus](ecosystem/prometheus.md)
+ - [InfluxDB](ecosystem/influxdb.md)
+ - [OpenTSDB](ecosystem/opentsdb.md)
+
+# 开发者手册
+
+- [支持平台](dev/platform.md)
+- [编译运行](dev/compile_run.md)
+ - [剖析](dev/profiling.md)
+- [开发规约](dev/conventional_commit.md)
+- [风格规范](dev/style_guide.md)
+- [里程碑](dev/roadmap.md)
+
+# 技术系列文章
+
+- [整体架构](design/architecture.md)
+- [集群](design/clustering.md)
+- [Shared-Nothing](design/shared_nothing.md)
+- [存储](design/storage.md)
+- [WAL](design/wal.md)
+ - [WAL on RocksDB](design/wal_on_rocksdb.md)
+ - [WAL on Kafka](design/wal_on_kafka.md)
+- [分区表](design/table_partitioning.md)
+
+[//]: # "- [查询介绍](query.md)"
diff --git a/docs/src/cn/about.md b/docs/src/cn/about.md
new file mode 100644
index 0000000..f0c45c9
--- /dev/null
+++ b/docs/src/cn/about.md
@@ -0,0 +1,22 @@
+
+
+
+[](https://github.com/apache/incubator-horaedb/actions/workflows/ci.yml)
+[](https://github.com/apache/incubator-horaedb/issues)
+
+HoraeDB 是一款高性能、分布式的云原生时序数据库。
+
+# 愿景
+
+在经典的时序数据库中,`Tag` 列(InfluxDB 称为 `Tag`,Prometheus 称为 `Label`)通常使用倒排来进行索引。
+我们发现在不同的情况下,`Tag` 的基数差异很大。在某些情况下,`Tag` 的基数非常高,存储和检索倒排索引的成本非常高。
+同时,我们发现分析型数据库经常使用的扫描+剪枝可以很好地处理这些场景。
+
+HoraeDB 的基础设计思想是采用混合存储格式和相应的查询方法,以便在处理时序和分析场景时都获得更好的性能。
+
+# 如何使用 HoraeDB?
+
+- 查看 [快速开始](quick_start.md) 掌握快速使用 HoraeDB 的方式
+- HoraeDB 支持的数据模型请查看 [Data Model](sql/model/README.md)
+- SQL 使用相关请查看 [SQL](sql/README.md)
+- HoraeDB 支持的相关 SDK 请查看 [SDK](sdk/README.md)
diff --git a/docs/src/cn/cluster_deployment/README.md b/docs/src/cn/cluster_deployment/README.md
new file mode 100644
index 0000000..e5a7a9a
--- /dev/null
+++ b/docs/src/cn/cluster_deployment/README.md
@@ -0,0 +1,10 @@
+# 集群部署
+
+在[快速开始](../quick_start.md)部分我们已经介绍过单机版本 HoraeDB 的部署。
+
+除此之外,HoraeDB 作为一个分布式时序数据库,多个 HoraeDB 实例能够以集群的方式提供可伸缩和高可用的数据服务。
+
+由于目前 HoraeDB 对于 Kubernetes 的支持还在开发之中,目前 HoraeDB 集群部署只能通过手动完成,集群部署的模式主要有两种,两者的区别在于是否需要部署 HoraeMeta,对于 `NoMeta` 的模式,我们仅建议在测试场景下使用。
+
+- [NoMeta 模式(仅供测试使用)](no_meta.md)
+- [WithMeta 模式](with_meta.md)
diff --git a/docs/src/cluster_deployment/no_meta.md b/docs/src/cn/cluster_deployment/no_meta.md
similarity index 61%
copy from docs/src/cluster_deployment/no_meta.md
copy to docs/src/cn/cluster_deployment/no_meta.md
index 04db40d..bef35df 100644
--- a/docs/src/cluster_deployment/no_meta.md
+++ b/docs/src/cn/cluster_deployment/no_meta.md
@@ -1,20 +1,22 @@
-**Note: This feature is for testing use only, not recommended for production use, related features may change in the future.**
+**注意:此功能仅供测试使用,不推荐生产使用,相关功能将来可能会发生变化。**
-# NoMeta
+# NoMeta 模式
-This guide shows how to deploy a HoraeDB cluster without HoraeMeta, but with static, rule-based routing.
+本章介绍如何部署一个静态(无 HoraeMeta)的 HoraeDB 集群。
-The crucial point here is that HoraeDB server provides configurable routing function on table name so what we need is just a valid config containing routing rules which will be shipped to every HoraeDB instance in the cluster.
+在没有 HoraeMeta 的情况下,利用 HoraeDB 服务端针对表名提供了可配置的路由功能即可实现集群化部署,为此我们需要提供一个包含路由规则的正确配置。根据这个配置,请求会被发送到集群中的每个 HoraeDB 实例。
-## Target
+## 目标
-First, let's assume that our target is to deploy a cluster consisting of two HoraeDB instances on the same machine. And a large cluster of more HoraeDB instances can be deployed according to the two-instance example.
+本文的目标是:在同一台机器上部署一个集群,这个集群包含两个 HoraeDB 实例。
-## Prepare Config
+如果想要部署一个更大规模的集群,参考此方案也可以进行部署。
-### Basic
+## 准备配置文件
-Suppose the basic config of HoraeDB is:
+### 基础配置
+
+HoraeDB 的基础配置如下:
```toml
[server]
@@ -37,9 +39,9 @@ type = "RocksDB"
data_dir = "/tmp/horaedb"
```
-In order to deploy two HoraeDB instances on the same machine, the config should choose different ports to serve and data directories to store data.
+为了在同一个机器上部署两个实例,我们需要为每个实例配置不同的服务端口和数据目录。
-Say the `HoraeDB_0`'s config is:
+实例 `HoraeDB_0` 的配置如下:
```toml
[server]
@@ -62,7 +64,7 @@ type = "RocksDB"
data_dir = "/tmp/horaedb_0"
```
-Then the `HoraeDB_1`'s config is:
+实例 `HoraeDB_1` 的配置如下:
```toml
[server]
@@ -85,11 +87,11 @@ type = "RocksDB"
data_dir = "/tmp/horaedb_1"
```
-### Schema&Shard Declaration
+### Schema 和 Shard
-Then we should define the common part -- schema&shard declaration and routing rules.
+接下来我们需要定义 `Schema` 和分片以及路由规则。
-Here is the config for schema&shard declaration:
+如下定义了 `Schema` 和分片:
```toml
[cluster_deployment]
@@ -122,14 +124,14 @@ addr = '127.0.0.1'
port = 18831
```
-In the config above, two schemas are declared:
+上述的配置中,定义了两个 `Schema`:
-- `public_0` has two shards served by `HoraeDB_0`.
-- `public_1` has two shards served by both `HoraeDB_0` and `HoraeDB_1`.
+- `public_0` 有两个分片在 `HoraeDB_0` 实例上。
+- `public_1` 有两个分片同时在 `HoraeDB_0` 和 `HoraeDB_1` 实例上。
-### Routing Rules
+### 路由规则
-Provided with schema&shard declaration, routing rules can be defined and here is an example of prefix rule:
+定义 `Schema` 和分片后,需要定义路由规则,如下是一个前缀路由规则:
```toml
[[cluster_deployment.route_rules.prefix_rules]]
@@ -138,9 +140,9 @@ prefix = 'prod_'
shard = 0
```
-This rule means that all the table with `prod_` prefix belonging to `public_0` should be routed to `shard_0` of `public_0`, that is to say, `HoraeDB_0`. As for the other tables whose names are not prefixed by `prod_` will be routed by hash to both `shard_0` and `shard_1` of `public_0`.
+在这个规则里,`public_0` 中表名以 `prod_` 为前缀的所有表属于,相关操作会被路由到 `shard_0` 也就是 `HoraeDB_0` 实例。 `public_0` 中其他的表会以 hash 的方式路由到 `shard_0` 和 `shard_1`.
-Besides prefix rule, we can also define a hash rule:
+在前缀规则之外,我们也可以定义一个 hash 规则:
```toml
[[cluster_deployment.route_rules.hash_rules]]
@@ -148,16 +150,16 @@ schema = 'public_1'
shards = [0, 1]
```
-This rule tells HoraeDB to route `public_1`'s tables to both `shard_0` and `shard_1` of `public_1`, that is to say, `HoraeDB0` and `HoraeDB_1`. And actually this is default routing behavior if no such rule provided for schema `public_1`.
+这个规则告诉 HoraeDB, `public_1` 的所有表会被路由到 `public_1` 的 `shard_0` and `shard_1`, 也就是 `HoraeDB0` 和 `HoraeDB_1`.
+实际上如果没有定义 `public_1` 的路由规则,这是默认的路由行为。
-For now, we can provide the full example config for `HoraeDB_0` and `HoraeDB_1`:
+`HoraeDB_0` 和 `HoraeDB_1` 实例完整的配置文件如下:
```toml
[server]
bind_addr = "0.0.0.0"
http_port = 5440
grpc_port = 8831
-mysql_port = 3307
[logger]
level = "info"
@@ -208,7 +210,6 @@ port = 18831
bind_addr = "0.0.0.0"
http_port = 15440
grpc_port = 18831
-mysql_port = 13307
[logger]
level = "info"
@@ -254,17 +255,18 @@ addr = '127.0.0.1'
port = 18831
```
-Let's name the two different config files as `config_0.toml` and `config_1.toml` but you should know in the real environment the different HoraeDB instances can be deployed across different machines, that is to say, there is no need to choose different ports and data directories for different HoraeDB instances so that all the HoraeDB instances can share one exactly **same** config file.
+我们给这两份不同的配置文件分别命名为 `config_0.toml` 和 `config_1.toml`;
+但是在实际环境中不同的实例可以部署在不同的服务器上,也就是说,不同的实例没有必要设置不同的服务端口和数据目录,这种情况下实例的配置可以使用同一份配置文件。
-## Start HoraeDBs
+## 启动 HoraeDB
-After the configs are prepared, what we should to do is to start HoraeDB container with the specific config.
+配置准备好后,我们就可以开始启动 HoraeDB 容器了。
-Just run the commands below:
+启动命令如下:
```shell
sudo docker run -d -t --name horaedb_0 -p 5440:5440 -p 8831:8831 -v $(pwd)/config_0.toml:/etc/horaedb/horaedb.toml horaedb/horaedb-server
sudo docker run -d -t --name horaedb_1 -p 15440:15440 -p 18831:18831 -v $(pwd)/config_1.toml:/etc/horaedb/horaedb.toml horaedb/horaedb-server
```
-After the two containers are created and starting running, read and write requests can be served by the two-instances HoraeDB cluster.
+容器启动成功后,两个实例的 HoraeDB 集群就搭建完成了,可以开始提供读写服务。
diff --git a/docs/src/cluster_deployment/platform.md b/docs/src/cn/cluster_deployment/platform.md
similarity index 65%
copy from docs/src/cluster_deployment/platform.md
copy to docs/src/cn/cluster_deployment/platform.md
index 0d6af92..97547a0 100644
--- a/docs/src/cluster_deployment/platform.md
+++ b/docs/src/cn/cluster_deployment/platform.md
@@ -1,4 +1,4 @@
-As an open source cloud-native, HoraeDB can be deployed in the Intel/ARM-based architecture server, and major virtualization environments.
+作为一个开源的数据库,HoraeDB 可以部署在基于英特尔 /ARM 架构的服务器,以及常见的虚拟环境。
| OS | status |
| :------------------------------------------------: | :----: |
@@ -8,5 +8,5 @@ As an open source cloud-native, HoraeDB can be deployed in the Intel/ARM-based a
| macOS 11 or later | ✅ |
| Windows | ❌ |
-- For production workloads, Linux is the preferred platform.
-- macOS is mainly used for development
+- 生产环境下 , Linux 是首选平台。
+- macOS 主要用在开发环境。
diff --git a/docs/src/cn/cluster_deployment/with_meta.md b/docs/src/cn/cluster_deployment/with_meta.md
new file mode 100644
index 0000000..d1abbf1
--- /dev/null
+++ b/docs/src/cn/cluster_deployment/with_meta.md
@@ -0,0 +1,269 @@
+# WithMeta 模式
+
+本文展示如何部署一个由 HoraeMeta 控制的 HoraeDB 集群,有了 HoraeMeta 提供的服务,如果 HoraeDB 使用存储不在本地的话,就可以实现很多分布式特性,比如水平扩容、负载均衡、服务高可用等。
+
+## 部署 HoraeMeta
+
+HoraeMeta 是 HoraeDB 分布式模式的核心服务之一,用于管理 HoraeDB 节点的调度,为 HoraeDB 集群提供高可用、负载均衡、集群管控等能力。
+HoraeMeta 本身通过嵌入式的 [ETCD](https://github.com/etcd-io/etcd) 保障高可用。此外,ETCD 的服务也被暴露给 HoraeDB 用于实现分布式锁使用。
+
+### 编译打包
+
+- 安装 Golang,版本号 >= 1.19。
+- 在项目根目录下使用 `make build` 进行编译打包。
+
+### 部署方式
+
+#### 启动配置
+
+目前 HoraeMeta 支持以配置文件和环境变量两种方式来指定服务启动配置。我们提供了配置文件方式启动的示例,具体可以参考 [config](https://github.com/apache/incubator-horaedb-meta/tree/main/config)。
+环境变量的配置优先级高于配置文件,当同时存在时,以环境变量为准。
+
+#### 动态拓扑和静态拓扑
+
+即使使用了 HoraeMeta 来部署 HoraeDB 集群,也可以选择静态拓扑或动态拓扑。对于静态拓扑,表的分布在集群初始化后是静态的,而对于动态拓扑,表可以在不同的 HoraeDB 节点之间进行动态迁移以达到负载平衡或者 failover 的目的。但是动态拓扑只有在 HoraeDB 节点使用的存储是非本地的情况下才能启用,否则会因为表的数据是持久化在本地,当表转移到不同的 HoraeDB 节点时会导致数据损坏。
+
+目前,HoraeMeta 默认关闭集群拓扑的动态调度,并且在本篇指南中,这个选项也不会被开启,因为指南中的例子采用的是本地存储。如果要启用动态调度,可以将 `TOPOLOGY_TYPE` 设置为 `dynamic`(默认为 `static`),之后负载均衡和 failover 将会起作用。但是需要注意的是,如果底层存储是本地磁盘,则不要启用它。
+
+此外对于静态拓扑,参数 `DEFAULT_CLUSTER_NODE_COUNT` 表示已部署集群中 HoraeDB 节点的数量,应该被设置为 HoraeDB 服务器的实际机器数,这个参数非常重要,因为集群初始化完毕之后,HoraeDB 集群将无法再增减机器。
+
+#### 启动实例
+
+HoraeMeta 基于 etcd 实现高可用,在线上环境我们一般部署多个节点,但是在本地环境和测试时,可以直接部署单个节点来简化整个部署流程。
+
+- 单节点
+
+```bash
+docker run -d --name horaemeta-server \
+ -p 2379:2379 \
+ ghcr.io/apache/horaemeta-server:nightly-20231225-ab067bf0
+```
+
+- 多节点
+
+```bash
+wget https://raw.githubusercontent.com/apache/incubator-horaedb-docs/main/docs/src/resources/config-horaemeta-cluster0.toml
+
+docker run -d --network=host --name horaemeta-server0 \
+ -v $(pwd)/config-horaemeta-cluster0.toml:/etc/horaemeta/horaemeta.toml \
+ ghcr.io/apache/horaemeta-server:nightly-20231225-ab067bf0
+
+wget https://raw.githubusercontent.com/apache/incubator-horaedb-docs/main/docs/src/resources/config-horaemeta-cluster1.toml
+
+docker run -d --network=host --name horaemeta-server1 \
+ -v $(pwd)/config-horaemeta-cluster1.toml:/etc/horaemeta/horaemeta.toml \
+ ghcr.io/apache/horaemeta-server:nightly-20231225-ab067bf0
+
+wget https://raw.githubusercontent.com/apache/incubator-horaedb-docs/main/docs/src/resources/config-horaemeta-cluster2.toml
+
+docker run -d --network=host --name horaemeta-server2 \
+ -v $(pwd)/config-horaemeta-cluster2.toml:/etc/horaemeta/horaemeta.toml \
+ ghcr.io/apache/horaemeta-server:nightly-20231225-ab067bf0
+```
+
+如果 HoraeDB 底层采用的是远程存储,可以环境变量来开启动态调度:只需将 `-e ENABLE_SCHEDULE=true` 加入到 docker run 命令中去。
+
+## 部署 HoraeDB
+
+在 `NoMeta` 模式中,由于 HoraeDB 集群拓扑是静态的,因此 HoraeDB 只需要一个本地存储来作为底层的存储层即可。但是在 `WithMeta` 模式中,集群的拓扑是可以变化的,因此如果 HoraeDB 的底层存储使用一个独立的存储服务的话,HoraeDB 集群就可以获得分布式系统的一些特性:高可用、负载均衡、水平扩展等。
+当然,HoraeDB 仍然可以使用本地存储,这样的话,集群的拓扑仍然是静态的。
+
+存储相关的配置主要包括两个部分:
+
+- Object Storage
+- WAL Storage
+
+注意:在生产环境中如果我们把 HoraeDB 部署在多个节点上时,请按照如下方式把机器的网络地址设置到环境变量中:
+
+```shell
+export HORAEDB_SERVER_ADDR="{server_addr}:8831"
+```
+
+注意,此网络地址用于 HoraeMeta 和 HoraeDB 通信使用,需保证网络联通可用。
+
+### Object Storage
+
+#### 本地存储
+
+类似 `NoMeta` 模式,我们仍然可以为 HoraeDB 配置一个本地磁盘作为底层存储:
+
+```toml
+[analytic.storage.object_store]
+type = "Local"
+data_dir = "/home/admin/data/horaedb"
+```
+
+#### OSS
+
+Aliyun OSS 也可以作为 HoraeDB 的底层存储,以此提供数据容灾能力。下面是一个配置示例,示例中的模版变量需要被替换成实际的 OSS 参数才可以真正的使用:
+
+```toml
+[analytic.storage.object_store]
+type = "Aliyun"
+key_id = "{key_id}"
+key_secret = "{key_secret}"
+endpoint = "{endpoint}"
+bucket = "{bucket}"
+prefix = "{data_dir}"
+```
+
+#### S3
+
+Amazon S3 也可以作为 HoraeDB 的底层存储,下面是一个配置示例,示例中的模版变量需要被替换成实际的 S3 参数才可以真正的使用:
+
+```toml
+[analytic.storage.object_store]
+type = "S3"
+region = "{region}"
+key_id = "{key_id}"
+key_secret = "{key_secret}"
+endpoint = "{endpoint}"
+bucket = "{bucket}"
+prefix = "{prefix}"
+```
+
+### WAL Storage
+
+#### RocksDB
+
+基于 RocksDB 的 WAL 也是一种本地存储,无第三方依赖,可以很方便的快速部署:
+
+```toml
+[analytic.wal]
+type = "RocksDB"
+data_dir = "/home/admin/data/horaedb"
+```
+
+#### OceanBase
+
+如果已经有了一个部署好的 OceanBase 集群的话,HoraeDB 可以使用它作为 WAL Storage 来保证其数据的容灾性。下面是一个配置示例,示例中的模版变量需要被替换成实际的 OceanBase 集群的参数才可以真正的使用:
+
+```toml
+[analytic.wal]
+type = "Obkv"
+
+[analytic.wal.data_namespace]
+ttl = "365d"
+
+[analytic.wal.obkv]
+full_user_name = "{full_user_name}"
+param_url = "{param_url}"
+password = "{password}"
+
+[analytic.wal.obkv.client]
+sys_user_name = "{sys_user_name}"
+sys_password = "{sys_password}"
+```
+
+#### Kafka
+
+如果你已经部署了一个 Kafka 集群,HoraeDB 可以也可以使用它作为 WAL Storage。下面是一个配置示例,示例中的模版变量需要被替换成实际的 Kafka 集群的参数才可以真正的使用:
+
+```toml
+[analytic.wal]
+type = "Kafka"
+
+[analytic.wal.kafka.client]
+boost_broker = "{boost_broker}"
+```
+
+#### Meta 客户端配置
+
+除了存储层的配置外,HoraeDB 需要 HoraeMeta 相关的配置来与 HoraeMeta 集群进行通信:
+
+```
+[cluster.meta_client]
+cluster_name = 'defaultCluster'
+meta_addr = 'http://{HoraeMetaAddr}:2379'
+lease = "10s"
+timeout = "5s"
+
+[cluster_deployment.etcd_client]
+server_addrs = ['http://{HoraeMetaAddr}:2379']
+```
+
+### 完整配置
+
+将上面提到的所有关键配置合并之后,我们可以得到一个完整的、可运行的配置。为了让这个配置可以直接运行起来,配置中均采用了本地存储:基于 RocksDB 的 WAL 和本地磁盘的 Object Storage:
+
+```toml
+[server]
+bind_addr = "0.0.0.0"
+http_port = 5440
+grpc_port = 8831
+
+[logger]
+level = "info"
+
+[runtime]
+read_thread_num = 20
+write_thread_num = 16
+background_thread_num = 12
+
+[cluster_deployment]
+mode = "WithMeta"
+
+[cluster_deployment.meta_client]
+cluster_name = 'defaultCluster'
+meta_addr = 'http://127.0.0.1:2379'
+lease = "10s"
+timeout = "5s"
+
+[cluster_deployment.etcd_client]
+server_addrs = ['127.0.0.1:2379']
+
+[analytic]
+write_group_worker_num = 16
+replay_batch_size = 100
+max_replay_tables_per_batch = 128
+write_group_command_channel_cap = 1024
+sst_background_read_parallelism = 8
+
+[analytic.manifest]
+scan_batch_size = 100
+snapshot_every_n_updates = 10000
+scan_timeout = "5s"
+store_timeout = "5s"
+
+[analytic.wal]
+type = "RocksDB"
+data_dir = "/home/admin/data/horaedb"
+
+[analytic.storage]
+mem_cache_capacity = "20GB"
+# 1<<8=256
+mem_cache_partition_bits = 8
+
+[analytic.storage.object_store]
+type = "Local"
+data_dir = "/home/admin/data/horaedb/"
+
+[analytic.table_opts]
+arena_block_size = 2097152
+write_buffer_size = 33554432
+
+[analytic.compaction]
+schedule_channel_len = 16
+schedule_interval = "30m"
+max_ongoing_tasks = 8
+memory_limit = "4G"
+```
+
+将这个配置命名成 `config.toml`。至于使用远程存储的配置示例在下面我们也提供了,需要注意的是,配置中的相关参数需要被替换成实际的参数才能真正使用:
+
+- [本地 RocksDB WAL + OSS](../../resources/config_local_oss.toml)
+- [OceanBase WAL + OSS](../../resources/config_obkv_oss.toml)
+- [Kafka WAL + OSS](../../resources/config_kafka_oss.toml)
+
+### 启动集群
+
+首先,我们先启动 HoraeMeta:
+
+```bash
+docker run -d --name horaemeta-server \
+ -p 2379:2379 \
+ ghcr.io/apache/horaemeta-server:nightly-20231225-ab067bf0
+```
+
+HoraeMeta 启动好了,没有问题之后,就可以把 HoraeDB 的容器创建出来:
+TODO: 补充完整
diff --git a/docs/src/design/architecture.md b/docs/src/cn/design/architecture.md
similarity index 51%
copy from docs/src/design/architecture.md
copy to docs/src/cn/design/architecture.md
index eb5cd7e..0c6dd1f 100644
--- a/docs/src/design/architecture.md
+++ b/docs/src/cn/design/architecture.md
@@ -1,19 +1,19 @@
-# Introduction to HoraeDB's Architecture
+# HoraeDB 架构介绍
-## Target
+## 本文目标
-- Provide the overview of HoraeDB to the developers who want to know more about HoraeDB but have no idea where to start.
-- Make a brief introduction to the important modules of HoraeDB and the connections between these modules but details about their implementations are not be involved.
+- 为想了解更多关于 HoraeDB 但不知道从何入手的开发者提供 HoraeDB 的概览
+- 简要介绍 HoraeDB 的主要模块以及这些模块之间的联系,但不涉及它们实现的细节
-## Motivation
+## 动机
-HoraeDB is a timeseries database (**TSDB**). However, HoraeDB's goal is to handle both timeseries and analytic workloads compared with the classic TSDB, which usually have a poor performance in handling analytic workloads.
+HoraeDB 是一个时序数据库,与经典时序数据库相比,HoraeDB 的目标是能够同时处理时序型和分析型两种模式的数据,并提供高效的读写。
-In the classic timeseries database, the `Tag` columns (InfluxDB calls them `Tag` and Prometheus calls them `Label`) are normally indexed by generating an inverted index. However, it is found that the cardinality of `Tag` varies in different scenarios. And in some scenarios the cardinality of `Tag` is very high (we name this case after analytic workload), and it takes a very high cost to store and retrieve the inverted index. On the other hand, it is observed that scanning+pruning often u [...]
+在经典的时序数据库中,`Tag` 列( `InfluxDB` 称之为 `Tag`,`Prometheus` 称之为 `Label`)通常会对其生成倒排索引,但在实际使用中,`Tag` 的基数在不同的场景中是不一样的 ———— 在某些场景下,`Tag` 的基数非常高(这种场景下的数据,我们称之为分析型数据),而基于倒排索引的读写要为此付出很高的代价。而另一方面,分析型数据库常用的扫描 + 剪枝方法,可以比较高效地处理这样的分析型数据。
-The basic design idea of HoraeDB is to adopt a hybrid storage format and the corresponding query method for a better performance in processing both timeseries and analytic workloads.
+因此 HoraeDB 的基本设计理念是采用混合存储格式和相应的查询方法,从而达到能够同时高效处理时序型数据和分析型数据。
-## Architecture
+## 架构
```plaintext
┌──────────────────────────────────────────┐
@@ -54,143 +54,144 @@ The basic design idea of HoraeDB is to adopt a hybrid storage format and the cor
└──────────────────────────────────────────┘
```
-The figure above shows the architecture of HoraeDB stand-alone service and the details of some important modules will be described in the following part.
+上图展示了 HoraeDB 单机版本的架构,下面将会介绍重要模块的细节。
-### RPC Layer
+### RPC 层
-module path: https://github.com/apache/incubator-horaedb/tree/main/server
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/server
-The current RPC supports multiple protocols including HTTP, gRPC, MySQL.
+当前的 RPC 支持多种协议,包括 HTTP、gRPC、MySQL。
-Basically, HTTP and MySQL are used to debug HoraeDB, query manually and perform DDL operations (such as creating, deleting tables, etc.). And gRPC protocol can be regarded as a customized protocol for high-performance, which is suitable for massive reading and writing operations.
+通常 HTTP 和 MySQL 用于调试 HoraeDB,手动查询和执行 DDL 操作(如创建、删除表等)。而 gRPC 协议可以被看作是一种用于高性能的定制协议,更适用于大量的读写操作。
-### SQL Layer
+### SQL 层
-module path: https://github.com/apache/incubator-horaedb/tree/main/query_frontend
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/query_frontend
-SQL layer takes responsibilities for parsing sql and generating the query plan.
+SQL 层负责解析 SQL 并生成查询计划。
-Based on [sqlparser](https://github.com/sqlparser-rs/sqlparser-rs) a sql dialect, which introduces some key concepts including `Tag` and `Timestamp`, is provided for processing timeseries data. And by utilizing [DataFusion](https://github.com/apache/arrow-datafusion) the planner is able to generate both regular logical plans and tailored ones which is used to implement the special operators defined by timeseries queries, e.g `PromQL`.
+HoraeDB 基于 [sqlparser](https://github.com/sqlparser-rs/sqlparser-rs) 提供了一种 SQL 方言,为了更好的适配时序数据,引入一些概念,包括 Tag 和 Timestamp。此外,利用 [DataFusion](https://github.com/apache/arrow-datafusion),HoraeDB 不仅可以生成常规的逻辑计划,还可以生成自定义的计划来实现时序场景要求的特殊算子,例如为了适配 PromQL 协议而做的工作就是利用了这个特性。
### Interpreter
-module path: https://github.com/apache/incubator-horaedb/tree/main/interpreters
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/interpreters
-The `Interpreter` module encapsulates the SQL `CRUD` operations. In the query procedure, a sql received by HoraeDB is parsed, converted into the query plan and then executed in some specific interpreter, such as `SelectInterpreter`, `InsertInterpreter` and etc.
+`Interpreter` 模块封装了 SQL 的 `CRUD` 操作。在查询流程中,一个 SQL 语句会经过解析,生成出对应的查询计划,然后便会在特定的解释器中执行,例如 `SelectInterpreter`、`InsertInterpreter` 等。
### Catalog
-module path: https://github.com/apache/incubator-horaedb/tree/main/catalog_impls
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/catalog_impls
-`Catalog` is actually the module managing metadata and the levels of metadata adopted by HoraeDB is similar to PostgreSQL: `Catalog > Schema > Table`, but they are only used as namespace.
+`Catalog` 实际上是管理元数据的模块,HoraeDB 采用的元数据分级与 PostgreSQL 类似:`Catalog > Schema > Table`,但目前它们只用作命名空间。
-At present, `Catalog` and `Schema` have two different kinds of implementation for standalone and distributed mode because some strategies to generate ids and ways to persist metadata differ in different mode.
+目前,`Catalog` 和 `Schema` 在单机模式和分布式模式存在两种不同实现,因为一些生成 id 和持久化元数据的策略在这两种模式下有所不同。
-### Query Engine
+### 查询引擎
-module path: https://github.com/apache/incubator-horaedb/tree/main/query_engine
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/query_engine
-`Query Engine` is responsible for optimizing and executing query plan given a basic SQL plan provided by SQL layer and now such work is mainly delegated to [DataFusion](https://github.com/apache/arrow-datafusion).
+查询引擎负责优化和执行由 SQL 层解析出来的 SQL 计划,目前查询引擎实际上基于 [DataFusion](https://github.com/apache/arrow-datafusion) 来实现的。
-In addition to the basic functions of SQL, HoraeDB also defines some customized query protocols and optimization rules for some specific query plans by utilizing the extensibility provided by [DataFusion](https://github.com/apache/arrow-datafusion). For example, the implementation of `PromQL` is implemented in this way and read it if you are interested.
+除了 SQL 的基本功能外,HoraeDB 还通过利用 [DataFusion](https://github.com/apache/arrow-datafusion) 提供的扩展接口,为某些特定的查询(比如 `PromQL`)构建了一些定制的查询协议和优化规则。
### Pluggable Table Engine
-module path: https://github.com/apache/incubator-horaedb/tree/main/table_engine
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/table_engine
-`Table Engine` is actually a storage engine for managing tables in HoraeDB and the pluggability of `Table Engine` is a core design of HoraeDB which matters in achieving our long-term target, e.g supporting handle log or tracing workload by implementing new storage engines. HoraeDB will have multiple kinds of `Table Engine` for different workloads and the most appropriate one should be chosen as the storage engine according to the workload pattern.
+`Table Engine` 是 HoraeDB 中用于管理表的存储引擎,其可插拔性是 HoraeDB 的一个核心设计,对于实现我们的一些长远目标(比如增加 Log 或 Tracing 类型数据的存储引擎)至关重要。HoraeDB 将会有多种 `Table Engine` 用于不同的工作负载,根据工作负载模式,应该选择最合适的存储引擎。
-Now the requirements for a `Table Engine` are:
+现在对 Table Engine 的要求是:
-- Manage all the shared resources under the engine:
- - Memory
- - Storage
+- 管理引擎下的所有共享资源:
+ - 内存
+ - 存储
- CPU
-- Manage metadata of tables such as table schema and table options;
-- Provide `Table` instances which provides `read` and `write` methods;
-- Take responsibilities for creating, opening, dropping and closing `Table` instance;
+- 管理表的元数据,如表的结构、表的参数选项;
+- 能够提供 `Table` 实例,该实例可以提供 `read` 和 `write` 的能力;
+- 负责 `Table` 实例的创建、打开、删除和关闭;
- ....
-Actually the things that a `Table Engine` needs to process are a little complicated. And now in HoraeDB only one `Table Engine` called `Analytic` is provided and does a good job in processing analytical workload, but it is not ready yet to handle the timeseries workload (we plan to enhance it for a better performance by adding some indexes which help handle timeseries workload).
+实际上,`Table Engine` 需要处理的事情有点复杂。现在在 HoraeDB 中,只提供了一个名为 `Analytic` 的 `Table Engine`,它在处理分析工作负载方面做得很好,但是在时序工作负载上还有很大的进步空间(我们计划通过添加一些帮助处理时序工作负载的索引来提高性能)。
-The following part gives a description about details of `Analytic Table Engine`.
+以下部分描述了 `Analytic Table Engine` 的详细信息。
#### WAL
-module path: https://github.com/apache/incubator-horaedb/tree/main/wal
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/wal
-The model of HoraeDB processing data is `WAL` + `MemTable` that the recent written data is written to `WAL` first and then to `MemTable` and after a certain amount of data in `MemTable` is accumulated, the data will be organized in a query-friendly form to persistent devices.
+HoraeDB 处理数据的模型是 `WAL` + `MemTable`,最近写入的数据首先被写入 `WAL`,然后写入 `MemTable`,在 `MemTable` 中累积了一定数量的数据后,该数据将以便于查询的形式被重新构建,并存储到持久化设备上。
-Now three implementations of `WAL` are provided for standalone and distributed mode:
+目前,为 `standalone` 模式和分布式模式提供了三种 `WAL` 实现:
-- For standalone mode, `WAL` is based on `RocksDB` and data is persisted on the local disk.
-- For distributed mode, `WAL` is required as a distributed component and to be responsible for durability of the newly written data, so now we provide an implementation based on [OceanBase](https://github.com/oceanbase/oceanbase).
-- For distributed mode, in addition to [OceanBase](https://github.com/oceanbase/oceanbase), we also provide a more lightweight implementation based on [`Apache Kafka`](https://github.com/apache/kafka).
+- 对于 `standalone` 模式,`WAL` 基于 `RocksDB`,数据存储在本地磁盘上。
+- 对于分布式模式,需要 `WAL` 作为一个分布式组件,负责新写入数据的可靠性,因此,我们现在提供了基于 [`OceanBase`](https://github.com/oceanbase/oceanbase) 的实现。
+- 对于分布式模式,除了 [`OceanBase`](https://github.com/oceanbase/oceanbase),我们还提供了一个更轻量级的基于 [`Apache Kafka`](https://github.com/apache/kafka) 实现。
#### MemTable
-module path: https://github.com/apache/incubator-horaedb/tree/main/analytic_engine/src/memtable
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/analytic_engine/src/memtable
-For `WAL` can't provide efficient data retrieval, the newly written data is also stored in `Memtable` for efficient data retrieval, after a certain amount of data is reached, HoraeDB organizes the data in `MemTable` into a query-friendly storage format (`SST`) and stores it to the persistent device.
+由于 `WAL` 无法提供高效的查询,因此新写入的数据会存储一份到 `Memtable` 用于查询,并且在积累了一定数量后,HoraeDB 将 `MemTable` 中的数据组织成便于查询的存储格式(`SST`)并存储到持久化设备中。
-The current implementation of `MemTable` is based on [agatedb's skiplist](https://github.com/tikv/agatedb/blob/8510bff2bfde5b766c3f83cf81c00141967d48a4/skiplist). It allows concurrent reads and writes and can control memory usage based on [Arena](https://github.com/apache/incubator-horaedb/tree/main/components/skiplist).
+MemTable 的当前实现基于 [agatedb 的 skiplist](https://github.com/tikv/agatedb/blob/8510bff2bfde5b766c3f83cf81c00141967d48a4/skiplist)。它允许并发读取和写入,并且可以根据 [Arena](https://github.com/apache/incubator-horaedb/tree/main/components/skiplist) 控制内存使用。
#### Flush
-module path: https://github.com/apache/incubator-horaedb/blob/main/analytic_engine/src/instance/flush_compaction.rs
+模块路径:https://github.com/apache/incubator-horaedb/blob/main/analytic_engine/src/instance/flush_compaction.rs
-What `Flush` does is that when the memory usage of `MemTable` reaches the threshold, some `MemTables` are selected for flushing into query-friendly `SST`s saved on persistent device.
+当 `MemTable` 的内存使用量达到阈值时,`Flush` 操作会选择一些老的 `MemTable`,将其中的数据组织成便于查询的 `SST` 存储到持久化设备上。
-During the flushing procedure, the data will be divided by a certain time range (which is configured by table option `Segment Duration`), and any `SST` is ensured that the timestamps of the data in it are in the same `Segment`. Actually this is also a common operation in most timeseries databases which organizes data in the time dimension to speed up subsequent time-related operations, such as querying data over a time range and assisting purge data outside the `TTL`.
+在刷新过程中,数据将按照一定的时间段(由表选项 `Segment Duration` 配置)进行划分,保证任何一个 `SST` 的所有数据的时间戳都属于同一个 `Segment`。实际上,这也是大多数时序数据库中常见的操作,按照时间维度组织数据,以加速后续的时间相关操作,如查询一段时间内的数据,清除超出 `TTL` 的数据等。
#### Compaction
-module path: https://github.com/apache/incubator-horaedb/tree/main/analytic_engine/src/compaction
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/analytic_engine/src/compaction
-The data of `MemTable` is flushed as `SST`s, but the file size of recently flushed `SST` may be very small. And too small or too many `SST`s lead to the poor query performance. Therefore, `Compaction` is then introduced to rearrange the `SST`s so that the multiple smaller `SST` files can be compacted into a larger `SST` file.
+`MemTable` 的数据被刷新为 `SST` 文件,但最近刷新的 `SST` 文件可能非常小,而过小或过多的 `SST` 文件会导致查询性能不佳,因此,引入 `Compaction` 来重新整理 SST 文件,使多个较小的 `SST` 文件可以合并成较大的 `SST` 文件。
#### Manifest
-module path: https://github.com/apache/incubator-horaedb/tree/main/analytic_engine/src/meta
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/analytic_engine/src/meta
-`Manifest` takes responsibilities for managing tables' metadata of `Analytic Engine` including:
+`Manifest` 负责管理每个表的元数据,包括:
-- Table schema and table options;
-- The sequence number where the newest flush finishes;
-- The information of all the `SST`s belonging to the table.
+- 表的结构和表的参数选项;
+- 最新 `Flush` 过的 sequence number;
+- 表的所有 `SST` 文件的信息。
-Now the `Manifest` is based on `WAL` and `Object Storage`. The newly written updates on the `Manifest` are persisted as logs in `WAL`, and in order to avoid infinite expansion of `Manifest` (actually every `Flush` leads to an update), `Snapshot` is also introduced to clean up the history of metadata updates, and the generated `Snapshot` will be saved to `Object Storage`.
+现在 `Manifest` 是基于 `WAL` 和 `Object Store` 来实现的,新的改动会直接写入到 `WAL`,而为了避免元数据无限增长(实际上每次 `Flush` 操作都会触发更新),会对其写入的记录做快照,生成的快照会被持久化道 `Object Store`。
#### Object Storage
-module path: https://github.com/apache/incubator-horaedb/tree/main/components/object_store
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/components/object_store
-The `SST` generated by `Flush` needs to be persisted and the abstraction of the persistent storage device is `ObjectStore` including multiple implementations:
+`Flush` 操作产生的 `SST` 文件需要持久化存储,而用于抽象持久化存储设备的就是 `Object Storage`,其中包括多种实现:
-- Based on local file system;
-- Based on [Alibaba Cloud OSS](https://www.alibabacloud.com/product/object-storage-service).
+- 基于本地文件系统;
+- 基于[阿里云 OSS](https://www.alibabacloud.com/product/object-storage-service)。
-The distributed architecture of HoraeDB separates storage and computing, which requires `Object Store` needs to be a highly available and reliable service independent of HoraeDB. Therefore, storage systems like [Amazon S3](https://aws.amazon.com/s3/), [Alibaba Cloud OSS](https://www.alibabacloud.com/product/object-storage-service) is a good choice and in the future implementations on storage systems of some other cloud service providers is planned to provide.
+HoraeDB 的分布式架构的一个核心特性就是存储和计算分离,因此要求 `Object Storage` 是一个高可用的服务,并独立于 HoraeDB。因此,像[Amazon S3](https://aws.amazon.com/s3/)、[阿里云 OSS](https://www.alibabacloud.com/product/object-storage-service)等存储系统是不错的选择,未来还将计划实现在其他云服务提供商的存储系统上。
#### SST
-module path: https://github.com/apache/incubator-horaedb/tree/main/analytic_engine/src/sst
+模块路径:https://github.com/apache/incubator-horaedb/tree/main/analytic_engine/src/sst
-`SST` is actually an abstraction that can have multiple specific implementations. The current implementation is based on [Parquet](https://parquet.apache.org/), which is a column-oriented data file format designed for efficient data storage and retrieval.
+`SST` 本身实际上是一种抽象,可以有多种具体实现。目前的实现是基于 [Parquet](https://parquet.apache.org/),它是一种面向列的数据文件格式,旨在实现高效的数据存储和检索。
-The format of `SST` is very critical for retrieving data and is also the most important part to perform well in handling both timeseries and analytic workloads. At present, our [Parquet](https://parquet.apache.org/)-based implementation is good at processing analytic workload but is poor at processing timeseries workload. In our roadmap, we will explore more storage formats in order to achieve a good performance in both workloads.
+`SST` 的格式对于数据检索非常关键,也是决定查询性能的关键所在。目前,我们基于 [Parquet](https://parquet.apache.org/) 的 `SST` 实现在处理分析型数据时表现良好,但目前在处理时序型数据上还有较高的提升空间。在我们的路线图中,我们将探索更多的存储格式,以便在两种类型的数据处理上都取得良好的性能。
#### Space
-module path: https://github.com/apache/incubator-horaedb/blob/main/analytic_engine/src/space.rs
+模块路径:https://github.com/apache/incubator-horaedb/blob/main/analytic_engine/src/space.rs
-In `Analytic Engine`, there is a concept called `space` and here is an explanation for it to resolve some ambiguities when read source code. Actually `Analytic Engine` does not have the concept of `catalog` and `schema` and only provides two levels of relationship: `space` and `table`. And in the implementation, the `schema id` (which should be unique across all `catalog`s) on the upper layer is actually mapped to `space id`.
+在 `Analytic Engine` 中,有一个叫做 `space` 的概念,这里着重解释一下,以解决阅读源代码时出现的一些歧义。
+实际上,`Analytic Engine` 没有 `catalog` 和 `schema` 的概念,只提供两个层级的关系:`space` 和 `table`。在实现中,上层的 `schema id`(要求在所有的 `catalogs` 中都应该是唯一的)实际上会直接映射成 `space id`。
-The `space` in `Analytic Engine` serves mainly for isolation of resources for different tenants, such as the usage of memory.
+`Analytic Engine` 中的 `space` 主要用于隔离不同租户的资源,如内存的使用。
## Critical Path
-After a brief introduction to some important modules of HoraeDB, we will give a description for some critical paths in code, hoping to provide interested developers with a guide for reading the code.
+简要介绍了 HoraeDB 的一些重要模块后,我们将对代码中的一些关键路径进行描述,希望为有兴趣的开发人员在阅读代码时提供一些帮助。
### Query
@@ -229,28 +230,28 @@ After a brief introduction to some important modules of HoraeDB, we will give a
└─────────────────────────────────────┘
```
-Take `SELECT` SQL as an example. The figure above shows the query procedure and the numbers in it indicates the order of calling between the modules.
-
-Here are the details:
-
-- Server module chooses a proper rpc module (it may be HTTP, gRPC or mysql) to process the requests according the protocol used by the requests;
-- Parse SQL in the request by the parser;
-- With the parsed sql and the information provided by catalog/schema module, [DataFusion](https://github.com/apache/arrow-datafusion) can generate the logical plan;
-- With the logical plan, the corresponding `Interpreter` is created and logical plan will be executed by it;
-- For the logical plan of normal `Select` SQL, it will be executed through `SelectInterpreter`;
-- In the `SelectInterpreter` the specific query logic is executed by the `Query Engine`:
- - Optimize the logical plan;
- - Generate the physical plan;
- - Optimize the physical plan;
- - Execute the physical plan;
-- The execution of physical plan involves `Analytic Engine`:
- - Data is obtained by `read` method of `Table` instance provided by `Analytic Engine`;
- - The source of the table data is `SST` and `Memtable`, and the data can be filtered by the pushed down predicates;
- - After retrieving the table data, `Query Engine` will complete the specific computation and generate the final results;
-- `SelectInterpreter` gets the results and feeds them to the protocol module;
-- After the protocol layer converts the results, the server module responds to the client with them.
-
-The following is the flow of function calls in version [v1.2.2](https://github.com/apache/incubator-horaedb/releases/tag/v1.2.2):
+以 `SELECT` SQL 为例,上图展示了查询过程,其中的数字表示模块之间调用的顺序。
+
+以下是详细流程:
+
+- Server 模块根据请求使用的协议选择合适的 rpc 模块(可能是 HTTP、gRPC 或 mysql)来处理请求;
+- 使用 parser 解析请求中的 sql ;
+- 根据解析好的 sql 以及 catalog/schema 提供的元信息,通过 [DataFusion](https://github.com/apache/arrow-datafusion) 可以生成逻辑计划;
+- 根据逻辑计划创建相应的 `Interpreter`,并由其执行逻辑计划;
+- 对于正常 `SELECT` SQL 的逻辑计划,它将通过 `SelectInterpreter` 执行;
+- 在 `SelectInterpreter` 中,特定的查询逻辑由 `Query Engine` 执行:
+ - 优化逻辑计划;
+ - 生成物理计划;
+ - 优化物理计划;
+ - 执行物理计划;
+- 执行物理计划涉及到 `Analytic Engine`:
+ - 通过 `Analytic Engine` 提供的 `Table` 实例的 `read` 方法获取数据;
+ - 表数据的来源是 `SST` 和 `Memtable`,可以通过谓词下推进行提前过滤;
+ - 在检索到表数据后,`Query Engine` 将完成具体计算并生成最终结果;
+- `SelectInterpreter` 获取结果并将其传输给 Protocol 模块;
+- 协议模块完成转换结果后,Server 模块将其响应给客户端。
+
+以下是[v1.2.2](https://github.com/apache/incubator-horaedb/releases/tag/v1.2.2)的函数调用流程:
```
┌───────────────────────◀─────────────┐ ┌───────────────────────┐
@@ -307,42 +308,42 @@ The following is the flow of function calls in version [v1.2.2](https://github.c
```
-1. The received request will be forwarded to `handle_sql` after various protocol conversions, and since the request may not be processed by this node, it may need to be forwarded to `maybe_forward_sql_query` to handle the forwarding logic.
-2. After constructing the `ForwardRequest` in `maybe_forward_sql_query`, call `forward`
-3. After constructing the `RouteRequest` in `forward`, call `route`
-4. Use `route` to get the destination node `endpoint` and return to `forward`.
-5. Call `forward_with_endpoint` to forward the request
-6. return `forward`
-7. return `maybe_forward_sql_query`
-8. return `handle_sql`
-9. If this is a `Local` request, call `fetch_sql_query_output` to process it
-10. Call `parse_sql` to parse `sql` into `Statment`
-11. return `fetch_sql_query_output`
-12. Call `statement_to_plan` with `Statment`
-13. Construct `Planner` with `ctx` and `Statment`, and call the `statement_to_plan` method of `Planner`
-14. The `planner` will call the corresponding `planner` method for the requested category, at this point our `sql` is a query and will call `sql_statement_to_plan`
-15. Call `sql_statement_to_datafusion_plan` , which will generate the `datafusion` object, and then call `SqlToRel::sql_statement_to_plan`
-16. The generated logical plan is returned from `SqlToRel::sql_statement_to_plan`
-17. return
-18. return
-19. return
-20. Call `execute_plan_involving_partition_table` (in the default configuration) for subsequent optimization and execution of this logical plan
-21. Call `build_interpreter` to generate `Interpreter`
-22. return
-23. Call `Interpreter's` `interpreter_execute_plan` method for logical plan execution.
-24. The corresponding `execute` function is called, at this time the `sql` is a query, so the execute of the `SelectInterpreter` will be called
-25. call `execute_logical_plan` , which will call `build_df_session_ctx` to generate the optimizer
-26. `build_df_session_ctx` will use the `config` information to generate the corresponding context, first using datafusion and some custom optimization rules (in logical_optimize_rules()) to generate the logical plan optimizer, using `apply_adapters_for_physical_optimize_rules` to generate the physical plan optimizer
-27. return optimizer
-28. Call `optimize_plan`, using the optimizer just generated to first optimize the logical plan and then the physical plan
-29. Return to optimized physical plan
-30. execute physical plan
-31. returned after execution
-32. After collecting the results of all slices, return
-33. return
-34. return
-35. return
-36. Return to the upper layer for network protocol conversion and finally return to the request sender
+1. 收到请求经过各种协议转换会转到`handle_sql`中执行,由于该请求可能是非本节点处理的,可能需要转发,进入`maybe_forward_sql_query`处理转发逻辑。
+2. 在`maybe_forward_sql_query`中构造好`ForwardRequest`后,调用`forward`
+3. 在`forward`中构造好`RouteRequest`,后调用`route`
+4. 使用`route`获取目的节点`endpoint`后回到`forward`
+5. 调用`forward_with_endpoint`将请求进行转发
+6. 回到`forward`
+7. 回到`maybe_forward_sql_query`
+8. 回到`handle_sql`
+9. 此时若是`Local`请求,调用`fetch_sql_query_output`进行处理
+10. 调用`parse_sql`将`sql`解析成`Statment`
+11. 回到`fetch_sql_query_output`
+12. 使用`Statment`调用`statement_to_plan`
+13. 在其中使用`ctx`和`Statment`构造`Planner`,调用`Planner`的`statement_to_plan`方法
+14. `planner`中会对于请求的类别调用对应的`planner`方法,此时我们的`sql`是查询,会调用`sql_statement_to_plan`
+15. 调用`sql_statement_to_datafusion_plan`,其中会生成`datafusion`的对象,然后调用`SqlToRel::sql_statement_to_plan`
+16. `SqlToRel::sql_statement_to_plan`中会返回生成的逻辑计划
+17. 返回
+18. 返回
+19. 返回
+20. 调用`execute_plan_involving_partition_table`(使用默认配置情况下)进行该逻辑计划的后续优化和执行
+21. 调用`build_interpreter`生成`Interpreter`
+22. 返回
+23. 调用`Interpreter`的`interpreter_execute_plan`方法进行逻辑计划的执行。
+24. 调用对应执行函数,此时`sql`是查询,所以会调用`SelectInterpreter`的`execute`
+25. 调用`execute_logical_plan`,其中会调用`build_df_session_ctx`生成优化器
+26. `build_df_session_ctx`中会使用`config`信息生成对应上下文,首先使用`datafusion`和自定义的一些优化规则(在`logical_optimize_rules()`中)生成逻辑计划优化器, 使用 `apply_adapters_for_physical_optimize_rules`生成物理计划优化器
+27. 将优化器返回
+28. 调用`optimize_plan`,使用刚刚生成的优化器首先进行逻辑计划的优化随后进行物理计划的优化
+29. 返回优化后的物理计划
+30. 执行物理计划
+31. 执行后返回
+32. 收集所有分片的结果后,返回
+33. 返回
+34. 返回
+35. 返回
+36. 返回给上层进行网络协议转化,最后返回给请求发送方
### Write
@@ -381,20 +382,21 @@ The following is the flow of function calls in version [v1.2.2](https://github.c
└─────────────────────────────────────┘
```
-Take `INSERT` SQL as an example. The figure above shows the query procedure and the numbers in it indicates the order of calling between the modules.
-
-Here are the details:
-
-- Server module chooses a proper rpc module (it may be HTTP, gRPC or mysql) to process the requests according the protocol used by the requests;
-- Parse SQL in the request by the parser;
-- With the parsed sql and the catalog/schema module, [DataFusion](https://github.com/apache/arrow-datafusion) can generate the logical plan;
-- With the logical plan, the corresponding `Interpreter` is created and logical plan will be executed by it;
-- For the logical plan of normal `INSERT` SQL, it will be executed through `InsertInterpreter`;
-- In the `InsertInterpreter`, `write` method of `Table` provided `Analytic Engine` is called:
- - Write the data into `WAL` first;
- - Write the data into `MemTable` then;
-- Before writing to `MemTable`, the memory usage will be checked. If the memory usage is too high, the flush process will be triggered:
- - Persist some old MemTables as `SST`s;
- - Store updates about the new `SST`s and the flushed sequence number of `WAL` to `Manifest`;
- - Delete the corresponding `WAL` entries;
-- Server module responds to the client with the execution result.
+以 `INSERT` SQL 为例,上图展示了查询过程,其中的数字表示模块之间调用的顺序。
+
+以下是详细流程:
+
+- Server 模块根据请求使用的协议选择合适的 rpc 模块(可能是 HTTP、gRPC 或 mysql)来处理请求;
+- 使用 parser 解析请求中的 sql;
+- 根据解析好的 sql 以及 catalog/schema 提供的元信息,通过 [DataFusion](https://github.com/apache/arrow-datafusion) 可以生成逻辑计划;
+- 根据逻辑计划创建相应的 `Interpreter` ,并由其执行逻辑计划;
+- 对于正常 `INSERT` SQL 的逻辑计划,它将通过 `InsertInterpreter` 执行;
+- 在 `InsertInterpreter` 中,调用 `Analytic Engine` 提供的 `Table` 的 `write` 方法:
+ - 首先将数据写入 `WAL`;
+ - 然后写入 `MemTable`;
+- 在写入 `MemTable` 之前,会检查内存使用情况。如果内存使用量过高,则会触发 `Flush`:
+ - 将一些旧的 `MemTable` 持久化为 `SST`;
+ - 将新的 SST 信息记录到 `Manifest`;
+ - 记录最新 Flush 的 `WAL` 序列号;
+ - 删除相应的 `WAL` 日志;
+- Server 模块将执行结果响应给客户端。
diff --git a/docs/src/cn/design/clustering.md b/docs/src/cn/design/clustering.md
new file mode 100644
index 0000000..e93d00b
--- /dev/null
+++ b/docs/src/cn/design/clustering.md
@@ -0,0 +1,182 @@
+注意:文章中提到的部分特性暂时还未实现。
+
+# HoraeDB 集群
+
+## 整体架构
+
+```plaintext
+┌───────────────────────────────────────────────────────────────────────┐
+│ │
+│ HoraeMeta Cluster │
+│ │
+└───────────────────────────────────────────────────────────────────────┘
+ ▲ ▲ ▲
+ │ │ │
+ │ │ │
+ ▼ ▼ ▼
+┌───────┐Route Info ┌HoraeDB─────┬┬─┐ ┌HoraeDB─────┬┬─┐ ┌HoraeDB─────┬┬─┐
+│client │◀────────▶ │ │ │TableN││ │ │ │ │TableN││ │ │ │ │TableN││ │
+└───────┘Write/Query└──Shard(L)──┴┴─┘ └──Shard(F)──┴┴─┘ └──Shard(F)──┴┴─┘
+ │ │ ▲ ▲
+ │ │ │
+ │ Write─────────┐ ├────Sync───────┘
+ │ │
+ │ ┌────────┬▼───┴────┬──────────────────┐
+ Upload SST │ │ │ │
+ │ │WAL │Region N │ │
+ │Service │ │ │
+ │ └────────┴─────────┴──────────────────┘
+
+ ▼
+┌───────────────────────────────────────────────────────────────────────┐
+│ │
+│ Object Storage │
+│ │
+└───────────────────────────────────────────────────────────────────────┘
+```
+
+上面给出来 HoraeDB 集群化方案的整体架构图,对于其中的一些名词做出解释:
+
+- `HoraeMeta Cluster`:集群的元数据中心,负责集群的整体调度;
+- `Shard(L)/Shard(F)`: Leader Shard 和 Follower Shard,每一个 Shard 由多张 Table 组成;
+- `HoraeDB`:一个 HoraeDB 实例, 包含多个 Shard;
+- `WAL Service`:WAL 服务,在集群方案中,用于存储实时写入的数据;
+- `Object Storage`:对象存储服务,用于存储从 memtable 生成的 SST 文件;
+
+根据架构图,读者应该对于集群化方案有一个初步的认知 —— 存储计算分离。正因如此,HoraeDB 实例本身不存储任何数据,从而使得计算存储弹性扩缩容、服务高可用和负载均衡等等有用的分布式特性比较容易实现。
+
+### Shard
+
+Shard 是一个重要的概念,是集群调度的基本单元,一个 Shard 包含多张表。
+
+这里值得说明的是,集群调度的基本单元并非是 Table,而是采用了 Shard,主要原因在于考虑到 HoraeDB 集群如果需要支持至少百万级别的 Table 的话,而部分组件,直接处理起这个量级的 Table 可能会存在一些性能问题,例如对于 WAL 服务,如果按照 Table 去单独管理数据的话,一个重要的性能问题就在于重启后恢复新写入数据的时候,按照 Table 级别去做数据恢复的话,将会非常耗时,但按照 Shard 去管理数据的话,这个情况会改善很多,因为具体的 WAL 服务实现可以按照 Shard 这个信息,将 Table 的数据进行合理组织,从而获得比较好的数据局部性,在重启恢复的时候,就可以通过 Shard 来进行快速的数据恢复。
+
+此外 Shard 具有 Leader 和 Follower 两种 Role,也就是图中的 `Shard(L)` 和 `Shard(F)`,Leader 负责读写,Follower 只有读权限,Follower 的引入其实是为了解决 HA 的问题,保证 Leader 在 crash 了之后,能快速的恢复相关表的读写服务,为了做到这一点,Follower 需要从 WAL 不停地同步最新的数据(由 Leader 写入)。
+
+一个 HoraeDB 实例会拥有多个 Shard,这些 Shard 可以是 Leader 也是 Follower,以交织的方式存在:
+
+```plaintext
+┌─HoraeDB Instance0──────┐ ┌─HoraeDB Instance1──────┐
+│ ┌─Shard0(L)────────┐ │ │ ┌─Shard0(F)────────┐ │
+│ │ ┌────┬────┬────┐ │ │ │ │ ┌────┬────┬────┐ │ │
+│ │ │ T0 │ T1 │ T2 │ │ │ │ │ │ T0 │ T1 │ T2 │ │ │
+│ │ └────┴────┴────┘ │ │ │ │ └────┴────┴────┘ │ │
+│ └──────────────────┘ │ │ └──────────────────┘ │
+│ │ │ │
+│ ┌─Shard1(F)────────┐ │ │ ┌─Shard1(L)────────┐ │
+│ │ ┌────┬────┬────┐ │ │ │ │ ┌────┬────┬────┐ │ │
+│ │ │ T0 │ T1 │ T2 │ │ │ │ │ │ T0 │ T1 │ T2 │ │ │
+│ │ └────┴────┴────┘ │ │ │ │ └────┴────┴────┘ │ │
+│ └──────────────────┘ │ │ └──────────────────┘ │
+└────────────────────────┘ └────────────────────────┘
+```
+
+上面提到集群的基本调度单元是 Shard,因此对于 Shard 存在这一些基本操作,通过这些操作可以完成一些负责的调度逻辑:
+
+- 向 Shard 新增/删除 Table:在建表/删表的时候使用;
+- Shard 的打开/关闭:可以用来将一个 Shard 迁移到另外一个 HoraeDB 实例上面;
+- Shard 分裂/合并:可以用来完成扩容和缩容;
+- Shard 角色切换:将一个 Shard 从 Leader 切换成 Follower,或者从 Follower 切换成 Leader;
+
+### HoraeMeta
+
+HoraeMeta 主要负责集群的元数据管理和集群调度,其实现是通过内置一个 ETCD 来保证分布式一致性的。
+
+元数据主要都是围绕 Table 来构建的,包括一些建表信息(如 Table ID),Table 所属集群(HoraeMeta 是可以支持多个 HoraeDB 集群的),Table 与 Shard 的映射关系等等。
+
+HoraeMeta 的主要工作还是负责集群的调度,需要完成:
+
+- 接收来自 HoraeDB 实例的心跳,根据心跳来检测 HoraeDB 实例的存活状态;
+- 负责 Shard 的具体分配以及调度,会将 Shard 按照一定的算法(负载均衡)分配到具体的实例上面去;
+- 参与 Table 的创建,将 Table 分配到合适的 Shard(也就会分配到具体的实例);
+- 如果有新的 HoraeDB 实例加入到集群中,可以进行扩容操作;
+- 如果有检测到 HoraeDB 实例的下线状况,可以完成自动的故障恢复;
+
+### 路由
+
+为了避免转发请求的开销,客户端与 HoraeDB 实例之间的通信是点对点的,也就是说,客户端在发送任何特定的写入/查询请求之前,应该从服务器获取路由信息。
+
+实际上,路由信息是由 HoraeMeta 决定的,但是客户端只允许通过 HoraeDB 实例而不是 HoraeMeta 来访问它,以避免由 HoraeMeta 引起的潜在性能瓶颈。
+
+### WAL Service & Object Storage
+
+在集群方案中,`WAL Service` 和 `Object Storage` 都是作为独立的服务而存在的,并且是具备容灾能力的分布式系统。目前 `WAL Service` 的实现,在集群化方案中主要包括两种实现:`Kafka` 和 `OBKV`(通过其提供的 Table API 访问 `OceanBase`);而 `Object Storage` 的实现比较多,基本覆盖了主流的对象存储服务。
+
+这两个系统的相似之处在于,它们是作为计算存储分离架构中的的存储层;而它们的区别也很明显,`WAL Service` 具备较高的实时写入性能(高吞吐、低延时),负责实时的新增数据写入,而 `Object Storage` 具备低成本的存储代价和较为高效的查询吞吐,负责后台整理好的、长期存储的数据读写。
+
+这两个服务组件的引入,使得 HoraeDB 集群的水平扩容、服务高可用、负载均衡提供了实现基础。
+
+## 水平扩容
+
+集群的扩展能力是评价一个集群方案的重要指标,下面从存储和计算两方面来介绍一下 HoraeDB 集群的水平扩容能力是如何实现的。
+
+### 存储
+
+很明显,在选择 WAL Service 和 Object Storage 两个底层服务实现的时候,水平扩容能力是必须的,存储容量出现问题的时候,可以单独地进行存储服务的扩容。
+
+### 计算
+
+计算能力的水平扩容可能会复杂一下,考虑如下几个容量问题:
+
+- 大量查询大量的表;
+- 大量查询同一张数据量大的表;
+- 大量查询同一张正常的表;
+
+对于第一种情况,通过 Shard 分裂,将分裂出来的新 Shard 迁移到新扩容的 HoraeDB 实例,这样表就会分散到新的实例上。
+
+对于第二种情况,可以通过表分区的功能,将该表分成多个子表,从而达到水平扩容的效果;
+
+第三种情况是最重要也是最难处理的。事实上,Follower Shard 可以为 Leader Shard 分担部分查询请求,但是由于 Follower Shard 需要同步数据,它的数量是受到 WAL 同步能力的限制的。实际上,从下图可以看到,对于这样的场景(当 Follower Shard 不足以承担住访问量的时候),可以考虑在集群中添加一种纯计算节点,其中的 Shard 不参与实时的数据同步,当这个实例被访问的时候,新写入的实时数据可以从 Leader Shard 或者 Follower Shard 恢复出来,历史的数据可以直接从 `Object Storage` 获取并缓存,这样的话,就可以在一定程度上(实际上还是存在单表的实时数据拉取的瓶颈,但是考虑到这部分数据并非特别多,理论上这个瓶颈应该比较难以达到),达到水平扩容的效果。
+
+```plaintext
+ ┌HoraeDB─────┬┬─┐
+ ┌──newly written─│ │ │TableN││ │
+ ▼ └──Shard(L/F)┴┴─┘
+┌───────┐ Query ┌HoraeDB─────┬┬─┐
+│client │────────▶│ │ │TableN││ │
+└───────┘ └──Shard─────┴┴─┘ ┌───────────────┐
+ ▲ │ Object │
+ └───old SST──────│ Storage │
+ └───────────────┘
+```
+
+## High Availability
+
+通过上面的介绍,HoraeDB 的高可用方案,其实比较自然了。考虑某台 HoraeDB 实例 Crash 了,后续的服务恢复步骤整体如下:
+
+- HoraeMeta 通过发现心跳断了,判断该 HoraeDB 实例下线;
+- HoraeMeta 选择将该实例上的所有 Leader Shard 所对应的 Follower Shard,切换成 Leader Shard,从而完成快速恢复;
+- 如果没有对应的 Follower Shard 存在,那么需要执行 Open Shard 操作,不过这样的恢复是较慢的;
+
+```plaintext
+┌─────────────────────────────────────────────────────────┐
+│ │
+│ HoraeMeta Cluster │
+│ │
+└─────────────────────────────────────────────────────────┘
+ ▲
+ ┌ ─ ─Broken ─ ─ ┤
+ │
+ │ │
+┌ HoraeDB Instance0 ─ ─ ─ │ ┌─HoraeDB Instance1──────┐ ┌─HoraeDB Instance1──────┐
+ ┌─Shard0(L)────────┐ │ │ │ ┌─Shard0(F)────────┐ │ │ ┌─Shard0(L)────────┐ │
+│ │ ┌────┬────┬────┐ │ │ │ │ ┌────┬────┬────┐ │ │ │ │ ┌────┬────┬────┐ │ │
+ │ │ T0 │ T1 │ T2 │ │ │ ├───│ │ │ T0 │ T1 │ T2 │ │ │ │ │ │ T0 │ T1 │ T2 │ │ │
+│ │ └────┴────┴────┘ │ │ │ │ └────┴────┴────┘ │ │ │ │ └────┴────┴────┘ │ │
+ └──────────────────┘ │ │ │ └──────────────────┘ │ Failover │ └──────────────────┘ │
+│ │ ├─HoraeDB Instance2──────┤ ───────────▶ ├─HoraeDB Instance2──────┤
+ ┌─Shard1(L)────────┐ │ │ │ ┌─Shard1(F)────────┐ │ │ ┌─Shard1(L)────────┐ │
+│ │ ┌────┬────┬────┐ │ │ │ │ ┌────┬────┬────┐ │ │ │ │ ┌────┬────┬────┐ │ │
+ │ │ T0 │ T1 │ T2 │ │ │ └───│ │ │ T0 │ T1 │ T2 │ │ │ │ │ │ T0 │ T1 │ T2 │ │ │
+│ │ └────┴────┴────┘ │ │ │ └────┴────┴────┘ │ │ │ │ └────┴────┴────┘ │ │
+ └──────────────────┘ │ │ └──────────────────┘ │ │ └──────────────────┘ │
+└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ └────────────────────────┘ └────────────────────────┘
+```
+
+## Load Balancing
+
+HoraeDB 上传的心跳信息会带上机器的负载信息,HoraeMeta 会根据这些信息,完成自动的负载均衡的调度工作,主要包括:
+
+- 新建表的时候,为该表挑选一个负载低的实例上面的 Shard;
+- 将负载高的实例上的 Shard 迁移到负载低的实例;
+- 更好地,可以根据 Shard 的负载,通过 Shard 的分裂和合并,来完成更细粒度的负载均衡;
diff --git a/docs/src/cn/design/shared_nothing.md b/docs/src/cn/design/shared_nothing.md
new file mode 100644
index 0000000..cbb5bb0
--- /dev/null
+++ b/docs/src/cn/design/shared_nothing.md
@@ -0,0 +1,98 @@
+# Shared Nothing 架构
+
+## 背景
+
+在 [集群](./clustering.md) 文章中介绍了 HoraeDB 的集群方案,简单总结一下就是:
+
+- 计算存储分离;
+- 由中心化的元数据中心,管理整个集群;
+
+然而计算存储分离的架构下,有个重要的问题在于,在集群调度的过程中,如何保证在共享的存储层中的数据不会因为不同的计算节点访问导致数据损坏,一个简单的例子就是如果同一块数据块被多个计算节点同时更新,可能就会出现数据损坏。
+
+而 HoraeDB 的解决方案是通过特定的机制,在共享存储的情况下达到了类似 [Shared-Nothing 架构](https://en.wikipedia.org/wiki/Shared-nothing_architecture) 的效果,也就是说存储层的数据经过一定规则的划分,**可以保证在任何时刻最多只有一个 HoraeDB 实例可以对其进行更新**,本文中,将这个特性定义成**集群拓扑的正确性**,如果这个正确性得到保证的话,那么数据就不会因为集群的灵活调度而受到损坏。
+
+本文对于 Shared Nothing 架构的优劣不做赘述,主要分享一下,HoraeDB 集群方案是如何在计算存储分离的方案下,达到 Shared Nothing 的效果(即如何保证 **集群拓扑的正确性**)。
+
+## 数据划分
+
+为了达到 Shared Nothing 的效果,首先需要将数据在共享的存储层上面进行好逻辑和物理的划分。在 [此前的集群介绍文章](./clustering.md#shard) 中介绍了 Shard 的基本作用,作为集群的基本调度单元,同时也是数据分布的基本划分单元,不同的 Shard 在存储层对应的数据是隔离的:
+
+- 在 WAL 中,写入的 Table 数据会按照 Shard 组织起来,按照 Shard 写入到 WAL 的不同区域中,不同的 Shard 在 WAL 中的数据是隔离开的;
+- 在 Object Storage 中,数据的管理是按照 Table 来划分的,而 Shard 和 Table 之间的关系是一对多的关系,也就说,任何一个 Table 只属于一个 Shard,因此在 Object Storage 中,Shard 之间的数据也是隔离的;
+
+## Shard Lock
+
+在数据划分好之后,需要保证的就是在任何时刻,同一时刻最多只有一个 HoraeDB 实例能够更新 Shard 的数据。那么要如何保证这一点的呢?很自然地,通过锁可以达到互斥的效果,不过在分布式集群中,我们需要的是分布式锁。通过分布式锁,每一个 Shard 被分配给 HoraeDB 实例时,HoraeDB 必须先获取到相应的 Shard Lock,才能完成 Shard 的打开操作,对应地,当 Shard 关闭后,HoraeDB 实例也需要主动释放 Shard Lock。
+
+HoraeDB 集群的元数据服务 HoraeMeta 是基于 ETCD 构建的,而基于 ETCD 实现分布式的 Shard Lock 是非常方便的,因此我们选择基于现有的 ETCD 实现 Shard Lock,具体逻辑如下:
+
+- 以 Shard ID 作为 ETCD 的 Key,获取到 Shard Lock 等价于创建出这个 Key;
+- 对应的 Value,可以把 HoraeDB 的地址编码进去(用于 HoraeMeta 调度);
+- Shard Lock 获取到了之后,HoraeDB 实例需要通过 ETCD 提供的接口对其进行续租,保证 Shard Lock 不会被释放;
+
+HoraeMeta 暴露了 ETCD 的服务提供给 HoraeDB 集群来构建 Shard Lock,下图展示了 Shard Lock 的工作流程,图中的两个 HoraeDB 实例都尝试打开 Shard 1,但是由于 Shard Lock 的存在,最终只有一个 HoraeDB 实例可以完成 Shard 1 的打开:
+
+```
+ ┌────────────────────┐
+ │ │
+ │ │
+ ├───────┐ │
+ ┌─────┬──▶│ ETCD │ │
+ │ │ └───────┴────HoraeMeta
+ │ │ ▲
+ │ │ └──────┬─────┐
+ │ │ Rejected │
+ │ │ │ │
+┌─────┬─────┐ ┌─────┬─────┐
+│Shard│Shard│ │Shard│Shard│
+│ 0 │ 1 │ │ 1 │ 2 │
+├─────┴─────┤ ├─────┴─────┤
+└─────HoraeDB └─────HoraeDB
+```
+
+## 其他方案
+
+Shard Lock 的方案本质上是 HoraeDB 通过 ETCD 来保证在集群中**对于任何一个 Shard 在任何时刻最多只有一个 HoraeDB 实例可以对其进行更新操作**,也就是保证了在任何时刻 **集群拓扑的正确性**,需要注意,这个保证实际上成为了 HoraeDB 实例提供的能力(虽然是利用 ETCD 来实现的),而 HoraeMeta 无需保证这一点,下面的对比中,这一点是一个非常大的优势。
+
+除此 Shard Lock 的方案,我们还考虑过这样的两种方案:
+
+### HoraeMeta 状态同步
+
+HoraeMeta 规划并存储集群的拓扑状态,保证其**正确性**,并将这个正确的拓扑状态同步到 HoraeDB,而 HoraeDB 本身无权决定 Shard 是否可以打开,只有在得到 HoraeMeta 的通知后,才能打开指定的 Shard。此外,HoraeDB 需要不停地向 HoraeMeta 发送心跳,一方面汇报自身的负载信息,另一方面让 HoraeMeta 知道该节点仍然在线,用以计算最新的正确的拓扑状态。
+
+该方案也是 HoraeDB 一开始采用的方案,该方案的思路简洁,但是在实现过程中却是很难做好的,其难点在于,HoraeMeta 在执行调度的时候,需要基于最新的拓扑状态,决策出一个新的变更,并且应用到 HoraeDB 集群,但是这个变更到达某个具体的 HoraeDB 实例时,即将产生效果的时候,该方案无法简单地保证此刻的集群状态仍然是和做出该变更决策时基于的那个集群状态是一致的。
+
+让我们用更精确的语言描述一下:
+
+```
+t0: 集群状态是 S0,HoraeMeta 据此计算出变更 U;
+t1: HoraeMeta 将 U 发送到某个 HoraeDB 实例,让其执行变更;
+t2: 集群状态变成 S1;
+t3: HoraeDB 接收到 U,准备进行变更;
+```
+
+上述的例子的问题在于,t3 时刻,HoraeDB 执行变更 U 是否正确呢?执行这个变更 U,是否会让数据遭到损坏?这个正确性,需要 HoraeMeta 完成相当复杂的逻辑来保证即使在集群状态为 S1 的情况下,执行 U 变更也不会出现问题,除此之外,状态的回滚也是一个非常麻烦的过程。
+
+举一个例子,就可以发现这个方案的处理比较麻烦:
+
+```
+t0: HoraeMeta 尝试在 HoraeDB0 打开 Shard0,然而 HoraeDB0 打开 Shard0 遇到一些问题,Hang 住了,HoraeMeta 只能在超时之后,认为打开失败;
+t1: HoraeMeta 计算出新的拓扑结构,尝试在 HoraeDB1 继续打开 Shard0;
+t2: HoraeDB0 和 HoraeDB1 可能会同时打开 Shard0;
+```
+
+自然是有一些办法来避免掉 t2 时刻的事情,比如在 t0 时刻失败了之后,需要等待一个心跳周期,来获知 HoraeDB0 是否仍然在尝试打开 Shard0,来避免 t1 时刻发出的命令,但是这样的逻辑比较繁琐,难以维护。
+
+对比 Shard Lock 的方案,可以发现,该方案尝试获得更强的一致性,即尝试保证集群的拓扑状态在任何时刻都需要和 HoraeMeta 中的集群状态保持一致,显然,这样的一致性肯定是能够保证**集群拓扑的正确性**的,但也正因为如此实现起来才会更加复杂,而基于 Shard Lock 的方案放弃了这样一个**已知**的、正确的集群拓扑状态,转而只需要保证集群状态是正确的即可,而不需要知道这个状态究竟是什么。更重要的是,从另外一个角度来看,保证**集群拓扑的正确性**这部分逻辑和集群的调度完成了解耦,因此 HoraeMeta 的逻辑大大简化,只需要专注于完成负载均衡的集群调度工作即可,而集群拓扑的正确性由 HoraeDB 本身来保证。
+
+## HoraeDB 提供一致性协议
+
+该方案是参考 TiDB 的元数据服务 [PD](https://github.com/tikv/pd) 的,PD 管理着所有的 TiKV 数据节点,但是 PD 不需要维护一致性的集群状态,并且应用到 TiKV 节点上面去,因为 TiKV 集群中,每一个 Raft Group,都能够达到一致性,也就是说 TiKV 无需借助 PD,本身就具备了让整个集群拓扑正确的能力(一个 Raft Group 不会出现两个 Leader)。
+
+参考该方案,实际上我们也可以在 HoraeDB 实例之间实现一致性协议,让其本身也具备这样的能力,不过在 HoraeDB 之间引入一致性协议,似乎把事情变得更加复杂了,而且目前也没有更多的数据需要同步,通过外部的服务(ETCD)依然可以同样的效果,从 HoraeMeta 看,就等价于 HoraeDB 本身获得了让集群一致的能力。
+
+因此 Shard Lock 的方案,可以看作是该方案的一个变种,是一种取巧但是很实用的实现。
+
+## 总结
+
+HoraeDB 分布式方案的最终目标自然不是保证集群拓扑正确就够了,但是保持正确性是后续特性的重要基石,一旦这部分的逻辑简洁明了,有充分的理论保证,就可以让后续的特性实现也同样的优雅、简洁。例如,为了使得 HoraeDB 集群中的各个节点达到负载均衡的效果,HoraeMeta 就必须根据 HoraeDB 实例上报的消息,对集群中的节点进行调度,而调度的单位必然是 Shard,然而任何一次 Shard 的变动都可能造成数据的损坏(一个 Shard 被两个实例同时打开),在有了 Shard Lock 的保证后,HoraeMeta 就可以放心地生成调度计划,根据上报的负载信息,计算出当前状态的最佳调度结果,然后发送到涉及的 HoraeDB 实例让其执行,即使计算的前提可能是错误的(即集群状态已经和计算出调度结果时的状态不一样了),也不用担心集群拓扑的正确性遭到破坏,因而 HoraeMeta
的调度逻辑,变得简洁而优雅(只需要生成、执行,不需要考虑失败处理)。
diff --git a/docs/src/cn/design/storage.md b/docs/src/cn/design/storage.md
new file mode 100644
index 0000000..d6c4cf1
--- /dev/null
+++ b/docs/src/cn/design/storage.md
@@ -0,0 +1,63 @@
+# 存储引擎
+
+存储引擎主要提供以下两个功能:
+
+1. 数据的持久化
+2. 在保证数据正确性的前提下,用最合理的方式来组织数据,来满足不同场景的查询需求
+
+本篇文档就来介绍 HoraeDB 中存储引擎的内部实现,读者可以参考这里面的内容,来探索如何高效使用 HoraeDB。
+
+# 整体架构
+
+HoraeDB 是一种基于 share-nothing 架构的分布式存储系统,不同服务器之间的数据相互隔离,互不影响。每一个单机中的存储引擎是 LSM(Log-structured merge-tree)的一个变种,针对时序场景做了优化,下图展示了其主要组件的运作方式:
+
+
+
+## Write Ahead Log (WAL)
+
+一次写入请求的数据会写到两个部分:
+
+1. 内存中的 memtable
+2. 可持久化的 WAL
+
+由于 memtable 不是实时持久化到底层存储系统,因此需要用 WAL 来保证 memtable 中数据的可靠性。
+
+另一方面,由于[分布式架构](cluster.md)的设计,要求 WAL 本身是高可用的,现在 HoraeDB 中,主要有以下几种实现:
+
+- [本地磁盘](wal_on_rocksdb.md)(基于 [RocksDB](http://rocksdb.org/),无分布式高可用)
+- [Oceanbase](https://www.oceanbase.com)
+- [Kafka](wal_on_kafka.md)
+
+## Memtable
+
+Memtable 是一个内存的数据结构,用来保存最近写入的数据。一个表对应一个 memtable。
+
+Memtable 默认是可读写的(称为 active),当写入达到一起阈值时,会变成只读的并且被一个新的 memtable 替换掉。只读的 memtable 会被后台线程以 SST 的形式写入到底层存储系统中,写入完成后,只读的 memtable 就可以被销毁,同时 WAL 中也可以删除对应部分的数据。
+
+## Sorted String Table(SST)
+
+SST 是数据的持久化格式,按照表主键的顺序存放,目前 HoraeDB 采用 parquet 格式来存储。
+
+对于 HoraeDB 来说,SST 有一个重要特性: `segment_duration`,只有同一个 segment 内的 SST 才有可能进行合并操作。而且有了 segment,也方便淘汰过期的数据。
+
+除了存放原始数据外,SST 内也会存储数据的统计信息来加速查询,比如:最大值、最小值等。
+
+## Compactor
+
+Compactor 可以把多个小 SST 文件合并成一个,用于解决小文件数过多的问题。此外,Compactor 也会在合并时进行过期数据的删除,重复数据的去重。目前 HoraeDB 中的合并策略参考自 Cassandra,主要有两个:
+
+- [SizeTieredCompactionStrategy](https://cassandra.apache.org/doc/latest/cassandra/operating/compaction/stcs.html)
+- [TimeWindowCompactionStrategy](https://cassandra.apache.org/doc/latest/cassandra/operating/compaction/twcs.html)
+
+## Manifest
+
+Manifest 记录表、SST 文件元信息,比如:一个 SST 内数据的最小、最大时间戳。
+
+由于分布式架构的设计,要求 Manifest 本身是高可用的,现在 HoraeDB 中,主要有以下几种实现:
+
+- WAL
+- ObjectStore
+
+## ObjectStore
+
+ObjectStore 是数据(即 SST)持久化的地方,一般来说各大云厂商均有对应服务,像阿里云的 OSS,AWS 的 S3。
diff --git a/docs/src/design/table_partitioning.md b/docs/src/cn/design/table_partitioning.md
similarity index 61%
copy from docs/src/design/table_partitioning.md
copy to docs/src/cn/design/table_partitioning.md
index 2f34e12..8e429c7 100644
--- a/docs/src/design/table_partitioning.md
+++ b/docs/src/cn/design/table_partitioning.md
@@ -1,22 +1,22 @@
-**Note: This feature is still in development, and the API may change in the future.**
+**注意:此功能仍在开发中,API 将来可能会发生变化。**
-# Table Partitioning
+# 分区表
-This chapter discusses `PartitionTable`.
+本章讨论 `PartitionTable`。
-The partition table syntax used by HoraeDB is similar to that of [MySQL](https://dev.mysql.com/doc/refman/8.0/en/partitioning-types.html).
+HoraeDB 使用的分区表语法类似于 [MySQL](https://dev.mysql.com/doc/refman/8.0/en/partitioning-types.html) 。
-General partition tables include `Range Partitioning`, `List Partitoning`, `Hash Partitioning`, and `Key Partititioning`.
+一般的分区表包括`Range Partitioning`、`List Partitoning`、`Hash Partitioning`和`Key Partititioning`。
-HoraeDB currently only supports `Key Partitioning`.
+HoraeDB 目前仅支持 Key Partitioning。
-## Architecture
+## 设计
-Similar to MySQL, different portions of a partition table are stored as separate tables in different locations.
+与 MySQL 类似,分区表的不同部分作为单独的表存储在不同的位置。
-Currently designed, a partition table can be opened on multiple HoraeDB nodes, supports writing and querying at the same time, and can be expanded horizontally.
+目前设计,一个分区表可以在多个 HoraeDB 节点上打开,支持同时写入和查询,可以水平扩展。
-As shown in the figure below, `PartitionTable` is opened on node0 and node1, and the physical subtables where the actual data are stored on node2 and node3.
+如下图所示,在 node0 和 node1 上打开了`PartitionTable`,在 node2 和 node3 上打开了存放实际数据的物理子表。
```
┌───────────────────────┐ ┌───────────────────────┐
@@ -42,16 +42,16 @@ As shown in the figure below, `PartitionTable` is opened on node0 and node1, and
└──────────────────────────────────────────────────┘ └──────────────────────────────────────────────────┘
```
-### Key Partitioning
+### Key 分区
-`Key Partitioning` supports one or more column calculations, using the hash algorithm provided by HoraeDB for calculations.
+`Key Partitioning`支持一列或多列计算,使用 HoraeDB 内置的 hash 算法进行计算。
-Use restrictions:
+使用限制:
-- Only tag column is supported as partition key.
-- `LINEAR KEY` is not supported yet.
+- 仅支持 `tag` 列作为分区键。
+- 暂时不支持 `LINEAR KEY`。
-The table creation statement for the key partitioning is as follows:
+key 分区的建表语句如下:
```sql
CREATE TABLE `demo`(
@@ -63,19 +63,19 @@ CREATE TABLE `demo`(
) PARTITION BY KEY(name) PARTITIONS 2 ENGINE = Analytic
```
-Refer to [MySQL key partitioning](https://dev.mysql.com/doc/refman/5.7/en/partitioning-key.html).
+参考 [MySQL key partitioning](https://dev.mysql.com/doc/refman/5.7/en/partitioning-key.html)。
-## Query
+## 查询
-Since the partition table data is actually stored in different physical tables, it is necessary to calculate the actual requested physical table according to the query request when querying.
+由于分区表数据实际上是存放在不同的物理表中,所以查询时需要根据查询请求计算出实际请求的物理表。
-The query will calculate the physical table to be queried according to the query parameters, and then remotely request the node where the physical table is located to obtain data through the HoraeDB internal service [remote engine](https://github.com/apache/incubator-horaedb/blob/89dca646c627de3cee2133e8f3df96d89854c1a3/server/src/grpc/remote_engine_service/mod.rs) (support predicate pushdown).
+首先查询会根据查询语句计算出要查询的物理表, 然后通过 HoraeDB 内部服务 [remote engine](https://github.com/apache/incubator-horaedb/blob/89dca646c627de3cee2133e8f3df96d89854c1a3/server/src/grpc/remote_engine_service/mod.rs) 远程请求物理表所在节点获取数据(支持谓词下推)。
-The implementation of the partition table is in [PartitionTableImpl](https://github.com/apache/incubator-horaedb/blob/89dca646c627de3cee2133e8f3df96d89854c1a3/analytic_engine/src/table/partition.rs).
+分区表的实现在 [PartitionTableImpl](https://github.com/apache/incubator-horaedb/blob/89dca646c627de3cee2133e8f3df96d89854c1a3/analytic_engine/src/table/partition.rs) 中。
-- Step 1: Parse query sql and calculate the physical table to be queried according to the query parameters.
-- Step 2: Query data of physical table.
-- Step 3: Compute with the raw data.
+- 第一步:解析查询 sql,根据查询参数计算出要查询的物理表。
+- 第二步:查询物理表数据。
+- 第三步:用拉取的数据进行计算。
```
│
@@ -100,15 +100,15 @@ The implementation of the partition table is in [PartitionTableImpl](https://git
└───────────────┘ └───────────────┘
```
-### Key partitioning
+### Key 分区
-- Filters like `and`, `or`, `in`, `=` will choose specific SubTables.
-- Fuzzy matching filters like `<`, `>` are also supported, but may have poor performance since it will scan all physical tables.
+- 带有 `and`, `or`, `in`, `=` 的过滤器将选择特定的子表。
+- 支持模糊匹配过滤器,如 `<`, `>`,但可能性能较差,因为它会扫描所有物理表。
-`Key partitioning` rule is implemented in [KeyRule](https://github.com/apache/incubator-horaedb/blob/89dca646c627de3cee2133e8f3df96d89854c1a3/table_engine/src/partition/rule/key.rs).
+`Key partitioning` 规则实现在 [KeyRule](https://github.com/apache/incubator-horaedb/blob/89dca646c627de3cee2133e8f3df96d89854c1a3/table_engine/src/partition/rule/key.rs)。
-## Write
+## 写入
-The write process is similar to the query process.
+写入过程与查询过程类似。
-First, according to the partition rules, the write request is split into different partitioned physical tables, and then sent to different physical nodes through the remote engine for actual data writing.
+首先根据分区规则,将写入请求拆分到不同的物理表中,然后通过 `remote engine` 服务发送到不同的物理节点进行实际的数据写入。
diff --git a/docs/src/design/wal.md b/docs/src/cn/design/wal.md
similarity index 62%
copy from docs/src/design/wal.md
copy to docs/src/cn/design/wal.md
index 3df2846..7d907cf 100644
--- a/docs/src/design/wal.md
+++ b/docs/src/cn/design/wal.md
@@ -2,4 +2,4 @@
- [WAL on RocksDB](wal_on_rocksdb.md)
- [WAL on Kafka](wal_on_kafka.md)
-- WAL on OceanBase will be introduced in later.
+- 基于 OceanBase 的 WAL 未来再做介绍
diff --git a/docs/src/design/wal_on_kafka.md b/docs/src/cn/design/wal_on_kafka.md
similarity index 73%
copy from docs/src/design/wal_on_kafka.md
copy to docs/src/cn/design/wal_on_kafka.md
index 47a405e..3a98346 100644
--- a/docs/src/design/wal_on_kafka.md
+++ b/docs/src/cn/design/wal_on_kafka.md
@@ -1,10 +1,10 @@
-# WAL on Kafka
+# 基于 Kafka 的 WAL
-## Architecture
+## 架构
-In this section we present a distributed WAL implementation(based on Kafka). Write-ahead logs(hereinafter referred to as logs) of tables are managed here by region, which can be simply understood as a shared log file of multiple tables.
+在本节中,将会介绍一种分布式 WAL 实现(基于 Kafka)。表的预写日志(write-ahead logs,以下简称日志)在本实现中是按 region 级别管理的,region 可以简单理解为多个表的共享日志文件。
-As shown in the following figure, regions are mapped to topics(with only one partition) in Kafka. And usually two topics are needed by a region, one is used for storing logs and the other is used for storing metadata.
+如下图所示,在本实现中将 region 映射到 Kafka 中的 topic(只有一个 partition)。 通常一个 region 需要两个 topic ,一个用于存储日志,另一个用于存储元数据。
```text
┌──────────────────────────┐
@@ -44,21 +44,20 @@ Read ◄─────────┼─┼─┤ └────────
└──────────────────────────────────────────────────────────┘
```
-## Data Model
+## 数据模型
-### Log Format
+### 日志格式
-The common log format described in [WAL on RocksDB](wal_on_rocksdb.md) is used here.
+日志格式采用了在 [基于 RocksDB 的 WAL](wal_on_rocksdb.md) 中定义的通用格式。
-### Metadata
+### 元数据
-Each region will maintain its metadata both in memory and in Kafka, we call it `RegionMeta` here. It can be thought of as a map, taking table id as a key and `TableMeta` as a value.
-We briefly introduce the variables in `TableMeta` here:
+每个 region 都将在内存和 Kafka 中维护其元数据,我们在这里称之为 `RegionMeta`。它可以被认为是一张映射表,以表 ID 作为键,以 `TableMeta` 作为值。我们简要介绍一下 `TableMeta` 中的变量:
-- `next_seq_num`, the sequence number allocated to the next log entry.
-- `latest_marked_deleted`, the last flushed sequence number, all logs in the table with a lower sequence number than it can be removed.
-- `current_high_watermark`, the high watermark in the Kafka partition after the last writing of this table.
-- `seq_offset_mapping`, mapping from sequence numbers to offsets will be done on every write and will removed to the updated `latest_marked_deleted` after flushing.
+- `next_seq_num`,为下一条写入日志分配的 sequence number。
+- `latest_marked_deleted`,表最后一次触发 flush 时对应的 sequence number, 所以对应 sequence number 小于该值的日志都将被标记为可以删除。
+- `current_high_watermark`, 该表最近一次日志写入后,Kafka 对应 topic 的高水位。
+- `seq_offset_mapping`,sequence number 和 Kafka 对应 topic offset 的映射,每次 flush 后,会将 `latest_marked_deleted` 前的条目进行清理。
```
┌─────────────────────────────────────────┐
@@ -85,28 +84,28 @@ We briefly introduce the variables in `TableMeta` here:
└─────────────────────────────────────────────────────┘
```
-## Main Process
+## 主要流程
-We focus on the main process in one region, following process will be introduced:
+我们主要关于对于单个 region 的主要操作,会介绍以下操作的主要流程:
-- Open or create region.
-- Write and read logs.
-- Delete logs.
+- 打开或创建 region。
+- 读写日志。
+- 删除日志。
-### Open or Create Region
+### 打开或创建 region
-#### Steps
+#### 步骤
-- Search the region in the opened namespace.
-- If the region found, the most important thing we need to do is to recover its metadata, we will introduce this later.
-- If the region not found and auto creating is defined, just create the corresponding topic in Kafka.
-- Add the found or created region to cache, return it afterwards.
+- 在打开的 namespace 中搜索 region。
+- 如果 region 存在,最重要的事是去恢复其元数据,恢复过程将在之后介绍。
+- 如果 region 不存在并且需要自动创建,则需要在 Kafka 上创建对应的 topic。
+- 在 cache 中插入相应 region 并将其返回。
-#### Recovery
+#### 恢复
-As mentioned above, the `RegionMeta` is actually a map of the `TableMeta`. So here we will focus on recovering a specific `TableMeta`, and examples will be given to better illustrate this process.
+上面提到,`RegionMeta` 实际就是以表 ID 为键,以 `TableMeta` 为值的映射表。因此,我们在本节中只关注特定 `TableMeta` 的恢复即可,将在每步的介绍中加入例子以作更好的说明。
-- First, recover the `RegionMeta` from snapshot. We will take a snapshot of the `RegionMeta` in some scenarios (e.g. mark logs deleted, clean logs) and put it to the meta topic. The snapshot is actually the `RegionMeta` at a particular point in time. When recovering a region, we can use it to avoid scanning all logs in the data topic. The following is the example, we recover from the snapshot taken at the time when Kafka high watermark is 64:
+- 从快照中恢复。我们会在某些场景下为 `RegionMeta`制作快照(例如当标记日志为可删除时,真正清理日志时),并且将其写到 meta topic 中,快照实际上就是在某个时间点的 `RegionMeta`。当恢复 region 时,我们可以使用快照来避免扫描 data topic 的全部数据。下面为上述过程对应的例子,我们从在 Kafka 高水位为 64 的时间点时制作的快照中恢复 `RegionMeta`:
```text
high watermark in snapshot: 64
@@ -132,7 +131,7 @@ high watermark in snapshot: 64
└──────────────────────────────┘
```
-- Recovering from logs. After recovering from snapshot, we can continue to recover by scanning logs in data topic from the Kafka high watermark when snapshot is taken, and obviously that avoid scanning the whole data topic. Let's see the example:
+- 从日志数据中恢复。 当从快照中恢复的过程完成后,我们以快照被制作时 data topic 中的高水位为起点,扫描其中的日志数据进行后续恢复,明显这能够避免扫描 data topic 中的全部数据。以下为上述过程的例子:
```text
┌────────────────────────────────────┐
@@ -176,37 +175,37 @@ high watermark in snapshot: 64
└────────────────────────────────────┘
```
-### Write and Read Logs
+### 读写日志
-The writing and reading process in a region is simple.
+读写流程比较简单。
-For writing:
+写流程:
-- Open the specified region (auto create it if necessary).
-- Put the logs to specified Kafka partition by client.
-- Update `next_seq_num`, `current_high_watermark` and `seq_offset_mapping` in corresponding `TableMeta`.
+- 打开指定的 region,如果不存在则需要创建。
+- 利用 client 将日志写入到 region 对应的 data topic 中。
+- 更新 `TableMeta` 中的 `next_seq_num`, `current_high_watermark` 和 `seq_offset_mapping`等元数据,
-For reading:
+读流程:
-- Open the specified region.
-- Just read all the logs of the region, and the split and replay work will be done by the caller.
+- 打开指定的 region。
+- 读取 region 的所有日志数据,按表切分数据和回放等工作需要调用者实现。
-### Delete Logs
+### 删除日志
-Log deletion can be divided into two steps:
+日志的删除可以划分为两个步骤:
-- Mark the logs deleted.
-- Do delayed cleaning work periodically in a background thread.
+- 标记日志为可删除。
+- 利用后台线程做延迟清理。
-#### Mark
+#### 标记
-- Update `latest_mark_deleted` and `seq_offset_mapping`(just retain the entries whose's sequence >= updated latest_mark_deleted) in `TableMeta`.
-- Maybe we need to make and sync the `RegionMeta` snapshot to Kafka while dropping table.
+- 更新在 `TableMeta` 中的 `latest_mark_deleted` 和 `seq_offset_mapping`(需要进行维护,使得每一条目的 sequence number 大于等于更新后的 `latest_mark_deleted`)。
+- 或许我们需要在删除表的时候,制作并及时同步 `RegionMeta` 的快照到 Kafka 中。
-#### Clean
+#### 清理
-The cleaning logic done in a background thread called cleaner:
+清理逻辑如下,会在后台线程中执行:
-- Make `RegionMeta` snapshot.
-- Decide whether to clean the logs based on the snapshot.
-- If so, sync the snapshot to Kafka first, then clean the logs.
+- 制作 `RegionMeta` 的快照。
+- 根据快照判断是否需要进行清理。
+- 如果需要,先同步快照到 Kafka 中,然后清理日志。
diff --git a/docs/src/cn/design/wal_on_rocksdb.md b/docs/src/cn/design/wal_on_rocksdb.md
new file mode 100644
index 0000000..3e1e684
--- /dev/null
+++ b/docs/src/cn/design/wal_on_rocksdb.md
@@ -0,0 +1,84 @@
+# 基于 RocksDB 的 WAL
+
+## 架构
+
+在本节中,我们将介绍单机版 WAL 的实现(基于 RocksDB)。预写日志(write-ahead logs,以下简称日志)在本实现中是按表级别进行管理的,对应的数据结构为 `TableUnit`。为简单起见,所有相关数据(日志或元数据)都存储在单个 column family(RocksDB 中的概念,可以类比关系型数据库的表) 中。
+
+```text
+ ┌─────────────────────────┐
+ │ HoraeDB │
+ │ │
+ │ ┌─────────────────────┐ │
+ │ │ WAL │ │
+ │ │ │ │
+ │ │ ...... │ │
+ │ │ │ │
+ │ │ ┌────────────────┐ │ │
+ Write ─────┼─┼──► TableUnit │ │ │
+ │ │ │ │ │ │
+ Read ─────┼─┼──► ┌────────────┐ │ │ │
+ │ │ │ │ RocksDBRef │ │ │ │
+ │ │ │ └────────────┘ │ │ │
+Delete ─────┼─┼──► │ │ │
+ │ │ └────────────────┘ │ │
+ │ │ ...... │ │
+ │ └─────────────────────┘ │
+ │ │
+ └─────────────────────────┘
+```
+
+## 数据模型
+
+### 通用日志格式
+
+通用日志格式分为 key 格式和 value 格式,下面是对 key 格式各个字段的介绍:
+
+- `namespace`: 出于不同的目的,可能会存在多个 WAL 实例(例如,manifest 也依赖于 wal), `namespace` 用于区分它们。
+- `region_id`: 在一些 WAL 实现中我们可能需要在共享日志文件中,管理来自多个表的日志,`region` 就是描述这样一组表的概念, 而 `region id` 就是其标识。
+- `table_id`: 表的标识。
+- `sequence_num`: 特定表中单条日志的标识。
+- `version`: 用于兼容新旧格式。
+
+```text
++---------------+----------------+-------------------+--------------------+-------------+
+| namespace(u8) | region_id(u64) | table_id(u64) | sequence_num(u64) | version(u8) |
++---------------+----------------+-------------------+--------------------+-------------+
+```
+
+下面是对 value 格式各个字段的介绍(`payload` 可以理解为编码后的具体日志内容):
+
+```text
++--------------------+----------+
+| version header(u8) | payload |
++--------------------+----------+
+```
+
+### 元数据
+
+与日志格式相同,元数据以 key-value 格式存储, 本实现的元数据实际只是存储了每张表最近一次 flush 对应的 `sequence_num`。下面是定义的元数据 key 格式和其中字段的介绍:
+
+- `namespace`, `table_id`, `version` 和日志格式中相同。
+- `key_type`, 用于定义元数据的类型,现在只定义了 MaxSeq 类型的元数据,在。
+ 因为在 RocksDB 版本的 WAL 实现中,日志是按表级别进行管理,所以这个 key 格式里面没有 `region_id` 字段。
+
+```text
++---------------+--------------+----------------+-------------+
+| namespace(u8) | key_type(u8) | table_id(u64) | version(u8) |
++---------------+--------------+----------------+-------------+
+```
+
+这是定义的元数据值格式,如下所示,其中只有 `version` 和 `max_seq`(flushed sequence):
+
+```text
++-------------+--------------+
+| version(u8) | max_seq(u64) |
++-------------+--------------+
+```
+
+## 主要流程
+
+- 打开 `TableUnit`:
+ - 读取所有表的最新日志条目,目的是恢复表的 next sequence num(将会分配给下一条写入的日志)。
+ - 扫描 metadata 恢复上一步遗漏的表的 next sequence num(因为可能有表刚刚触发了 flush,并且之后没有新的写入日志,所以当前不存在日志数据)。
+- 读写日志。从 RocksDB 读取或者写入相关日志数据。
+- 删除日志。为简单起见,在本实现中只是同步地删除相应的日志数据。
diff --git a/docs/src/cn/dev/README.md b/docs/src/cn/dev/README.md
new file mode 100644
index 0000000..a5b829e
--- /dev/null
+++ b/docs/src/cn/dev/README.md
@@ -0,0 +1 @@
+# 开发手册
diff --git a/docs/src/cn/dev/compile_run.md b/docs/src/cn/dev/compile_run.md
new file mode 100644
index 0000000..fb06f82
--- /dev/null
+++ b/docs/src/cn/dev/compile_run.md
@@ -0,0 +1,60 @@
+为了编译 HoraeDB, 首先需要安装相关的依赖(包括 `Rust` 的工具链)。
+
+# 依赖(Ubuntu20.04)
+
+假设我们的开发环境是 Ubuntu20.04, 可以执行如下命令来安装所需的依赖。
+
+```shell
+sudo apt install git curl gcc g++ libssl-dev pkg-config cmake protobuf-compiler
+```
+
+需要注意的是,项目的编译对 cmake、gcc、g++等依赖项有版本要求。
+
+如果你的开发环境是旧的 Linux 发行版,有必要手动安装这些依赖项的高版本。
+
+# 依赖(MacOS)
+
+如果你的开发环境是 `MacOS` ,可以使用如下命令手动安装这些依赖项的高版本。
+
+1. 安装命令行工具:
+
+```shell
+xcode-select --install
+```
+
+2. 安装 cmake:
+
+```shell
+brew install cmake
+```
+
+3. 安装 protobuf:
+
+```shell
+brew install protobuf
+```
+
+# Rust
+
+`Rust` 可以使用 [rustup](https://rustup.rs/) 来安装。
+安装 `Rust` 后,进入 HoraeDB 工程目录,根据工具链文件指定的 `Rust` 版本会被自动下载。
+
+执行后,你需要添加环境变量来使用 `Rust` 工具链。只要把下面的命令放到你的`~/.bashrc`或`~/.bash_profile`中即可。
+
+```shell
+source $HOME/.cargo/env
+```
+
+# 编译运行
+
+编译 HoraeDB 命令如下:
+
+```
+cargo build --release
+```
+
+然后可以使用特定的配置文件运行 HoraeDB。
+
+```bash
+./target/release/horaedb-server --config ./docs/minimal.toml
+```
diff --git a/docs/src/cn/dev/conventional_commit.md b/docs/src/cn/dev/conventional_commit.md
new file mode 100644
index 0000000..6ad1530
--- /dev/null
+++ b/docs/src/cn/dev/conventional_commit.md
@@ -0,0 +1,3 @@
+# 开发规约
+
+开发规约请参考英文版本[文档](../../en/dev/conventional_commit.md)
diff --git a/docs/src/dev/platform.md b/docs/src/cn/dev/platform.md
similarity index 100%
copy from docs/src/dev/platform.md
copy to docs/src/cn/dev/platform.md
diff --git a/docs/src/dev/profiling.md b/docs/src/cn/dev/profiling.md
similarity index 56%
copy from docs/src/dev/profiling.md
copy to docs/src/cn/dev/profiling.md
index 20d666a..2463571 100644
--- a/docs/src/dev/profiling.md
+++ b/docs/src/cn/dev/profiling.md
@@ -1,39 +1,39 @@
-## Profiling
+## 剖析
-### CPU profiling
+### CPU 剖析
-HoraeDB provides cpu profiling http api `debug/profile/cpu`.
+HoraeDB 提供 CPU 剖析 http 接口 `debug/profile/cpu`.
-Example:
+例子:
```
-// 60s cpu sampling data
+// 60s CPU 采样数据
curl 0:5000/debug/profile/cpu/60
-// Output file path.
+// 产出文件
/tmp/flamegraph_cpu.svg
```
-### Heap profiling
+### 内存剖析
-HoraeDB provides heap profiling http api `debug/profile/heap`.
+HoraeDB 提供内存剖析 http 接口 `debug/profile/heap`.
-### Install dependencies
+### 安装依赖
```
sudo yum install -y jemalloc-devel ghostscript graphviz
```
-Example:
+例子:
```
-// enable malloc prof
+// 开启 malloc prof
export MALLOC_CONF=prof:true
-// run horaedb-server
+// 运行 horaedb-server
./horaedb-server ....
-// 60s cpu sampling data
+// 60s 内存采样数据
curl -L '0:5000/debug/profile/heap/60' > /tmp/heap_profile
jeprof --show_bytes --pdf /usr/bin/horaedb-server /tmp/heap_profile > profile_heap.pdf
diff --git a/docs/src/cn/dev/roadmap.md b/docs/src/cn/dev/roadmap.md
new file mode 100644
index 0000000..0e8ef16
--- /dev/null
+++ b/docs/src/cn/dev/roadmap.md
@@ -0,0 +1,52 @@
+## RoadMap
+
+### v0.1.0
+
+- [x] 支持基于本地磁盘的 Standalone 版本
+- [x] 支持分析存储格式
+- [x] 支持 SQL
+
+### v0.2.0
+
+- [x] 静态路由的分布式版本
+- [x] 远端存储支持阿里云 OSS
+- [x] 支持基于 [OBKV](https://github.com/oceanbase/oceanbase)的 WAL
+
+### v0.3.0
+
+- [x] 发布多语言客户端,包括 Java, Rust 和 Python
+- [x] 支持使用 `HoraeMeta` 的静态集群
+- [x] 混合存储格式基本实现
+
+### v0.4.0
+
+- [x] 实现更复杂的集群方案,增强 HoraeDB 的可靠性和可扩展性
+- [x] 构建日常运行的、基于 TSBS 的压测任务
+
+### v1.0.0-alpha (Released)
+
+- [x] 基于 `Apache Kafka` 实现分布式 WAL
+- [x] 发布 Golang 客户端
+- [x] 优化时序场景下的查询性能
+- [x] 支持集群模式下表的动态转移
+
+### v1.0.0
+
+- [x] 正式发布 HoraeDB 和相关 SDK,并完成所有的 breaking changes
+- [x] 完成分区表的主要工作
+- [x] 优化查询性能,特别是云原生集群模式下,包括:
+ - 多级缓存
+ - 多种方式减少从远端获取的数据量(提高 SST 数据过滤精度)
+ - 提高获取远程对象存储数据的并发度
+- [x] 通过控制合并时的资源消耗,提高数据写入性能
+
+### Afterwards
+
+随着对时序数据库及其各种使用情况的深入了解,我们的大部分工作将聚焦在性能、可靠性、可扩展性、易用性以及与开源社区的合作方面
+
+- [ ] 增加支持 `PromQL`, `InfluxQL`, `OpenTSDB` 协议
+- [ ] 提供基础的运维工具。特别包括如下:
+ - 适配云基础设施的部署工具,如 `Kubernetes`
+ - 加强自监控能力,特别是关键的日志和指标
+- [ ] 开发多种工具,方便使用 HoraeDB,例如,数据导入和导出工具
+- [ ] 探索新的存储格式,提高混合负载(分析和时序负载)的性能
diff --git a/docs/src/cn/dev/style_guide.md b/docs/src/cn/dev/style_guide.md
new file mode 100644
index 0000000..5941feb
--- /dev/null
+++ b/docs/src/cn/dev/style_guide.md
@@ -0,0 +1,3 @@
+# 风格规范
+
+风格规范请参考英文版本[文档](../../en/dev/style_guide.md)
diff --git a/docs/src/cn/ecosystem/README.md b/docs/src/cn/ecosystem/README.md
new file mode 100644
index 0000000..67851d6
--- /dev/null
+++ b/docs/src/cn/ecosystem/README.md
@@ -0,0 +1,7 @@
+# 周边生态
+
+HoraeDB 是一个开放的系统,鼓励合作和创新,允许开发者使用最适合其自身需求的系统。目前 HoraeDB 支持以下系统:
+
+- [Prometheus](prometheus.md)
+- [InfluxDB](influxdb.md)
+- [OpenTSDB](opentsdb.md)
diff --git a/docs/src/cn/ecosystem/influxdb.md b/docs/src/cn/ecosystem/influxdb.md
new file mode 100644
index 0000000..a470a79
--- /dev/null
+++ b/docs/src/cn/ecosystem/influxdb.md
@@ -0,0 +1,88 @@
+# InfluxDB
+
+[InfluxDB](https://www.influxdata.com/products/influxdb-overview/) 是一个时间序列数据库,旨在处理高写入和查询负载。它是 TICK 堆栈的一个组成部分。InfluxDB 旨在用作涉及大量时间戳数据的任何用例的后备存储,包括 DevOps 监控、应用程序指标、物联网传感器数据和实时分析。
+
+HoraeDB 支持 [InfluxDB v1.8](https://docs.influxdata.com/influxdb/v1.8/tools/api/#influxdb-1x-http-endpoints) 写入和查询 API。
+
+> 注意:用户需要将以下配置添加到服务器的配置中才能尝试 InfluxDB 写入/查询。
+
+```
+[server.default_schema_config]
+default_timestamp_column_name = "time"
+```
+
+# 写入
+
+```
+curl -i -XPOST "http://localhost:5440/influxdb/v1/write" --data-binary '
+demo,tag1=t1,tag2=t2 field1=90,field2=100 1679994647000
+demo,tag1=t1,tag2=t2 field1=91,field2=101 1679994648000
+demo,tag1=t11,tag2=t22 field1=90,field2=100 1679994647000
+demo,tag1=t11,tag2=t22 field1=91,field2=101 1679994648000
+'
+```
+
+Post 的内容采用的是 [InfluxDB line protocol](https://docs.influxdata.com/influxdb/v1.8/write_protocols/line_protocol_reference/) 格式。
+
+`measurement` 将映射到 HoraeDB 中的一个表,在首次写入时 server 会自动进行建表(注意:创建表的 TTL 是 7d,写入超过当前周期数据会被丢弃)。
+
+例如,在上面插入数据时,HoraeDB 中将创建下表:
+
+```
+CREATE TABLE `demo` (
+ `tsid` uint64 NOT NULL,
+ `time` timestamp NOT NULL,
+ `field1` double,
+ `field2` double,
+ `tag1` string TAG,
+ `tag2` string TAG,
+ PRIMARY KEY (tsid, time),
+ timestamp KEY (time))
+```
+
+## 注意事项
+
+- InfluxDB 在写入时,时间戳精度默认是纳秒,HoraeDB 只支持毫秒级时间戳,用户可以通过 `precision` 参数指定数据精度,HoraeDB 内部会自动转成毫秒精度。
+- 暂时不支持诸如 `db` 等查询参数
+
+# 查询
+
+```sh
+ curl -G 'http://localhost:5440/influxdb/v1/query' --data-urlencode 'q=SELECT * FROM "demo"'
+```
+
+查询结果和 InfluxDB 查询接口一致:
+
+```json
+{
+ "results": [
+ {
+ "statement_id": 0,
+ "series": [
+ {
+ "name": "demo",
+ "columns": ["time", "field1", "field2", "tag1", "tag2"],
+ "values": [
+ [1679994647000, 90.0, 100.0, "t1", "t2"],
+ [1679994647000, 90.0, 100.0, "t11", "t22"],
+ [1679994648000, 91.0, 101.0, "t1", "t2"],
+ [1679994648000, 91.0, 101.0, "t11", "t22"]
+ ]
+ }
+ ]
+ }
+ ]
+}
+```
+
+## 如何在 Grafana 中使用
+
+HoraeDB 可以用作 Grafana 中的 InfluxDB 数据源。具体方式如下:
+
+- 在新增数据源时,选择 InfluxDB 类型
+- 在 HTTP URL 处,输入 `http://{ip}:{5440}/influxdb/v1/` 。对于本地部署的场景,可以直接输入 http://localhost:5440/influxdb/v1/
+- `Save & test`
+
+## 注意事项
+
+暂时不支持诸如 `epoch`, `db` 等的查询参数
diff --git a/docs/src/ecosystem/opentsdb.md b/docs/src/cn/ecosystem/opentsdb.md
similarity index 58%
copy from docs/src/ecosystem/opentsdb.md
copy to docs/src/cn/ecosystem/opentsdb.md
index b1d846b..66f4afb 100644
--- a/docs/src/ecosystem/opentsdb.md
+++ b/docs/src/cn/ecosystem/opentsdb.md
@@ -1,12 +1,12 @@
# OpenTSDB
-[OpenTSDB](http://opentsdb.net/) is a distributed and scalable time series database based on HBase.
+[OpenTSDB](http://opentsdb.net/) 是基于 HBase 的分布式、可伸缩的时间序列数据库。
-# Write
+# 写入
-HoraeDB follows the [OpenTSDB put](http://opentsdb.net/docs/build/html/api_http/put.html) write protocol.
+HoraeDB 遵循 [OpenTSDB put](http://opentsdb.net/docs/build/html/api_http/put.html) 写入接口。
-`summary` and `detailed` are not yet supported.
+`summary` 和 `detailed` 还未支持。
```
curl --location 'http://localhost:5440/opentsdb/api/put' \
@@ -28,12 +28,11 @@ curl --location 'http://localhost:5440/opentsdb/api/put' \
"host": "web01"
}
}]'
-'
```
-Metric will be mapped to table in HoraeDB, and it will be created automatically in first write(Note: The default TTL is 7d, and points written exceed TTL will be discarded directly).
+`metric` 将映射到 HoraeDB 中的一个表,在首次写入时 server 会自动进行建表(注意:创建表的 TTL 是 7d,写入超过当前周期数据会被丢弃)。
-For example, when inserting data above, table below will be automatically created in HoraeDB:
+例如,在上面插入数据时,HoraeDB 中将创建下表:
```
CREATE TABLE `sys.cpu.nice`(
@@ -51,6 +50,6 @@ CREATE TABLE `sys.cpu.nice`(
update_mode = 'OVERWRITE', write_buffer_size = '33554432')
```
-# Query
+# 查询
-OpenTSDB query protocol is not currently supported, [tracking issue](https://github.com/apache/incubator-horaedb/issues/904).
+暂不支持 OpenTSDB 查询,[tracking issue](https://github.com/apache/incubator-horaedb/issues/904)。
diff --git a/docs/src/cn/ecosystem/prometheus.md b/docs/src/cn/ecosystem/prometheus.md
new file mode 100644
index 0000000..c6d8630
--- /dev/null
+++ b/docs/src/cn/ecosystem/prometheus.md
@@ -0,0 +1,49 @@
+# Prometheus
+
+[Prometheus](https://prometheus.io/)是一个流行的云原生监控工具,由于其可扩展性、可靠性和可伸缩性,被企业广泛采用。它用于从云原生服务(如 Kubernetes 和 OpenShift)中获取指标,并将其存储在时序数据库中。Prometheus 也很容易扩展,允许用户用其他数据库扩展其特性和功能。
+
+HoraeDB 可以作为 Prometheus 的长期存储解决方案,同时支持远程读取和远程写入 API。
+
+## 配置
+
+你可以通过在`prometheus.yml`中添加以下几行来配置 Prometheus 使用 HoraeDB 作为一个远程存储:
+
+```yml
+remote_write:
+ - url: "http://<address>:<http_port>/prom/v1/write"
+remote_read:
+ - url: "http://<address>:<http_port>/prom/v1/read"
+```
+
+每一个指标都会对应一个 HoraeDB 中的表:
+
+- 标签(labels)对应字符串类型的 `tag` 列
+- 数据的时间戳对应一个 timestamp 类型的 `timestmap` 列
+- 数据的值对应一个双精度浮点数类型的 `value` 列
+
+比如有如下 Prometheus 指标:
+
+```
+up{env="dev", instance="127.0.0.1:9090", job="prometheus-server"}
+```
+
+对应 HoraeDB 中如下的表(注意:创建表的 TTL 是 7d,写入超过当前周期数据会被丢弃):
+
+```
+CREATE TABLE `up` (
+ `timestamp` timestamp NOT NULL,
+ `tsid` uint64 NOT NULL,
+ `env` string TAG,
+ `instance` string TAG,
+ `job` string TAG,
+ `value` double,
+ PRIMARY KEY (tsid, timestamp),
+ timestamp KEY (timestamp)
+);
+
+SELECT * FROM up;
+```
+
+| tsid | timestamp | env | instance | job | value |
+| :------------------: | :-----------: | :-: | :------------: | :---------------: | :---: |
+| 12683162471309663278 | 1675824740880 | dev | 127.0.0.1:9090 | prometheus-server | 1 |
diff --git a/docs/src/cn/operation/README.md b/docs/src/cn/operation/README.md
new file mode 100644
index 0000000..30a6156
--- /dev/null
+++ b/docs/src/cn/operation/README.md
@@ -0,0 +1,9 @@
+# 运维文档
+
+本章介绍 HoraeDB 的运维相关的操作,包括表相关操作,设置访问黑名单,已经如何监控 HoraeDB。未来还会介绍集群扩容,容灾相关:
+
+- [表](./table.md)
+- [系统表](./system_table.md)
+- [黑名单](./block_list.md)
+- [监控](./observability.md)
+- [集群运维](./cluster.md)
diff --git a/docs/src/operation/block_list.md b/docs/src/cn/operation/block_list.md
similarity index 69%
copy from docs/src/operation/block_list.md
copy to docs/src/cn/operation/block_list.md
index 71ae32d..a028401 100644
--- a/docs/src/operation/block_list.md
+++ b/docs/src/cn/operation/block_list.md
@@ -1,10 +1,10 @@
-# Block List
+# 黑名单
-## Add block list
+## 增加黑名单
-If you want to reject query for a table, you can add table name to 'read_block_list'.
+如果你想限制某个表的查询,可以把表名加到 `read_block_list` 中。
-### Example
+示例如下:
```shell
curl --location --request POST 'http://localhost:5000/admin/block' \
@@ -16,7 +16,7 @@ curl --location --request POST 'http://localhost:5000/admin/block' \
}'
```
-### Response
+返回结果:
```json
{
@@ -25,11 +25,11 @@ curl --location --request POST 'http://localhost:5000/admin/block' \
}
```
-## Set block list
+## 设置黑名单
-You can use set operation to clear exist tables and set new tables to 'read_block_list' like following example.
+设置黑名单的操作首先会清理已有的列表,然后再把新的表设置进去。
-### Example
+示例如下:
```shell
curl --location --request POST 'http://localhost:5000/admin/block' \
@@ -41,7 +41,7 @@ curl --location --request POST 'http://localhost:5000/admin/block' \
}'
```
-### Response
+返回结果:
```json
{
@@ -50,11 +50,9 @@ curl --location --request POST 'http://localhost:5000/admin/block' \
}
```
-## Remove block list
+## 删除黑名单
-You can remove tables from 'read_block_list' like following example.
-
-### Example
+如果你想把表从黑名单中移除,可以使用如下命令:
```shell
curl --location --request POST 'http://localhost:5000/admin/block' \
@@ -66,7 +64,7 @@ curl --location --request POST 'http://localhost:5000/admin/block' \
}'
```
-### Response
+返回结果:
```json
{
diff --git a/docs/src/operation/horaemeta.md b/docs/src/cn/operation/horaemeta.md
similarity index 78%
copy from docs/src/operation/horaemeta.md
copy to docs/src/cn/operation/horaemeta.md
index 48116ac..33692b4 100644
--- a/docs/src/operation/horaemeta.md
+++ b/docs/src/cn/operation/horaemeta.md
@@ -1,14 +1,14 @@
-# Cluster Operation
+# 集群运维
-The Operations for HoraeDB cluster mode, it can only be used when HoraeMeta is deployed.
+集群运维接口的使用前提是,HoraeDB 部署为使用 HoraeMeta 的集群模式。
-## Operation Interface
+## 运维接口
-You need to replace 127.0.0.1 with the actual project path.
+注意: 如下接口在实际使用时需要将 127.0.0.1 替换为 HoraeMeta 的真实地址。
-- Query table
- When tableNames is not empty, use tableNames for query.
- When tableNames is empty, ids are used for query. When querying with ids, schemaName is useless.
+- 查询表元信息
+ 当 tableNames 不为空的时候,使用 tableNames 进行查询。
+ 当 tableNames 为空的时候,使用 ids 进行查询。使用 ids 查询的时候,schemaName 不生效。
```
curl --location 'http://127.0.0.1:8080/api/v1/table/query' \
@@ -27,7 +27,7 @@ curl --location 'http://127.0.0.1:8080/api/v1/table/query' \
}'
```
-- Query the route of table
+- 查询表的路由信息
```
curl --location --request POST 'http://127.0.0.1:8080/api/v1/route' \
@@ -39,7 +39,7 @@ curl --location --request POST 'http://127.0.0.1:8080/api/v1/route' \
}'
```
-- Query the mapping of shard and node
+- 查询节点对应的 Shard 信息
```
curl --location --request POST 'http://127.0.0.1:8080/api/v1/getNodeShards' \
@@ -49,8 +49,8 @@ curl --location --request POST 'http://127.0.0.1:8080/api/v1/getNodeShards' \
}'
```
-- Query the mapping of table and shard
- If ShardIDs in the request is empty, query with all shardIDs in the cluster.
+- 查询 Shard 对应的表信息
+ 如果 shardIDs 为空时,查询所有 shard 上表信息。
```
curl --location --request POST 'http://127.0.0.1:8080/api/v1/getShardTables' \
@@ -61,7 +61,7 @@ curl --location --request POST 'http://127.0.0.1:8080/api/v1/getShardTables' \
}'
```
-- Drop table
+- 删除指定表的元数据
```
curl --location --request POST 'http://127.0.0.1:8080/api/v1/dropTable' \
@@ -73,7 +73,7 @@ curl --location --request POST 'http://127.0.0.1:8080/api/v1/dropTable' \
}'
```
-- Transfer leader shard
+- Shard 切主
```
curl --location --request POST 'http://127.0.0.1:8080/api/v1/transferLeader' \
@@ -86,7 +86,7 @@ curl --location --request POST 'http://127.0.0.1:8080/api/v1/transferLeader' \
}'
```
-- Split shard
+- Shard 分裂
```
curl --location --request POST 'http://127.0.0.1:8080/api/v1/split' \
@@ -100,7 +100,7 @@ curl --location --request POST 'http://127.0.0.1:8080/api/v1/split' \
}'
```
-- Create cluster
+- 创建 HoraeDB 集群
```
curl --location 'http://127.0.0.1:8080/api/v1/clusters' \
@@ -108,13 +108,13 @@ curl --location 'http://127.0.0.1:8080/api/v1/clusters' \
--data '{
"name":"testCluster",
"nodeCount":3,
- "shardTotal":9,
+ "ShardTotal":9,
"enableScheduler":true,
"topologyType":"static"
}'
```
-- Update cluster
+- 更新 HoraeDB 集群
```
curl --location --request PUT 'http://127.0.0.1:8080/api/v1/clusters/{NewClusterName}' \
@@ -127,13 +127,13 @@ curl --location --request PUT 'http://127.0.0.1:8080/api/v1/clusters/{NewCluster
}'
```
-- List clusters
+- 列出 HoraeDB 集群
```
curl --location 'http://127.0.0.1:8080/api/v1/clusters'
```
-- Update DeployMode
+- 修改 DeployMode
```
curl --location --request PUT 'http://127.0.0.1:8080/api/v1/cluster/{ClusterName}/deployMode' \
@@ -143,13 +143,13 @@ curl --location --request PUT 'http://127.0.0.1:8080/api/v1/cluster/{ClusterName
}'
```
-- Query DeployMode
+- 查询 DeployMode
```
curl --location 'http://127.0.0.1:8080/api/v1/cluster/{ClusterName}/deployMode'
```
-- Update flow limiter
+- 更新限流器
```
curl --location --request PUT 'http://127.0.0.1:8080/api/v1/flowLimiter' \
@@ -161,19 +161,19 @@ curl --location --request PUT 'http://127.0.0.1:8080/api/v1/flowLimiter' \
}'
```
-- Query information of flow limiter
+- 查询限流器信息
```
curl --location 'http://127.0.0.1:8080/api/v1/flowLimiter'
```
-- List nodes of HoraeMeta cluster
+- HoraeMeta 列出节点
```
curl --location 'http://127.0.0.1:8080/api/v1/etcd/member'
```
-- Move leader of HoraeMeta cluster
+- HoraeMeta 节点切主
```
curl --location 'http://127.0.0.1:8080/api/v1/etcd/moveLeader' \
@@ -183,7 +183,7 @@ curl --location 'http://127.0.0.1:8080/api/v1/etcd/moveLeader' \
}'
```
-- Add node of HoraeMeta cluster
+- HoraeMeta 节点扩容
```
curl --location --request PUT 'http://127.0.0.1:8080/api/v1/etcd/member' \
@@ -193,7 +193,7 @@ curl --location --request PUT 'http://127.0.0.1:8080/api/v1/etcd/member' \
}'
```
-- Replace node of HoraeMeta cluster
+- HoraeMeta 替换节点
```
curl --location 'http://127.0.0.1:8080/api/v1/etcd/member' \
diff --git a/docs/src/cn/operation/observability.md b/docs/src/cn/operation/observability.md
new file mode 100644
index 0000000..7867688
--- /dev/null
+++ b/docs/src/cn/operation/observability.md
@@ -0,0 +1,98 @@
+# 监控
+
+HoraeDB 支持使用 Prometheus 和 Grafana 做自监控。
+
+## Prometheus
+
+[Prometheus](https://github.com/prometheus/prometheus) 是非常流行的系统和服务监控系统。
+
+### 配置
+
+把下面的配置保存到 `prometheus.yml` 文件中。比如,在 `tmp` 目录下,文件地址为 `/tmp/prometheus.yml`。
+
+有两个 HoraeDB http 服务启动在 `localhost:5440`、`localhost:5441`。
+
+```yaml
+global:
+ scrape_interval: 30s
+scrape_configs:
+ - job_name: horaedb-server
+ static_configs:
+ - targets: [your_ip:5440, your_ip:5441]
+ labels:
+ env: horaedbcluster
+```
+
+Prometheus 详细配置见[这里](https://prometheus.io/docs/prometheus/latest/configuration/configuration/)。
+
+### 运行
+
+你可以使用 docker 来运行 Prometheus。Docker 镜像在[这里](https://hub.docker.com/r/prom/prometheus)可以找到。
+
+```
+docker run \
+ -d --name=prometheus \
+ -p 9090:9090 \
+ -v /tmp/prometheus.yml:/etc/prometheus/prometheus.yml \
+ prom/prometheus:v2.41.0
+```
+
+更多 Prometheus 安装方法,参考[这里](https://prometheus.io/docs/prometheus/latest/installation/)。
+
+## Grafana
+
+[Grafana](https://github.com/grafana/grafana) 是一个非常流行的可观察性和数据可视化平台。
+
+### 运行
+
+你可以使用 docker 来运行 Grafana。Docker 镜像在[这里](https://hub.docker.com/r/grafana/grafana)可以找到。
+
+```
+docker run -d --name=grafana -p 3000:3000 grafana/grafana:9.3.6
+```
+
+默认用户密码是 admin/admin.
+
+运行上面命令后,grafana 可以用浏览器打开 http://127.0.0.1:3000。
+
+更多 Prometheus 安装方法,参考[这里](https://grafana.com/docs/grafana/latest/setup-grafana/installation/)。
+
+### 配置数据源
+
+1. 将光标悬停在配置(齿轮)图标上。
+2. 选择数据源。
+3. 选择 Prometheus 数据源。
+
+注意: Prometheus 的 url 需要填写成这样 `http://your_ip:9090`, your_ip 换成本地地址。
+
+<img src="../../resources/images/grafana-datasource.png" height="400" width="200"/>
+
+更详细的配置可以参考[这里](https://grafana.com/docs/grafana/latest/datasources/prometheus/)。
+
+### 导入监控页面
+
+<a href="../../resources/grafana-dashboard.json">页面 json</a>
+
+<img src="../../resources/images/grafana-dashboard.png" height="400" width="200"/>
+
+## HoraeDB 指标
+
+当导入完成后,你可以看到如下页面:
+
+<img src="../../resources/images/grafana-horaedb-dashboard.png" height="400" width="600"/>
+
+### Panels
+
+- tps: 集群写入请求数。
+- qps: 集群查询请求数。
+- 99th query/write duration: 查询写入的 99% 分位数。
+- table query by table: 表查询请求数。
+- 99th write duration details by instance: 写入耗时的 99% 分位数。
+- 99th query duration details by instance: 查询耗时的 99% 分位数。
+- 99th write partition table duration: 分区表查询耗时的 99% 分位数。
+- table rows: 表的写入行数。
+- table rows by instance: 实例级别的写入行数。
+- total tables to write: 有数据写入的表数目。
+- flush count: HoraeDB flush 的次数。
+- 99th flush duration details by instance: 实例级别的 flush 耗时的 99% 分位数。
+- 99th write stall duration details by instance: 实例级别的写入停顿时间的 99% 分位数 。
diff --git a/docs/src/operation/system_table.md b/docs/src/cn/operation/system_table.md
similarity index 71%
copy from docs/src/operation/system_table.md
copy to docs/src/cn/operation/system_table.md
index 8359d82..bf5065f 100644
--- a/docs/src/operation/system_table.md
+++ b/docs/src/cn/operation/system_table.md
@@ -1,9 +1,10 @@
-# Table Operation
+# 系统表
-## Query Table Information
+## 查询 Table 信息
-Like Mysql's `information_schema.tables`, HoraeDB provides `system.public.tables` to save tables information.
-Columns:
+类似于 Mysql's `information_schema.tables`, HoraeDB 提供 `system.public.tables` 存储表信息。
+
+`system.public.tables` 表的列如下 :
- timestamp([TimeStamp])
- catalog([String])
@@ -12,9 +13,7 @@ Columns:
- table_id([Uint64])
- engine([String])
-### Example
-
-Query table information via table_name like this:
+通过表名查询表信息示例如下:
```shell
curl --location --request POST 'http://localhost:5000/sql' \
@@ -24,7 +23,7 @@ curl --location --request POST 'http://localhost:5000/sql' \
}'
```
-### Response
+返回结果
```json
{
diff --git a/docs/src/operation/table.md b/docs/src/cn/operation/table.md
similarity index 78%
copy from docs/src/operation/table.md
copy to docs/src/cn/operation/table.md
index 031fa95..be1acd5 100644
--- a/docs/src/operation/table.md
+++ b/docs/src/cn/operation/table.md
@@ -1,10 +1,10 @@
-# Table Operation
+# 表操作
-HoraeDB supports standard SQL protocols and allows you to create tables and read/write data via http requests. More [SQL](../sql/README.md)
+HoraeDB 支持标准的 SQL,用户可以使用 Http 协议创建表和读写表。更多内容可以参考 [SQL 语法](../sql/README.md)
-## Create Table
+## 创建表
-### Example
+示例如下
```shell
curl --location --request POST 'http://127.0.0.1:5000/sql' \
@@ -14,9 +14,9 @@ curl --location --request POST 'http://127.0.0.1:5000/sql' \
}'
```
-## Write Data
+## 写数据
-### Example
+示例如下
```shell
curl --location --request POST 'http://127.0.0.1:5000/sql' \
@@ -26,9 +26,9 @@ curl --location --request POST 'http://127.0.0.1:5000/sql' \
}'
```
-## Read Data
+## 读数据
-### Example
+示例如下
```shell
curl --location --request POST 'http://127.0.0.1:5000/sql' \
@@ -38,9 +38,9 @@ curl --location --request POST 'http://127.0.0.1:5000/sql' \
}'
```
-## Query Table Info
+## 查询表信息
-### Example
+示例如下
```shell
curl --location --request POST 'http://127.0.0.1:5000/sql' \
@@ -50,9 +50,9 @@ curl --location --request POST 'http://127.0.0.1:5000/sql' \
}'
```
-### Drop Table
+### Drop 表
-### Example
+示例如下
```shell
curl --location --request POST 'http://127.0.0.1:5000/sql' \
@@ -62,9 +62,9 @@ curl --location --request POST 'http://127.0.0.1:5000/sql' \
}'
```
-## Route Table
+## 查询表路由
-### Example
+示例如下
```shell
curl --location --request GET 'http://127.0.0.1:5000/route/{table_name}'
diff --git a/docs/src/protocol.md b/docs/src/cn/protocol.md
similarity index 100%
copy from docs/src/protocol.md
copy to docs/src/cn/protocol.md
diff --git a/docs/src/cn/quick_start.md b/docs/src/cn/quick_start.md
new file mode 100644
index 0000000..497f6e2
--- /dev/null
+++ b/docs/src/cn/quick_start.md
@@ -0,0 +1,115 @@
+# 快速开始
+
+本章介绍如何快速启动 HoraeDB。在这里你将会学到启动一个单机模式的 HoraeDB,然后使用 SQL 写入一些数据并查询结果。
+
+## 启动
+
+使用 [HoraeDB docker 镜像](https://github.com/apache/incubator-horaedb/pkgs/container/horaedb-server) 是一种最简单的启动方式;如果你还没有安装 Docker,请首先参考 [这里](https://www.docker.com/products/docker-desktop/) 安装 Docker。
+
+> 注意:请选择一个大于等于 v1.0.0 的 tag 镜像。
+
+使用如下命令安装并启动一个单机版 HoraeDB。
+
+```bash
+docker run -d --name horaedb-server \
+ -p 8831:8831 \
+ -p 3307:3307 \
+ -p 5440:5440 \
+ ghcr.io/apache/horaedb-server:nightly-20231222-f57b3827
+```
+
+启动后 HoraeDB 会监听如下端口:
+
+- 8831, gRPC port
+- 3307, MySQL port
+- 5440, HTTP port
+
+`HTTP` 协议是最简单的交互方式,接下来的演示会使用 `HTTP` 协议进行介绍。不过在生产环境,我们推荐使用 `gRPC/MySQL`。
+
+### 自定义 docker 的配置
+
+参考如下命令,可以自定义 docker 中 horaedb-server 的配置,并把数据目录 `/data` 挂载到 docker 母机的硬盘上。
+
+```
+wget -c https://raw.githubusercontent.com/apache/incubator-horaedb/main/docs/minimal.toml -O horaedb.toml
+
+sed -i 's/\/tmp\/horaedb/\/data/g' horaedb.toml
+
+docker run -d --name horaedb-server \
+ -p 8831:8831 \
+ -p 3307:3307 \
+ -p 5440:5440 \
+ -v ./horaedb.toml:/etc/horaedb/horaedb.toml \
+ -v ./data:/data \
+ ghcr.io/apache/horaedb-server:nightly-20231222-f57b3827
+```
+
+## 写入和查询数据
+
+### 建表
+
+```shell
+curl --location --request POST 'http://127.0.0.1:5440/sql' \
+-d '
+CREATE TABLE `demo` (
+ `name` string TAG,
+ `value` double NOT NULL,
+ `t` timestamp NOT NULL,
+ timestamp KEY (t))
+ENGINE=Analytic
+ with
+(enable_ttl="false")
+'
+```
+
+### 写数据
+
+```shell
+curl --location --request POST 'http://127.0.0.1:5440/sql' \
+-d '
+INSERT INTO demo (t, name, value)
+ VALUES (1651737067000, "horaedb", 100)
+'
+```
+
+### 查询
+
+```shell
+curl --location --request POST 'http://127.0.0.1:5440/sql' \
+-d '
+SELECT
+ *
+FROM
+ `demo`
+'
+```
+
+### 展示建表语句
+
+```shell
+curl --location --request POST 'http://127.0.0.1:5440/sql' \
+-d '
+SHOW CREATE TABLE `demo`
+'
+```
+
+### 删除表
+
+```shell
+curl --location --request POST 'http://127.0.0.1:5440/sql' \
+-d '
+DROP TABLE `demo`
+'
+```
+
+## 使用 SDK
+
+当前我们支持多种开发语言 SDK,例如 Java,Rust,Python, Go 等, 具体使用方式请参考 [sdk](sdk/README.md)。
+
+## 下一步
+
+恭喜你,你已经学习了 HoraeDB 的简单使用。关于 HoraeDB 的更多信息,请参见以下内容。
+
+- [SQL 语法](sql/README.md)
+- [部署文档](cluster_deployment/README.md)
+- [运维文档](operation/README.md)
diff --git a/docs/src/sdk/README.md b/docs/src/cn/sdk/README.md
similarity index 86%
copy from docs/src/sdk/README.md
copy to docs/src/cn/sdk/README.md
index de44d3d..dcca00d 100644
--- a/docs/src/sdk/README.md
+++ b/docs/src/cn/sdk/README.md
@@ -1,4 +1,4 @@
-# SDK
+# SDK 文档
- [Rust](./rust.md)
- [Java](./java.md)
diff --git a/docs/src/cn/sdk/go.md b/docs/src/cn/sdk/go.md
new file mode 100644
index 0000000..6eb69c1
--- /dev/null
+++ b/docs/src/cn/sdk/go.md
@@ -0,0 +1,109 @@
+# Go
+
+## 安装
+
+```
+go get github.com/apache/incubator-horaedb-client-go
+```
+
+你可以在这里找到最新的版本 [here](https://github.com/apache/incubator-horaedb-client-go/tags).
+
+## 如何使用
+
+### 初始化客户端
+
+```go
+ client, err := horaedb.NewClient(endpoint, horaedb.Direct,
+ horaedb.WithDefaultDatabase("public"), // Client所使用的database
+ )
+```
+
+| 参数名称 | 说明 |
+| ----------------- | --------------------------------------------------------------------------------------------- |
+| defaultDatabase | 所使用的 database,可以被单个 `Write` 或者 `SQLRequest` 请求中的 database 覆盖 |
+| RPCMaxRecvMsgSize | grpc `MaxCallRecvMsgSize` 配置, 默认是 1024 _ 1024 _ 1024 |
+| RouteMaxCacheSize | 如果 router 客户端中的 路由缓存超过了这个值,将会淘汰最不活跃的直至降低这个阈值, 默认是 10000 |
+
+注意: HoraeDB 当前仅支持预创建的 `public` database , 未来会支持多个 database。
+
+### 管理表
+
+HoraeDB 使用 SQL 来管理表格,比如创建表、删除表或者新增列等等,这和你在使用 SQL 管理其他的数据库时没有太大的区别。
+
+为了方便使用,在使用 gRPC 的 write 接口进行写入时,如果某个表不存在,HoraeDB 会根据第一次的写入自动创建一个表。
+
+当然你也可以通过 `create table` 语句来更精细化的管理的表(比如添加索引等)。
+
+**创建表的样例**
+
+```go
+ createTableSQL := `
+ CREATE TABLE IF NOT EXISTS demo (
+ name string TAG,
+ value double,
+ t timestamp NOT NULL,
+ TIMESTAMP KEY(t)
+ ) ENGINE=Analytic with (enable_ttl=false)`
+
+ req := horaedb.SQLQueryRequest{
+ Tables: []string{"demo"},
+ SQL: createTableSQL,
+ }
+ resp, err := client.SQLQuery(context.Background(), req)
+```
+
+**删除表的样例**
+
+```go
+ dropTableSQL := `DROP TABLE demo`
+ req := horaedb.SQLQueryRequest{
+ Tables: []string{"demo"},
+ SQL: dropTableSQL,
+ }
+ resp, err := client.SQLQuery(context.Background(), req)
+```
+
+### 构建写入数据
+
+```go
+ points := make([]horaedb.Point, 0, 2)
+ for i := 0; i < 2; i++ {
+ point, err := horaedb.NewPointBuilder("demo").
+ SetTimestamp(now)).
+ AddTag("name", horaedb.NewStringValue("test_tag1")).
+ AddField("value", horaedb.NewDoubleValue(0.4242)).
+ Build()
+ if err != nil {
+ panic(err)
+ }
+ points = append(points, point)
+ }
+```
+
+### 写入数据
+
+```go
+ req := horaedb.WriteRequest{
+ Points: points,
+ }
+ resp, err := client.Write(context.Background(), req)
+```
+
+### 查询数据
+
+```go
+ querySQL := `SELECT * FROM demo`
+ req := horaedb.SQLQueryRequest{
+ Tables: []string{"demo"},
+ SQL: querySQL,
+ }
+ resp, err := client.SQLQuery(context.Background(), req)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Printf("query table success, rows:%+v\n", resp.Rows)
+```
+
+## 示例
+
+你可以在[这里](https://github.com/apache/incubator-horaedb-client-go/blob/main/examples/read_write.go)找到完整的示例。
diff --git a/docs/src/sdk/java.md b/docs/src/cn/sdk/java.md
similarity index 50%
copy from docs/src/sdk/java.md
copy to docs/src/cn/sdk/java.md
index d3fe0bc..c8941b5 100644
--- a/docs/src/sdk/java.md
+++ b/docs/src/cn/sdk/java.md
@@ -1,14 +1,14 @@
-# Java
+# Java 客户端使用文档
-## Introduction
+## 介绍
-HoraeDB Client is a high-performance Java client for HoraeDB.
+HoraeDBClient 是 HoraeDB 的高性能 Java 版客户端。
-## Requirements
+## 环境要求
-- Java 8 or later is required for compilation
+Java 8 及以上
-## Dependency
+## 依赖
```xml
<dependency>
@@ -18,19 +18,17 @@ HoraeDB Client is a high-performance Java client for HoraeDB.
</dependency>
```
-You can get latest version [here](https://github.com/apache/incubator-horaedb-client-java/blob/main/docs/CHANGELOG.md).
+最新的版本可以从[这里](https://github.com/apache/incubator-horaedb-client-java/blob/main/docs/CHANGELOG.md)获取。
-## Init HoraeDB Client
+## 初始化客户端
```java
-final CeresDBOptions opts = CeresDBOptions.newBuilder("127.0.0.1", 8831, DIRECT) // CeresDB default grpc port 8831,use DIRECT RouteMode
- .database("public") // use database for client, can be overridden by the RequestContext in request
- // maximum retry times when write fails
- // (only some error codes will be retried, such as the routing table failure)
- .writeMaxRetries(1)
- // maximum retry times when read fails
- // (only some error codes will be retried, such as the routing table failure)
- .readMaxRetries(1).build();
+// CeresDB options
+final CeresDBOptions opts = CeresDBOptions.newBuilder("127.0.0.1", 8831, DIRECT) // 默认 gprc 端口号,DIRECT 模式
+ .database("public") // Client所使用的database,可被RequestContext的database覆盖
+ .writeMaxRetries(1) // 写入失败重试次数上限(只有部分错误 code 才会重试,比如路由表失效)
+ .readMaxRetries(1) // 查询失败重试次数上限(只有部分错误 code 才会重试,比如路由表失效)
+ .build();
final CeresDBClient client = new CeresDBClient();
if (!client.init(opts)) {
@@ -38,35 +36,33 @@ if (!client.init(opts)) {
}
```
-The initialization requires at least three parameters:
+客户端初始化至少需要三个参数:
-- `Endpoint`: 127.0.0.1
-- `Port`: 8831
-- `RouteMode`: DIRECT/PROXY
+- EndPoint: 127.0.0.1
+- Port: 8831
+- RouteMode: DIRECT/PROXY
-Here is the explanation of `RouteMode`. There are two kinds of `RouteMode`,The `Direct` mode should be adopted to avoid forwarding overhead if all the servers are accessible to the client.
-However, the `Proxy` mode is the only choice if the access to the servers from the client must go through a gateway.
-For more configuration options, see [configuration](https://github.com/apache/incubator-horaedb-client-java/tree/main/docs/configuration.md)
+这里重点解释下 `RouteMode` 参数,`PROXY` 模式用在客户端和服务端存在网络隔离,请求需要经过转发的场景;`DIRECT` 模式用在客户端和服务端网络连通的场景,节省转发的开销,具有更高的性能。
+更多的参数配置详情见 [configuration](https://github.com/apache/incubator-horaedb-client-java/tree/main/docs/configuration.md)。
-Notice: HoraeDB currently only supports the default database `public` now, multiple databases will be supported in the future;
+注意: HoraeDB 当前仅支持默认的 `public` database , 未来会支持多个 database。
-## Create Table Example
+## 建表
-For ease of use, when using gRPC's write interface for writing, if a table does not exist, HoraeDB will automatically create a table based on the first write.
+为了方便使用,在使用 gRPC 的 write 接口进行写入时,如果某个表不存在,HoraeDB 会根据第一次的写入自动创建一个表。
-Of course, you can also use `create table` statement to manage the table more finely (such as adding indexes).
+当然你也可以通过 `create table` 语句来更精细化的管理的表(比如添加索引等)。
-The following table creation statement(using the SQL API included in SDK )shows all field types supported by HoraeDB:
+下面的建表语句(使用 SDK 的 SQL API)包含了 HoraeDB 支持的所有字段类型:
```java
-// Create table manually, creating table schema ahead of data ingestion is not required
String createTableSql = "CREATE TABLE IF NOT EXISTS machine_table(" +
"ts TIMESTAMP NOT NULL," +
"city STRING TAG NOT NULL," +
"ip STRING TAG NOT NULL," +
"cpu DOUBLE NULL," +
"mem DOUBLE NULL," +
- "TIMESTAMP KEY(ts)" + // timestamp column must be specified
+ "TIMESTAMP KEY(ts)" + // 建表时必须指定时间戳序列
") ENGINE=Analytic";
Result<SqlQueryOk, Err> createResult = client.sqlQuery(new SqlQueryRequest(createTableSql)).get();
@@ -75,9 +71,9 @@ if (!createResult.isOk()) {
}
```
-## Drop Table Example
+## 删表
-Here is an example of dropping table:
+下面是一个删表的示例:
```java
String dropTableSql = "DROP TABLE machine_table";
@@ -88,14 +84,14 @@ if (!dropResult.isOk()) {
}
```
-## Write Data Example
+## 数据写入
-Firstly, you can use `PointBuilder` to build HoraeDB points:
+首先我们需要构建数据,示例如下:
```java
List<Point> pointList = new LinkedList<>();
for (int i = 0; i < 100; i++) {
- // build one point
+ // 构建单个Point
final Point point = Point.newPointBuilder("machine_table")
.setTimestamp(t0)
.addTag("city", "Singapore")
@@ -107,30 +103,32 @@ for (int i = 0; i < 100; i++) {
}
```
-Then, you can use `write` interface to write data:
+然后使用 `write` 接口写入数据,示例如下:
```java
-final CompletableFuture<Result<WriteOk, Err>> wf = client.write(new WriteRequest(pointList));
-// here the `future.get` is just for demonstration, a better async programming practice would be using the CompletableFuture API
+final CompletableFuture<Result<WriteOk, Err>> wf = client.write(pointList);
+// 这里用 `future.get` 只是方便演示,推荐借助 CompletableFuture 强大的 API 实现异步编程
final Result<WriteOk, Err> writeResult = wf.get();
- Assert.assertTrue(writeResult.isOk());
- // `Result` class referenced the Rust language practice, provides rich functions (such as mapXXX, andThen) transforming the result value to improve programming efficiency. You can refer to the API docs for detail usage.
- Assert.assertEquals(3, writeResult.getOk().getSuccess());
- Assert.assertEquals(3, writeResult.mapOr(0, WriteOk::getSuccess).intValue());
- Assert.assertEquals(0, writeResult.mapOr(-1, WriteOk::getFailed).intValue());
+
+Assert.assertTrue(writeResult.isOk());
+Assert.assertEquals(3, writeResult.getOk().getSuccess());
+// `Result` 类参考了 Rust 语言,提供了丰富的 mapXXX、andThen 类 function 方便对结果值进行转换,提高编程效率,欢迎参考 API 文档使用
+Assert.assertEquals(3, writeResult.mapOr(0, WriteOk::getSuccess).intValue());
+Assert.assertEquals(0, writeResult.getOk().getFailed());
+Assert.assertEquals(0, writeResult.mapOr(-1, WriteOk::getFailed).intValue());
```
-See [write](https://github.com/apache/incubator-horaedb-client-java/tree/main/docs/write.md)
+详情见 [write](https://github.com/apache/incubator-horaedb-client-java/tree/main/docs/write.md)
-## Query Data Example
+## 数据查询
```java
final SqlQueryRequest queryRequest = SqlQueryRequest.newBuilder()
- .forTables("machine_table") // table name is optional. If not provided, SQL parser will parse the `ssql` to get the table name and do the routing automaticly
+ .forTables("machine_table") // 这里表名是可选的,如果未提供,SDK将自动解析SQL填充表名并自动路由
.sql("select * from machine_table where ts = %d", t0) //
.build();
final CompletableFuture<Result<SqlQueryOk, Err>> qf = client.sqlQuery(queryRequest);
-// here the `future.get` is just for demonstration, a better async programming practice would be using the CompletableFuture API
+// 这里用 `future.get` 只是方便演示,推荐借助 CompletableFuture 强大的 API 实现异步编程
final Result<SqlQueryOk, Err> queryResult = qf.get();
Assert.assertTrue(queryResult.isOk());
@@ -138,7 +136,7 @@ Assert.assertTrue(queryResult.isOk());
final SqlQueryOk queryOk = queryResult.getOk();
Assert.assertEquals(1, queryOk.getRowCount());
-// get rows as list
+// 直接获取结果数组
final List<Row> rows = queryOk.getRowList();
Assert.assertEquals(t0, rows.get(0).getColumn("ts").getValue().getTimestamp());
Assert.assertEquals("Singapore", rows.get(0).getColumn("city").getValue().getString());
@@ -146,16 +144,16 @@ Assert.assertEquals("10.0.0.1", rows.get(0).getColumn("ip").getValue().getString
Assert.assertEquals(0.23, rows.get(0).getColumn("cpu").getValue().getDouble(), 0.0000001);
Assert.assertEquals(0.55, rows.get(0).getColumn("mem").getValue().getDouble(), 0.0000001);
-// get rows as stream
+// 获取结果流
final Stream<Row> rowStream = queryOk.stream();
rowStream.forEach(row -> System.out.println(row.toString()));
```
-See [read](https://github.com/apache/incubator-horaedb-client-java/tree/main/docs/read.md)
+详情见 [read](https://github.com/apache/incubator-horaedb-client-java/tree/main/docs/read.md)
-## Stream Write/Read Example
+## 流式读写
-HoraeDB support streaming writing and reading,suitable for large-scale data reading and writing。
+HoraeDB 支持流式读写,适用于大规模数据读写。
```java
long start = System.currentTimeMillis();
@@ -182,4 +180,4 @@ Assert.assertTrue(streamQueryResult.isOk());
Assert.assertEquals(1000, streamQueryResult.getOk().getRowCount());
```
-See [streaming](https://github.com/apache/incubator-horaedb-client-java/tree/main/docs/streaming.md)
+详情见 [streaming](https://github.com/apache/incubator-horaedb-client-java/tree/main/docs/streaming.md)
diff --git a/docs/src/cn/sdk/python.md b/docs/src/cn/sdk/python.md
new file mode 100644
index 0000000..7094d2c
--- /dev/null
+++ b/docs/src/cn/sdk/python.md
@@ -0,0 +1,156 @@
+# Python
+
+## 介绍
+
+[horaedb-client](https://pypi.org/project/ceresdb-client/) 是 [HoraeDB](https://github.com/apache/incubator-horaedb) python 客户端.
+
+借助于 [PyO3](https://github.com/PyO3),python 客户端的实现实际上是基于 [rust 客户端](https://github.com/apache/incubator-horaedb-client-rs) 的封装。
+
+本手册将会介绍 python client 的一些基本用法,其中涉及到的完整示例,可以查看[该示例代码](https://github.com/apache/incubator-horaedb-client-py/blob/main/examples/read_write.py).
+
+## 环境要求
+
+- Python >= 3.7
+
+## 安装
+
+```bash
+pip install ceresdb-client
+```
+
+你可以在这里找到最新的版本 [here](https://github.com/apache/incubator-horaedb-client-py/tags).
+
+## 初始化客户端
+
+首先介绍下如何初始化客户端,代码示例如下:
+
+```python
+import asyncio
+import datetime
+from ceresdb_client import Builder, RpcContext, PointBuilder, ValueBuilder, WriteRequest, SqlQueryRequest, Mode, RpcConfig
+
+rpc_config = RpcConfig()
+rpc_config = RpcConfig()
+rpc_config.thread_num = 1
+rpc_config.default_write_timeout_ms = 1000
+
+builder = Builder('127.0.0.1:8831', Mode.Direct)
+builder.set_rpc_config(rpc_config)
+builder.set_default_database('public')
+client = builder.build()
+```
+
+代码的最开始部分是依赖库的导入,在后面的示例中将省略这部分。
+
+客户端初始化需要至少两个参数:
+
+- `Endpoint`: 服务端地址,由 ip 和端口组成,例如 `127.0.0.1:8831`;
+- `Mode`: 客户端和服务端通信模式,有两种模式可供选择: `Direct` 和 `Proxy`。
+
+这里重点介绍下通信模式 `Mode`, 当客户端可以访问所有的服务器的时候,建议采用 `Direct` 模式,以减少转发开销;但是如果客户端访问服务器必须要经过网关,那么只能选择 `Proxy` 模式。
+
+至于 `default_database`,会在执行 RPC 请求时未通过 `RpcContext` 设置 database 的情况下,将被作为目标 database 使用。
+
+最后,通过配置 `RpcConfig`, 可以管理客户端使用的资源和调整其性能,所有的配置参数可以参考[这里](https://github.com/apache/incubator-horaedb-client-py/blob/main/ceresdb_client.pyi).
+
+## 建表
+
+为了方便使用,在使用 gRPC 的 write 接口进行写入时,如果某个表不存在,HoraeDB 会根据第一次的写入自动创建一个表。
+
+当然你也可以通过 `create table` 语句来更精细化的管理的表(比如添加索引等)。
+
+初始化客户端后,建表示例如下:
+
+```python
+def async_query(client, ctx, req):
+ await client.sql_query(ctx, req)
+
+create_table_sql = 'CREATE TABLE IF NOT EXISTS demo ( \
+ name string TAG, \
+ value double, \
+ t timestamp NOT NULL, \
+ TIMESTAMP KEY(t)) ENGINE=Analytic with (enable_ttl=false)'
+
+req = SqlQueryRequest(['demo'], create_table_sql)
+rpc_ctx = RpcContext()
+rpc_ctx.database = 'public'
+rpc_ctx.timeout_ms = 100
+
+event_loop = asyncio.get_event_loop()
+event_loop.run_until_complete(async_query(client, rpc_ctx, req))
+```
+
+`RpcContext` 可以用来指定目标 database (可以覆盖在初始化的时候设置的 default_space) 和超时参数。
+
+## 数据写入
+
+可以使用 `PointBuilder` 来构建一个 point(实际上就是数据集的一行),多个 point 构成一个写入请求。
+
+示例如下:
+
+```python
+async def async_write(client, ctx, req):
+ return await client.write(ctx, req)
+
+point_builder = PointBuilder('demo')
+point_builder.set_timestamp(1000 * int(round(datetime.datetime.now().timestamp())))
+point_builder.set_tag("name", ValueBuilder().string("test_tag1"))
+point_builder.set_field("value", ValueBuilder().double(0.4242))
+point = point_builder.build()
+
+write_request = WriteRequest()
+write_request.add_point(point)
+
+event_loop = asyncio.get_event_loop()
+event_loop.run_until_complete(async_write(client, ctx, req))
+```
+
+## 数据查询
+
+通过 `sql_query` 接口, 可以方便地从服务端查询数据:
+
+```
+req = SqlQueryRequest(['demo'], 'select * from demo')
+event_loop = asyncio.get_event_loop()
+resp = event_loop.run_until_complete(async_query(client, ctx, req))
+```
+
+如示例所展示, 构建 `SqlQueryRequest` 需要两个参数:
+
+- 查询 sql 中涉及到的表;
+- 查询 sql.
+
+当前为了查询的性能,第一个参数是必须的。
+
+查询到数据后,逐行逐列处理数据的示例如下:
+
+```python
+# Access row by index in the resp.
+for row_idx in range(0, resp.num_rows()):
+ row_tokens = []
+ row = resp.row_by_idx(row_idx)
+ for col_idx in range(0, row.num_cols()):
+ col = row.column_by_idx(col_idx)
+ row_tokens.append(f"{col.name()}:{col.value()}#{col.data_type()}")
+ print(f"row#{row_idx}: {','.join(row_tokens)}")
+
+# Access row by iter in the resp.
+for row in resp.iter_rows():
+ row_tokens = []
+ for col in row.iter_columns():
+ row_tokens.append(f"{col.name()}:{col.value()}#{col.data_type()}")
+ print(f"row: {','.join(row_tokens)}")
+```
+
+## 删表
+
+和创建表类似,我们可以使用 sql 来删除表:
+
+```python
+drop_table_sql = 'DROP TABLE demo'
+
+req = SqlQueryRequest(['demo'], drop_table_sql)
+
+event_loop = asyncio.get_event_loop()
+event_loop.run_until_complete(async_query(client, rpc_ctx, req))
+```
diff --git a/docs/src/sdk/rust.md b/docs/src/cn/sdk/rust.md
similarity index 52%
copy from docs/src/sdk/rust.md
copy to docs/src/cn/sdk/rust.md
index 48358ab..91089a6 100644
--- a/docs/src/sdk/rust.md
+++ b/docs/src/cn/sdk/rust.md
@@ -1,26 +1,26 @@
# Rust
-## Install
+## 安装
```bash
cargo add ceresdb-client
```
-You can get latest version [here](https://github.com/apache/incubator-horaedb-client-rs/tags).
+你可以在这里找到最新的版本 [here](https://github.com/apache/incubator-horaedb-client-rs/tags).
-## Init Client
+## 初始化客户端
-At first, we need to init the client.
+首先,我们需要初始化客户端。
-- New builder for the client, and you must set `endpoint` and `mode`:
- - `endpoint` is a string which is usually like "ip/domain_name:port".
- - `mode` is used to define the way to access horaedb server, [detail about mode](https://github.com/apache/incubator-horaedb-client-rs/blob/main/src/db_client/builder.rs#L20).
+- 创建客户端的 builder,你必须设置 `endpoint` 和 `mode`:
+ - `endpoint` 是类似 "ip/domain_name:port" 形式的字符串。
+ - `mode` 用于指定访问 HoraeDB 服务器的方式,[关于 mode 的详细信息](https://github.com/apache/incubator-horaedb-client-rs/blob/main/src/db_client/builder.rs#L20)。
```rust
let mut builder = Builder::new("ip/domain_name:port", Mode::Direct/Mode::Proxy);
```
-- New and set `rpc_config`, it can be defined on demand or just use the default value, [detail about rpc config](https://github.com/apache/incubator-horaedb-client-rs/blob/main/src/options.rs):
+- 创建和设置 `rpc_config`,可以按需进行定义或者直接使用默认值,更多详细参数请参考[这里](https://github.com/apache/incubator-horaedb-client-rs/blob/main/src/options.rs):
```rust
let rpc_config = RpcConfig {
@@ -31,27 +31,25 @@ let rpc_config = RpcConfig {
let builder = builder.rpc_config(rpc_config);
```
-- Set `default_database`, it will be used if following rpc calling without setting the database in the `RpcContext`(will be introduced in later):
+- 设置 `default_database`,这会在执行 RPC 请求时未通过 RpcContext 设置 database 的情况下,将被作为目标 database 使用。
```rust
let builder = builder.default_database("public");
```
-- Finally, we build client from builder:
+- 最后,我们从 builder 中创建客户端:
```rust
let client = builder.build();
```
-## Manage Table
+## 管理表
-For ease of use, when using gRPC's write interface for writing, if a table does not exist, HoraeDB will automatically create a table based on the first write.
+为了方便使用,在使用 gRPC 的 write 接口进行写入时,如果某个表不存在,HoraeDB 会根据第一次的写入自动创建一个表。
-Of course, you can also use `create table` statement to manage the table more finely (such as adding indexes).
+当然你也可以通过 `create table` 语句来更精细化的管理的表(比如添加索引等)。
-You can use the sql query interface to create or drop table, related setting will be introduced in `sql query` section.
-
-- Create table:
+- 建表:
```rust
let create_table_sql = r#"CREATE TABLE IF NOT EXISTS horaedb (
@@ -75,7 +73,7 @@ let resp = client
.expect("Should succeed to create table");
```
-- Drop table:
+- 删表:
```rust
let drop_table_sql = "DROP TABLE horaedb";
@@ -90,11 +88,11 @@ let resp = client
.expect("Should succeed to create table");
```
-## Write
+## 写入数据
-We support to write with the time series data model like [InfluxDB](https://awesome.influxdata.com/docs/part-2/influxdb-data-model/).
+我们支持使用类似 [InfluxDB](https://awesome.influxdata.com/docs/part-2/influxdb-data-model) 的时序数据模型进行写入。
-- Build the `point` first by `PointBuilder`, the related data structure of `tag value` and `field value` in it is defined as `Value`, [detail about Value](https://github.com/apache/incubator-horaedb-client-rs/blob/main/src/model/value.rs):
+- 利用 `PointBuilder` 创建 `point`,`tag value` 和 `field value` 的相关数据结构为 `Value`,[`Value` 的详细信息](detail about Value](https://github.com/apache/incubator-horaedb-client-rs/blob/main/src/model/value.rs:
```rust
let test_table = "horaedb";
@@ -120,16 +118,23 @@ let point = PointBuilder::new(test_table.to_string())
.unwrap();
```
-- Add the `point` to `write request`:
+- 将 `point` 添加到 `write request` 中:
```rust
let mut write_req = WriteRequest::default();
write_req.add_point(point);
```
-- New `rpc_ctx`, and it can also be defined on demand or just use the default value, [detail about rpc ctx](https://github.com/apache/incubator-horaedb-client-rs/blob/a72e673103463c7962e01a097592fc7edbcc0b79/src/rpc_client/mod.rs#L29):
+- 创建 `rpc_ctx`,同样地可以按需设置或者使用默认值,`rpc_ctx` 的详细信息请参考[这里](https://github.com/apache/incubator-horaedb-client-rs/blob/main/src/rpc_client/mod.rs#L23):
+
+```rust
+let rpc_ctx = RpcContext {
+ database: Some("public".to_string()),
+ ..Default::default()
+};
+```
-- Finally, write to server by client.
+- 最后,利用客户端写入数据到服务器:
```rust
let rpc_ctx = RpcContext {
@@ -139,11 +144,11 @@ let rpc_ctx = RpcContext {
let resp = client.write(rpc_ctx, &write_req).await.expect("Should success to write");
```
-## Sql Query
+## Sql query
-We support to query data with sql.
+我们支持使用 sql 进行数据查询。
-- Define related tables and sql in `sql query request`:
+- 在 `sql query request` 中指定相关的表和 sql 语句:
```rust
let req = SqlQueryRequest {
@@ -152,12 +157,12 @@ let req = SqlQueryRequest {
};
```
-- Query by client:
+- 利用客户端进行查询:
```rust
let resp = client.sql_query(rpc_ctx, &req).await.expect("Should success to write");
```
-## Example
+## 示例
-You can find the [complete example](https://github.com/apache/incubator-horaedb-client-rs/blob/main/examples/read_write.rs) in the project.
+你可以在本项目的仓库中找到[完整的例子](https://github.com/apache/incubator-horaedb-client-rs/blob/main/examples/read_write.rs)。
diff --git a/docs/src/cn/sql/README.md b/docs/src/cn/sql/README.md
new file mode 100644
index 0000000..e6ff7a0
--- /dev/null
+++ b/docs/src/cn/sql/README.md
@@ -0,0 +1,19 @@
+# SQL 语法
+
+本章介绍 HoraeDB 的 SQL 使用语法。
+
+- [数据模型](model/README.md)
+ - [数据类型](model/data_types.md)
+ - [特殊字段](model/special_columns.md)
+- [标识符](identifier.md)
+- [表结构操作](ddl/README.md)
+ - [建表](ddl/create_table.md)
+ - [表结构变更](ddl/alter_table.md)
+ - [删除表](ddl/drop_table.md)
+- [数据操作](dml/README.md)
+ - [数据写入](dml/insert.md)
+ - [数据查询](dml/select.md)
+- [引擎配置](engine_options.md)
+- [常见 SQL](utility.md)
+- [标量函数](functions/scalar_functions.md)
+- [聚合函数](functions/aggregate_functions.md)
diff --git a/docs/src/cn/sql/ddl/README.md b/docs/src/cn/sql/ddl/README.md
new file mode 100644
index 0000000..a1eb8ee
--- /dev/null
+++ b/docs/src/cn/sql/ddl/README.md
@@ -0,0 +1,7 @@
+# 表结构操作
+
+本章介绍表结构相关 SQL 语句:
+
+- [建表](create_table.md)
+- [表结构变更](alter_table.md)
+- [删除表](drop_table.md)
diff --git a/docs/src/sql/ddl/alter_table.md b/docs/src/cn/sql/ddl/alter_table.md
similarity index 72%
copy from docs/src/sql/ddl/alter_table.md
copy to docs/src/cn/sql/ddl/alter_table.md
index 041d593..7362871 100644
--- a/docs/src/sql/ddl/alter_table.md
+++ b/docs/src/cn/sql/ddl/alter_table.md
@@ -1,10 +1,10 @@
# ALTER TABLE
-`ALTER TABLE` can change the schema or options of a table.
+使用 `ALTER TABLE` 可以改变表的结构和参数 .
-## ALTER TABLE SCHEMA
+## 变更表结构
-HoraeDB current supports `ADD COLUMN` to alter table schema.
+例如可以使用 `ADD COLUMN` 增加表的列 :
```sql
-- create a table and add a column to it
@@ -12,7 +12,7 @@ CREATE TABLE `t`(a int, t timestamp NOT NULL, TIMESTAMP KEY(t)) ENGINE = Analyti
ALTER TABLE `t` ADD COLUMN (b string);
```
-It now becomes:
+变更后的表结构如下:
```
-- DESCRIBE TABLE `t`;
@@ -25,9 +25,9 @@ a int false true false
b string false true false
```
-## ALTER TABLE OPTIONS
+## 变更表参数
-HoraeDB current supports `MODIFY SETTING` to alter table schema.
+例如可以使用 `MODIFY SETTING` 修改表的参数 :
```sql
-- create a table and add a column to it
@@ -35,13 +35,13 @@ CREATE TABLE `t`(a int, t timestamp NOT NULL, TIMESTAMP KEY(t)) ENGINE = Analyti
ALTER TABLE `t` MODIFY SETTING write_buffer_size='300M';
```
-The SQL above tries to modify the `write_buffer_size` of the table, and the table's option becomes:
+上面的 SQL 用来更改 `writer_buffer` 大小,变更后的建表如下:
```sql
CREATE TABLE `t` (`tsid` uint64 NOT NULL, `t` timestamp NOT NULL, `a` int, PRIMARY KEY(tsid,t), TIMESTAMP KEY(t)) ENGINE=Analytic WITH(arena_block_size='2097152', compaction_strategy='default', compression='ZSTD', enable_ttl='true', num_rows_per_row_group='8192', segment_duration='', storage_format='AUTO', ttl='7d', update_mode='OVERWRITE', write_buffer_size='314572800')
```
-Besides, the `ttl` can be altered from 7 days to 10 days by such SQL:
+除此之外,我们可以修改其 `ttl` 为 10 天:
```sql
ALTER TABLE `t` MODIFY SETTING ttl='10d';
diff --git a/docs/src/cn/sql/ddl/create_table.md b/docs/src/cn/sql/ddl/create_table.md
new file mode 100644
index 0000000..f0cff10
--- /dev/null
+++ b/docs/src/cn/sql/ddl/create_table.md
@@ -0,0 +1,80 @@
+# 创建表
+
+## 基础语法
+
+建表的基础语法如下 ( `[]` 之间的内容是可选部分):
+
+```sql
+CREATE TABLE [IF NOT EXISTS]
+ table_name ( column_definitions )
+ ENGINE = engine_type
+ [WITH ( table_options )];
+```
+
+列定义的语法 :
+
+```sql
+column_name column_type [[NOT] NULL] {[TAG] | [TIMESTAMP KEY] | [PRIMARY KEY]} [DICTIONARY] [COMMENT '']
+```
+
+表选项的语法是键-值对,值用单引号(`'`)来引用。例如:
+
+```sql
+... WITH ( enable_ttl='false' )
+```
+
+## IF NOT EXISTS
+
+添加 `IF NOT EXISTS` 时,HoraeDB 在表名已经存在时会忽略建表错误。
+
+## 定义列
+
+一个列的定义至少应该包含名称和类型部分,支持的类型见 [这里](../model/data_types.md)。
+
+列默认为可空,即 "NULL " 关键字是隐含的;添加 `NOT NULL` 时列不可为空。
+
+```sql
+-- this definition
+a_nullable int
+-- equals to
+a_nullable int NULL
+
+-- add NOT NULL to make it required
+b_not_null NOT NULL
+```
+
+定义列时可以使用相关的关键字将列标记为 [特殊列](../model/special_columns.md)。
+
+对于 string 的 tag 列,推荐设置为字典类型来减少内存占用:
+
+```sql
+`tag1` string TAG DICTIONARY
+```
+
+## 引擎设置
+
+HoraeDB 支持指定某个表使用哪种引擎,目前支持的引擎类型为 `Analytic`。注意这个属性设置后不可更改。
+
+## 分区设置
+
+> 仅适用于集群部署模式
+
+```
+CREATE TABLE ... PARTITION BY KEY
+```
+
+下面这个例子创建了一个具有 8 个分区的表,分区键为 `name`:
+
+```sql
+CREATE TABLE `demo` (
+ `name` string TAG COMMENT 'client username',
+ `value` double NOT NULL,
+ `t` timestamp NOT NULL,
+ timestamp KEY (t)
+)
+ PARTITION BY KEY(name) PARTITIONS 8
+ ENGINE=Analytic
+ with (
+ enable_ttl='false'
+)
+```
diff --git a/docs/src/cn/sql/ddl/drop_table.md b/docs/src/cn/sql/ddl/drop_table.md
new file mode 100644
index 0000000..846e55a
--- /dev/null
+++ b/docs/src/cn/sql/ddl/drop_table.md
@@ -0,0 +1,11 @@
+# DROP TABLE
+
+## 基础语法
+
+删除表的基础语法如下:
+
+```sql
+DROP TABLE [IF EXISTS] table_name
+```
+
+`Drop Table` 用来删除一个表,请谨慎使用这个语句,因为会同时删除表的定义和表的数据,并且无法恢复。
diff --git a/docs/src/cn/sql/dml/README.md b/docs/src/cn/sql/dml/README.md
new file mode 100644
index 0000000..33303da
--- /dev/null
+++ b/docs/src/cn/sql/dml/README.md
@@ -0,0 +1,6 @@
+# 数据操作
+
+本章介绍数据操作相关的 SQL.
+
+- [数据写入](./insert.md)
+- [数据查询](./select.md)
diff --git a/docs/src/cn/sql/dml/insert.md b/docs/src/cn/sql/dml/insert.md
new file mode 100644
index 0000000..12acb6f
--- /dev/null
+++ b/docs/src/cn/sql/dml/insert.md
@@ -0,0 +1,17 @@
+# INSERT
+
+## 基础语法
+
+写入数据的基础语法如下:
+
+```sql
+INSERT [INTO] tbl_name
+ [(col_name [, col_name] ...)]
+ { {VALUES | VALUE} (value_list) [, (value_list)] ... }
+```
+
+写入一行数据的示例如下:
+
+```sql
+INSERT INTO demo(`timestamp`, tag1) VALUES(1667374200022, 'horaedb')
+```
diff --git a/docs/src/sql/dml/select.md b/docs/src/cn/sql/dml/select.md
similarity index 72%
copy from docs/src/sql/dml/select.md
copy to docs/src/cn/sql/dml/select.md
index ecf0873..f2d5ce3 100644
--- a/docs/src/sql/dml/select.md
+++ b/docs/src/cn/sql/dml/select.md
@@ -1,8 +1,8 @@
# SELECT
-## Basic syntax
+## 基础语法
-Basic syntax (parts between `[]` are optional):
+数据查询的基础语法如下:
```sql
SELECT select_expr [, select_expr] ...
@@ -14,7 +14,7 @@ SELECT select_expr [, select_expr] ...
[LIMIT [offset,] row_count ]
```
-`Select` syntax in HoraeDB is similar to mysql, here is an example:
+数据查询的语法和 mysql 类似,示例如下:
```sql
SELECT * FROM `demo` WHERE time_stamp > '2022-10-11 00:00:00' AND time_stamp < '2022-10-12 00:00:00' LIMIT 10
diff --git a/docs/src/cn/sql/engine_options.md b/docs/src/cn/sql/engine_options.md
new file mode 100644
index 0000000..6949081
--- /dev/null
+++ b/docs/src/cn/sql/engine_options.md
@@ -0,0 +1,92 @@
+# 配置项
+
+建表时可以使用下列的选项配置引擎:
+
+- `enable_ttl`:布尔类型,默认为 `true`,当一个表配置 TTL 时,早于 `ttl` 的数据不会被查询到并且会被删除。
+- `ttl`:`duration` 类型,默认值为`7d`,此项定义数据的生命周期,只在 `enable_ttl` 为 `true` 的情况下使用。
+- `storage_format`: `string` 类型,数据存储的格式,有两种可选:
+
+ - `columnar`, 默认值
+ - `hybrid`, 注意:此功能仍在开发中,将来可能会发生变化。
+
+上述两种存储格式详见 [存储格式](#存储格式) 部分。
+
+## 存储格式
+
+HoraeDB 支持两种存储格式,一个是 `columnar`, 这是传统的列式格式,一个物理列中存储表的一个列。
+
+```plaintext
+| Timestamp | Device ID | Status Code | Tag 1 | Tag 2 |
+| --------- |---------- | ----------- | ----- | ----- |
+| 12:01 | A | 0 | v1 | v1 |
+| 12:01 | B | 0 | v2 | v2 |
+| 12:02 | A | 0 | v1 | v1 |
+| 12:02 | B | 1 | v2 | v2 |
+| 12:03 | A | 0 | v1 | v1 |
+| 12:03 | B | 0 | v2 | v2 |
+| ..... | | | | |
+```
+
+另一个是 `hybrid`, 当前还在实验阶段的存储格式,用于在列式存储中模拟面向行的存储,以加速经典的时序查询。
+
+在经典的时序场景中,如 IoT 或 DevOps,查询通常会先按系列 ID(或设备 ID)分组,然后再按时间戳分组。
+为了在这些场景中实现良好的性能,数据的物理布局应该与这种风格相匹配, `hybrid` 格式就是这样提出的。
+
+```plaintext
+ | Device ID | Timestamp | Status Code | Tag 1 | Tag 2 | minTime | maxTime |
+ |-----------|---------------------|-------------|-------|-------|---------|---------|
+ | A | [12:01,12:02,12:03] | [0,0,0] | v1 | v1 | 12:01 | 12:03 |
+ | B | [12:01,12:02,12:03] | [0,1,0] | v2 | v2 | 12:01 | 12:03 |
+ | ... | | | | | | |
+```
+
+- 在一个文件中,同一个主键(例如设备 ID)的数据会被压缩到一行。
+- 除了主键之外的列被分成两类:
+ - `collapsible`, 这些列会被压缩成一个 list,常用于时序表中的`field`字段。
+ - 注意: 当前仅支持定长的字段
+ - `non-collapsible`, 这些列只能包含一个去重值,常用于时序表中的`tag`字段。
+ - 注意: 当前仅支持字符串类型
+- 另外多加了两个字段,`minTime` 和 `maxTime`, 用于查询中过滤不必要的数据。
+ - 注意: 暂未实现此能力
+
+### 示例
+
+```sql
+CREATE TABLE `device` (
+ `ts` timestamp NOT NULL,
+ `tag1` string tag,
+ `tag2` string tag,
+ `value1` double,
+ `value2` int,
+ timestamp KEY (ts)) ENGINE=Analytic
+ with (
+ enable_ttl = 'false',
+ storage_format = 'hybrid'
+);
+```
+
+这段语句会创建一个混合存储格式的表, 这种情况下用户可以通过 [parquet-tools](https://formulae.brew.sh/formula/parquet-tools)查看数据格式.
+上面定义的表的 parquet 结构如下所示:
+
+```
+message arrow_schema {
+ optional group ts (LIST) {
+ repeated group list {
+ optional int64 item (TIMESTAMP(MILLIS,false));
+ }
+ }
+ required int64 tsid (INTEGER(64,false));
+ optional binary tag1 (STRING);
+ optional binary tag2 (STRING);
+ optional group value1 (LIST) {
+ repeated group list {
+ optional double item;
+ }
+ }
+ optional group value2 (LIST) {
+ repeated group list {
+ optional int32 item;
+ }
+ }
+}
+```
diff --git a/docs/src/cn/sql/functions/aggregate_functions.md b/docs/src/cn/sql/functions/aggregate_functions.md
new file mode 100644
index 0000000..bb9e255
--- /dev/null
+++ b/docs/src/cn/sql/functions/aggregate_functions.md
@@ -0,0 +1,35 @@
+# 聚合函数
+
+HoraeDB SQL 基于 [DataFusion](https://github.com/apache/arrow-datafusion) 实现,支持的聚合函数如下。更多详情请参考: [Datafusion](https://github.com/apache/arrow-datafusion/blob/master/docs/source/user-guide/sql/aggregate_functions.md)
+
+## 常用
+
+| 函数 | 描述 |
+| --------- | ------------------ |
+| min | 最小值 |
+| max | 最大值 |
+| count | 求行数 |
+| avg | 平均值 |
+| sum | 求和 |
+| array_agg | 把数据放到一个数组 |
+
+## 统计
+
+| 函数 | 描述 |
+| -------------------- | ---------------------- |
+| var / var_samp | 返回给定列的样本方差 |
+| var_pop | 返回给定列的总体方差 |
+| stddev / stddev_samp | 返回给定列的样本标准差 |
+| stddev_pop | 返回给定列的总体标准差 |
+| covar / covar_samp | 返回给定列的样本协方差 |
+| covar_pop | 返回给定列的总体协方差 |
+| corr | 返回给定列的相关系数 |
+
+## 估值函数
+
+| 函数 | 描述 |
+| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
+| approx_distinct | 返回输入值的近似去重数量(HyperLogLog) |
+| approx_median | 返回输入值的近似中位数,它是 approx_percentile_cont(x, 0.5) 的简单写法 |
+| approx_percentile_cont | 返回输入值的近似百分位数(TDigest),其中 p 是 0 和 1(包括)之间的 float64,等同于 approx_percentile_cont_with_weight(x, 1, p) |
+| approx_percentile_cont_with_weight | 返回输入值带权重的近似百分位数(TDigest),其中 w 是权重列表达式,p 是 0 和 1(包括)之间的 float64 |
diff --git a/docs/src/cn/sql/functions/scalar_functions.md b/docs/src/cn/sql/functions/scalar_functions.md
new file mode 100644
index 0000000..21adb08
--- /dev/null
+++ b/docs/src/cn/sql/functions/scalar_functions.md
@@ -0,0 +1,105 @@
+# 标量函数
+
+HoraeDB SQL 基于 [DataFusion](https://github.com/CeresDB/arrow-datafusion) 实现,支持的标量函数如下。更多详情请参考: [Datafusion](https://github.com/CeresDB/arrow-datafusion/blob/master/docs/source/user-guide/sql/scalar_functions.md)
+
+## 数值函数
+
+| 函数 | 描述 |
+| --------------------- | ---------------------------- |
+| abs(x) | 绝对值 |
+| acos(x) | 反余弦 |
+| asin(x) | 反正弦 |
+| atan(x) | 反正切 |
+| atan2(y, x) | y/x 的反正切 |
+| ceil(x) | 小于或等于参数的最接近整数 |
+| cos(x) | 余弦 |
+| exp(x) | 指数 |
+| floor(x) | 大于或等于参数的最接近整数 |
+| ln(x) | 自然对数 |
+| log10(x) | 以 10 为底的对数 |
+| log2(x) | 以 2 为底的对数 |
+| power(base, exponent) | 幂函数 |
+| round(x) | 四舍五入 |
+| signum(x) | 根据参数的正负返回 -1、0、+1 |
+| sin(x) | 正弦 |
+| sqrt(x) | 平方根 |
+| tan(x) | 正切 |
+| trunc(x) | 截断计算,取整(向零取整) |
+
+## 条件函数
+
+| 函数 | 描述 |
+| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| coalesce | 如果它的参数中有一个不为 null,则返回第一个参数,如果所有参数均为 null,则返回 null。当从数据库中检索数据用于显示时,它经常用于用默认值替换 null 值。 |
+| nullif | 如果 value1 等于 value2,则返回 null 值;否则返回 value1。这可用于执行与 coalesce 表达式相反的操作 |
+
+## 字符函数
+
+| 函数 | 描述 |
+| ---------------- | --------------------------------------------------------------------------------------------------------------------------- |
+| ascii | 返回参数的第一个字符的 ascii 数字编码。在 UTF8 编码下,返回字符的 Unicode 码点。在其他多字节编码中,参数必须是 ASCII 字符。 |
+| bit_length | 返回字符串的比特位数。 |
+| btrim | 从字符串的开头和结尾删除给定字符串中的字符组成的最长字符串 |
+| char_length | 等效于 length。 |
+| character_length | 等效于 length。 |
+| concat | 将两个或多个字符串合并为一个字符串。 |
+| concat_ws | 使用给定的分隔符组合两个值。 |
+| chr | 根据数字码返回字符。 |
+| initcap | 将字符串中每个单词的首字母大写。 |
+| left | 返回字符串的指定最左边字符。 |
+| length | 返回字符串中字符的数量。 |
+| lower | 将字符串中的所有字符转换为它们的小写。 |
+| lpad | 使用特定字符集将字符串左填充到给定长度。 |
+| ltrim | 从字符串的开头删除由字符中的字符组成的最长字符串(默认为空格)。 |
+| md5 | 计算给定字符串的 MD5 散列值。 |
+| octet_length | 等效于 length。 |
+| repeat | 返回一个由输入字符串重复指定次数组成的字符串。 |
+| replace | 替换字符串中所有子字符串的出现为新子字符串。 |
+| reverse | 反转字符串。 |
+| right | 返回字符串的指定最右边字符。 |
+| rpad | 使用特定字符集将字符串右填充到给定长度。 |
+| rtrim | 从字符串的结尾删除包含 characters 中任何字符的最长字符串。 |
+| digest | 计算给定字符串的散列值。 |
+| split_part | 按指定分隔符拆分字符串,并从结果数组中返回 |
+| starts_with | 检查字符串是否以给定字符串开始 |
+| strpos | 搜索字符串是否包含一个给定的字符串,并返回位置 |
+| substr | 提取子字符串 |
+| translate | 把字符串翻译成另一种字符集 Translates one set of characters into another. |
+| trim | 移除字符串两侧的空白字符或其他指定字符。 |
+| upper | 将字符串中的所有字符转换为它们的大写。 |
+
+## 正则函数
+
+| 函数 | 描述 |
+| -------------- | -------------------------------------- |
+| regexp_match | 判断一个字符串是否匹配正则表达式 |
+| regexp_replace | 使用新字符串替换正则匹配的字符串中内容 |
+
+## 时间函数
+
+| 函数 | 描述 |
+| -------------------- | ----------------------------------------------------- |
+| to_timestamp | 将字符串转换为 Timestamp(Nanoseconds,None)类型。 |
+| to_timestamp_millis | 将字符串转换为 Timestamp(Milliseconds,None)类型。 |
+| to_timestamp_micros | 将字符串转换为 Timestamp(Microseconds,None)类型。 |
+| to_timestamp_seconds | 将字符串转换为 Timestamp(Seconds,None)类型。 |
+| extract | 从日期/时间值中检索年份或小时等子字段。 |
+| date_part | 从日期/时间值中检索子字段。 |
+| date_trunc | 将日期/时间值截断到指定的精度。 |
+| date_bin | 将日期/时间值按指定精度进行分组。 |
+| from_unixtime | 将 Unix 时代转换为 Timestamp(Nanoseconds,None)类型。 |
+| now | 作为 Timestamp(Nanoseconds,UTC)返回当前时间。 |
+
+## 其他函数
+
+| Function | 描述 |
+| ------------ | ------------------------ |
+| array | 创建有一个数组 |
+| arrow_typeof | 返回内置的数据类型 |
+| in_list | 检测数值是否在 list 里面 |
+| random | 生成随机值 |
+| sha224 | sha224 |
+| sha256 | sha256 |
+| sha384 | sha384 |
+| sha512 | sha512 |
+| to_hex | 转换为 16 进制 |
diff --git a/docs/src/cn/sql/identifier.md b/docs/src/cn/sql/identifier.md
new file mode 100644
index 0000000..c958e58
--- /dev/null
+++ b/docs/src/cn/sql/identifier.md
@@ -0,0 +1,3 @@
+# 标识符
+
+HoraeDB 中表名、列名等标识符不能是保留关键字或以数字和标点符号开始,不过 HoraeDB 允许用反引号引用标识符(\`)。在这种情况下,它可以是任何字符串,如 `00_table` 或 `select`。
diff --git a/docs/src/cn/sql/model/README.md b/docs/src/cn/sql/model/README.md
new file mode 100644
index 0000000..4a216d4
--- /dev/null
+++ b/docs/src/cn/sql/model/README.md
@@ -0,0 +1,6 @@
+# 数据模型
+
+本章介绍 HoraeDB 的数据模型。
+
+- [数据类型](data_types.md)
+- [特殊字段](special_columns.md)
diff --git a/docs/src/sql/model/data_types.md b/docs/src/cn/sql/model/data_types.md
similarity index 71%
copy from docs/src/sql/model/data_types.md
copy to docs/src/cn/sql/model/data_types.md
index 65abd65..af94c13 100644
--- a/docs/src/sql/model/data_types.md
+++ b/docs/src/cn/sql/model/data_types.md
@@ -1,9 +1,9 @@
-# Data Types
+# 数据类型
-HoraeDB implements table model, and the supported data types are similar to MySQL.
-The following table lists the mapping relationship between MySQL and HoraeDB.
+HoraeDB 实现了 `Table` 模型,支持的数据类型和 MySQL 比较类似。
+下列表格列出了 HoraeDB 的数据类型和 MySQL 的数据类型的对应关系。
-## Support Data Type(case-insensitive)
+## 支持的数据类型 (大小写不敏感)
| SQL | HoraeDB |
| -------------- | --------- |
diff --git a/docs/src/cn/sql/model/special_columns.md b/docs/src/cn/sql/model/special_columns.md
new file mode 100644
index 0000000..8b3f7de
--- /dev/null
+++ b/docs/src/cn/sql/model/special_columns.md
@@ -0,0 +1,65 @@
+# 特殊字段
+
+HoraeDB 的表的约束如下:
+
+- 必须有主键
+- 主键必须包含时间列,并且只能包含一个时间列
+- 主键不可为空,并且主键的组成字段也不可为空
+
+## Timestamp 列
+
+HoraeDB 的表必须包含一个时间戳列,对应时序数据中的时间,例如 OpenTSDB/Prometheus 的 `timestamp`。
+时间戳列通过关键字 `timestamp key` 设置,例如 `TIMESTAMP KEY(ts)`。
+
+## Tag 列
+
+`Tag` 关键字定义了一个字段作为标签列,和时序数据中的 `tag` 类似,例如 OpenTSDB 的 `tag` 或 Prometheus 的 `label`。
+
+## 主键
+
+主键用于数据去重和排序,由一些列和一个时间列组成。
+主键可以通过以下一些方式设置:
+
+- 使用 `primary key` 关键字
+- 使用 `tag` 来自动生成 TSID,HoraeDB 默认将使用 `(TSID,timestamp)` 作为主键。
+- 只设置时间戳列,HoraeDB 将使用 `(timestamp)` 作为主键。
+
+注意:如果同时指定了主键和 `Tag` 列,那么 `Tag` 列只是一个额外的信息标识,不会影响主键生成逻辑。
+
+```sql
+CREATE TABLE with_primary_key(
+ ts TIMESTAMP NOT NULL,
+ c1 STRING NOT NULL,
+ c2 STRING NULL,
+ c4 STRING NULL,
+ c5 STRING NULL,
+ TIMESTAMP KEY(ts),
+ PRIMARY KEY(c1, ts)
+) ENGINE=Analytic WITH (ttl='7d');
+
+CREATE TABLE with_tag(
+ ts TIMESTAMP NOT NULL,
+ c1 STRING TAG NOT NULL,
+ c2 STRING TAG NULL,
+ c3 STRING TAG NULL,
+ c4 DOUBLE NULL,
+ c5 STRING NULL,
+ c6 STRING NULL,
+ TIMESTAMP KEY(ts)
+) ENGINE=Analytic WITH (ttl='7d');
+
+CREATE TABLE with_timestamp(
+ ts TIMESTAMP NOT NULL,
+ c1 STRING NOT NULL,
+ c2 STRING NULL,
+ c3 STRING NULL,
+ c4 DOUBLE NULL,
+ c5 STRING NULL,
+ c6 STRING NULL,
+ TIMESTAMP KEY(ts)
+) ENGINE=Analytic WITH (ttl='7d');
+```
+
+## TSID
+
+如果建表时没有设置主键,并且提供了 `Tag` 列,HoraeDB 会自动生成一个 `TSID` 列和时间戳列作为主键。`TSID` 由所有 `Tag` 列的 hash 值生成,本质上这是一种自动生成 ID 的机制。
diff --git a/docs/src/sql/utility.md b/docs/src/cn/sql/utility.md
similarity index 73%
copy from docs/src/sql/utility.md
copy to docs/src/cn/sql/utility.md
index 757c8fa..0c36a00 100644
--- a/docs/src/sql/utility.md
+++ b/docs/src/cn/sql/utility.md
@@ -1,14 +1,14 @@
-# Utility Statements
+# 常用 SQL
-There are serval utilities SQL in HoraeDB that can help in table manipulation or query inspection.
+HoraeDB 中有许多实用的 SQL 工具,可以辅助表操作或查询检查。
-## SHOW CREATE TABLE
+## 查看建表语句
```sql
SHOW CREATE TABLE table_name;
```
-`SHOW CREATE TABLE` returns a `CREATE TABLE` DDL that will create a same table with the given one. Including columns, table engine and options. The schema and options shows in `CREATE TABLE` will based on the current version of the table. An example:
+`SHOW CREATE TABLE` 返回指定表的当前版本的创建语句,包括列定义、表引擎和参数选项等。例如:
```sql
-- create one table
@@ -41,15 +41,16 @@ CREATE TABLE `t` (
)
```
-## DESCRIBE
+## 查看表信息
```sql
DESCRIBE table_name;
```
-`DESCRIBE` will show a detailed schema of one table. The attributes include column name and type, whether it is tag and primary key (todo: ref) and whether it's nullable. The auto created column `tsid` will also be included (todo: ref).
+`DESCRIBE` 语句返回一个表的详细结构信息,包括每个字段的名称和类型,字段是否为 `Tag` 或主键,字段是否可空等。
+此外,自动生成的字段 `tsid` 也会展示在结果里。
-Example:
+例如:
```sql
CREATE TABLE `t`(a int, b string, t timestamp NOT NULL, TIMESTAMP KEY(t)) ENGINE = Analytic;
@@ -57,7 +58,7 @@ CREATE TABLE `t`(a int, b string, t timestamp NOT NULL, TIMESTAMP KEY(t)) ENGINE
DESCRIBE TABLE `t`;
```
-The result is:
+返回结果如下:
```
name type is_primary is_nullable is_tag
@@ -68,19 +69,19 @@ a int false true false
b string false true false
```
-## EXPLAIN
+## 解释执行计划
```sql
EXPLAIN query;
```
-`EXPLAIN` shows how a query will be executed. Add it to the beginning of a query like
+`EXPLAIN` 语句结果展示一个查询如何被执行。例如:
```sql
EXPLAIN SELECT max(value) AS c1, avg(value) AS c2 FROM `t` GROUP BY name;
```
-will give
+结果如下:
```
logical_plan
diff --git a/docs/src/trouble_shooting.md b/docs/src/cn/trouble_shooting.md
similarity index 100%
copy from docs/src/trouble_shooting.md
copy to docs/src/cn/trouble_shooting.md
diff --git a/docs/src/SUMMARY.md b/docs/src/en/SUMMARY.md
similarity index 100%
rename from docs/src/SUMMARY.md
rename to docs/src/en/SUMMARY.md
diff --git a/docs/src/about.md b/docs/src/en/about.md
similarity index 95%
rename from docs/src/about.md
rename to docs/src/en/about.md
index e77d0ad..e8c0780 100644
--- a/docs/src/about.md
+++ b/docs/src/en/about.md
@@ -1,3 +1,5 @@
+
+

[](https://github.com/apache/incubator-horaedb/actions/workflows/ci.yml)
[](https://github.com/apache/incubator-horaedb/issues)
diff --git a/docs/src/cluster_deployment/README.md b/docs/src/en/cluster_deployment/README.md
similarity index 100%
rename from docs/src/cluster_deployment/README.md
rename to docs/src/en/cluster_deployment/README.md
diff --git a/docs/src/cluster_deployment/no_meta.md b/docs/src/en/cluster_deployment/no_meta.md
similarity index 100%
rename from docs/src/cluster_deployment/no_meta.md
rename to docs/src/en/cluster_deployment/no_meta.md
diff --git a/docs/src/cluster_deployment/platform.md b/docs/src/en/cluster_deployment/platform.md
similarity index 100%
rename from docs/src/cluster_deployment/platform.md
rename to docs/src/en/cluster_deployment/platform.md
diff --git a/docs/src/cluster_deployment/with_meta.md b/docs/src/en/cluster_deployment/with_meta.md
similarity index 98%
rename from docs/src/cluster_deployment/with_meta.md
rename to docs/src/en/cluster_deployment/with_meta.md
index ecbaa8e..e6439f9 100644
--- a/docs/src/cluster_deployment/with_meta.md
+++ b/docs/src/en/cluster_deployment/with_meta.md
@@ -253,9 +253,9 @@ memory_limit = "4G"
Let's name this config file as `config.toml`. And the example configs, in which the templates must be replaced with real parameters before use, for remote storages are also provided:
-- [RocksDB WAL + OSS](../resources/config_local_oss.toml)
-- [OceanBase WAL + OSS](../resources/config_obkv_oss.toml)
-- [Kafka WAL + OSS](../resources/config_kafka_oss.toml)
+- [RocksDB WAL + OSS](../../resources/config_local_oss.toml)
+- [OceanBase WAL + OSS](../../resources/config_obkv_oss.toml)
+- [Kafka WAL + OSS](../../resources/config_kafka_oss.toml)
## Run HoraeDB cluster with HoraeMeta
diff --git a/docs/src/design/architecture.md b/docs/src/en/design/architecture.md
similarity index 100%
rename from docs/src/design/architecture.md
rename to docs/src/en/design/architecture.md
diff --git a/docs/src/design/clustering.md b/docs/src/en/design/clustering.md
similarity index 100%
rename from docs/src/design/clustering.md
rename to docs/src/en/design/clustering.md
diff --git a/docs/src/design/storage.md b/docs/src/en/design/storage.md
similarity index 98%
rename from docs/src/design/storage.md
rename to docs/src/en/design/storage.md
index ee1264f..2829412 100644
--- a/docs/src/design/storage.md
+++ b/docs/src/en/design/storage.md
@@ -13,7 +13,7 @@ HoraeDB is a distributed storage system based on the share-nothing architecture.
Data between different servers is isolated from each other and does not affect each other. The storage engine in each stand-alone machine is a variant of [log-structured merge-tree](https://en.wikipedia.org/wiki/Log-structured_merge-tree), which is optimized for time-series scenarios. The following figure shows its core components:
-
+
## Write Ahead Log (WAL)
diff --git a/docs/src/design/table_partitioning.md b/docs/src/en/design/table_partitioning.md
similarity index 100%
rename from docs/src/design/table_partitioning.md
rename to docs/src/en/design/table_partitioning.md
diff --git a/docs/src/design/wal.md b/docs/src/en/design/wal.md
similarity index 100%
rename from docs/src/design/wal.md
rename to docs/src/en/design/wal.md
diff --git a/docs/src/design/wal_on_kafka.md b/docs/src/en/design/wal_on_kafka.md
similarity index 100%
rename from docs/src/design/wal_on_kafka.md
rename to docs/src/en/design/wal_on_kafka.md
diff --git a/docs/src/design/wal_on_rocksdb.md b/docs/src/en/design/wal_on_rocksdb.md
similarity index 100%
rename from docs/src/design/wal_on_rocksdb.md
rename to docs/src/en/design/wal_on_rocksdb.md
diff --git a/docs/src/dev/README.md b/docs/src/en/dev/README.md
similarity index 100%
rename from docs/src/dev/README.md
rename to docs/src/en/dev/README.md
diff --git a/docs/src/dev/compile_run.md b/docs/src/en/dev/compile_run.md
similarity index 100%
rename from docs/src/dev/compile_run.md
rename to docs/src/en/dev/compile_run.md
diff --git a/docs/src/dev/conventional_commit.md b/docs/src/en/dev/conventional_commit.md
similarity index 100%
rename from docs/src/dev/conventional_commit.md
rename to docs/src/en/dev/conventional_commit.md
diff --git a/docs/src/dev/platform.md b/docs/src/en/dev/platform.md
similarity index 100%
rename from docs/src/dev/platform.md
rename to docs/src/en/dev/platform.md
diff --git a/docs/src/dev/profiling.md b/docs/src/en/dev/profiling.md
similarity index 100%
rename from docs/src/dev/profiling.md
rename to docs/src/en/dev/profiling.md
diff --git a/docs/src/dev/roadmap.md b/docs/src/en/dev/roadmap.md
similarity index 100%
rename from docs/src/dev/roadmap.md
rename to docs/src/en/dev/roadmap.md
diff --git a/docs/src/dev/style_guide.md b/docs/src/en/dev/style_guide.md
similarity index 100%
rename from docs/src/dev/style_guide.md
rename to docs/src/en/dev/style_guide.md
diff --git a/docs/src/ecosystem/README.md b/docs/src/en/ecosystem/README.md
similarity index 100%
rename from docs/src/ecosystem/README.md
rename to docs/src/en/ecosystem/README.md
diff --git a/docs/src/ecosystem/influxdb.md b/docs/src/en/ecosystem/influxdb.md
similarity index 100%
rename from docs/src/ecosystem/influxdb.md
rename to docs/src/en/ecosystem/influxdb.md
diff --git a/docs/src/ecosystem/opentsdb.md b/docs/src/en/ecosystem/opentsdb.md
similarity index 100%
rename from docs/src/ecosystem/opentsdb.md
rename to docs/src/en/ecosystem/opentsdb.md
diff --git a/docs/src/ecosystem/prometheus.md b/docs/src/en/ecosystem/prometheus.md
similarity index 100%
rename from docs/src/ecosystem/prometheus.md
rename to docs/src/en/ecosystem/prometheus.md
diff --git a/docs/src/operation/README.md b/docs/src/en/operation/README.md
similarity index 100%
rename from docs/src/operation/README.md
rename to docs/src/en/operation/README.md
diff --git a/docs/src/operation/block_list.md b/docs/src/en/operation/block_list.md
similarity index 100%
rename from docs/src/operation/block_list.md
rename to docs/src/en/operation/block_list.md
diff --git a/docs/src/operation/horaemeta.md b/docs/src/en/operation/horaemeta.md
similarity index 100%
rename from docs/src/operation/horaemeta.md
rename to docs/src/en/operation/horaemeta.md
diff --git a/docs/src/operation/observability.md b/docs/src/en/operation/observability.md
similarity index 90%
rename from docs/src/operation/observability.md
rename to docs/src/en/operation/observability.md
index 85f9b8f..669fe42 100644
--- a/docs/src/operation/observability.md
+++ b/docs/src/en/operation/observability.md
@@ -65,21 +65,21 @@ For more detailed installation methods, refer to [here](https://grafana.com/docs
Note: The url of Prometheus is `http://your_ip:9090`.
-<img src="../resources/images/grafana-datasource.png" height="400" width="200"/>
+<img src="../../resources/images/grafana-datasource.png" height="400" width="200"/>
See more details [here](https://grafana.com/docs/grafana/latest/datasources/prometheus/).
### Import grafana dashboard
-<a href="../resources/grafana-dashboard.json">dashboard json</a>
+<a href="../../resources/grafana-dashboard.json">dashboard json</a>
-<img src="../resources/images/grafana-dashboard.png" height="400" width="200"/>
+<img src="../../resources/images/grafana-dashboard.png" height="400" width="200"/>
## HoraeDB Metrics
After importing the dashboard, you will see the following page:
-<img src="../resources/images/grafana-horaedb-dashboard.png" height="400" width="600"/>
+<img src="../../resources/images/grafana-horaedb-dashboard.png" height="400" width="600"/>
### Panels
diff --git a/docs/src/operation/system_table.md b/docs/src/en/operation/system_table.md
similarity index 100%
rename from docs/src/operation/system_table.md
rename to docs/src/en/operation/system_table.md
diff --git a/docs/src/operation/table.md b/docs/src/en/operation/table.md
similarity index 100%
rename from docs/src/operation/table.md
rename to docs/src/en/operation/table.md
diff --git a/docs/src/protocol.md b/docs/src/en/protocol.md
similarity index 100%
rename from docs/src/protocol.md
rename to docs/src/en/protocol.md
diff --git a/docs/src/quick_start.md b/docs/src/en/quick_start.md
similarity index 100%
rename from docs/src/quick_start.md
rename to docs/src/en/quick_start.md
diff --git a/docs/src/sdk/README.md b/docs/src/en/sdk/README.md
similarity index 100%
rename from docs/src/sdk/README.md
rename to docs/src/en/sdk/README.md
diff --git a/docs/src/sdk/go.md b/docs/src/en/sdk/go.md
similarity index 100%
rename from docs/src/sdk/go.md
rename to docs/src/en/sdk/go.md
diff --git a/docs/src/sdk/java.md b/docs/src/en/sdk/java.md
similarity index 100%
rename from docs/src/sdk/java.md
rename to docs/src/en/sdk/java.md
diff --git a/docs/src/sdk/python.md b/docs/src/en/sdk/python.md
similarity index 100%
rename from docs/src/sdk/python.md
rename to docs/src/en/sdk/python.md
diff --git a/docs/src/sdk/rust.md b/docs/src/en/sdk/rust.md
similarity index 100%
rename from docs/src/sdk/rust.md
rename to docs/src/en/sdk/rust.md
diff --git a/docs/src/sql/README.md b/docs/src/en/sql/README.md
similarity index 100%
rename from docs/src/sql/README.md
rename to docs/src/en/sql/README.md
diff --git a/docs/src/sql/ddl/README.md b/docs/src/en/sql/ddl/README.md
similarity index 100%
rename from docs/src/sql/ddl/README.md
rename to docs/src/en/sql/ddl/README.md
diff --git a/docs/src/sql/ddl/alter_table.md b/docs/src/en/sql/ddl/alter_table.md
similarity index 100%
rename from docs/src/sql/ddl/alter_table.md
rename to docs/src/en/sql/ddl/alter_table.md
diff --git a/docs/src/sql/ddl/create_table.md b/docs/src/en/sql/ddl/create_table.md
similarity index 100%
rename from docs/src/sql/ddl/create_table.md
rename to docs/src/en/sql/ddl/create_table.md
diff --git a/docs/src/sql/ddl/drop_table.md b/docs/src/en/sql/ddl/drop_table.md
similarity index 100%
rename from docs/src/sql/ddl/drop_table.md
rename to docs/src/en/sql/ddl/drop_table.md
diff --git a/docs/src/sql/dml/README.md b/docs/src/en/sql/dml/README.md
similarity index 100%
rename from docs/src/sql/dml/README.md
rename to docs/src/en/sql/dml/README.md
diff --git a/docs/src/sql/dml/insert.md b/docs/src/en/sql/dml/insert.md
similarity index 100%
rename from docs/src/sql/dml/insert.md
rename to docs/src/en/sql/dml/insert.md
diff --git a/docs/src/sql/dml/select.md b/docs/src/en/sql/dml/select.md
similarity index 100%
rename from docs/src/sql/dml/select.md
rename to docs/src/en/sql/dml/select.md
diff --git a/docs/src/sql/engine_options.md b/docs/src/en/sql/engine_options.md
similarity index 100%
rename from docs/src/sql/engine_options.md
rename to docs/src/en/sql/engine_options.md
diff --git a/docs/src/sql/functions/aggregate_functions.md b/docs/src/en/sql/functions/aggregate_functions.md
similarity index 100%
rename from docs/src/sql/functions/aggregate_functions.md
rename to docs/src/en/sql/functions/aggregate_functions.md
diff --git a/docs/src/sql/functions/scalar_functions.md b/docs/src/en/sql/functions/scalar_functions.md
similarity index 98%
rename from docs/src/sql/functions/scalar_functions.md
rename to docs/src/en/sql/functions/scalar_functions.md
index 9e4cd13..3923c56 100644
--- a/docs/src/sql/functions/scalar_functions.md
+++ b/docs/src/en/sql/functions/scalar_functions.md
@@ -1,6 +1,6 @@
# Scalar Functions
-HoraeDB SQL is implemented with [DataFusion](https://github.com/apache/arrow-datafusion), Here is the list of scalar functions. See more detail, Refer to [Datafusion](https://github.com/apache/arrow-datafusion/blob/master/docs/source/user-guide/sql/scalar_functions.md)
+HoraeDB SQL is implemented with [DataFusion](https://github.com/CeresDB/arrow-datafusion), Here is the list of scalar functions. See more detail, Refer to [Datafusion](https://github.com/CeresDB/arrow-datafusion/blob/master/docs/source/user-guide/sql/scalar_functions.md)
## Math Functions
diff --git a/docs/src/sql/identifier.md b/docs/src/en/sql/identifier.md
similarity index 100%
rename from docs/src/sql/identifier.md
rename to docs/src/en/sql/identifier.md
diff --git a/docs/src/sql/model/README.md b/docs/src/en/sql/model/README.md
similarity index 100%
rename from docs/src/sql/model/README.md
rename to docs/src/en/sql/model/README.md
diff --git a/docs/src/sql/model/data_types.md b/docs/src/en/sql/model/data_types.md
similarity index 100%
rename from docs/src/sql/model/data_types.md
rename to docs/src/en/sql/model/data_types.md
diff --git a/docs/src/sql/model/special_columns.md b/docs/src/en/sql/model/special_columns.md
similarity index 100%
rename from docs/src/sql/model/special_columns.md
rename to docs/src/en/sql/model/special_columns.md
diff --git a/docs/src/sql/utility.md b/docs/src/en/sql/utility.md
similarity index 100%
rename from docs/src/sql/utility.md
rename to docs/src/en/sql/utility.md
diff --git a/docs/src/trouble_shooting.md b/docs/src/en/trouble_shooting.md
similarity index 100%
rename from docs/src/trouble_shooting.md
rename to docs/src/en/trouble_shooting.md
diff --git a/docs/theme/index.hbs b/docs/theme/index.hbs
index 9269572..b3453ce 100644
--- a/docs/theme/index.hbs
+++ b/docs/theme/index.hbs
@@ -1,372 +1,344 @@
<!DOCTYPE HTML>
<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
+ <head>
+ <!-- Book generated using mdBook -->
+ <meta charset="UTF-8">
+ <title>{{ title }}</title>
+ {{#if is_print }}
+ <meta name="robots" content="noindex" />
+ {{/if}}
+ {{#if base_url}}
+ <base href="{{ base_url }}">
+ {{/if}}
+
+
+ <!-- Custom HTML head -->
+ {{> head}}
+
+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
+ <meta name="description" content="{{ description }}">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="theme-color" content="#ffffff" />
+
+ {{#if favicon_svg}}
+ <link rel="icon" href="{{ path_to_root }}favicon.svg">
+ {{/if}}
+ {{#if favicon_png}}
+ <link rel="shortcut icon" href="{{ path_to_root }}favicon.png">
+ {{/if}}
+ <link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
+ <link rel="stylesheet" href="{{ path_to_root }}css/general.css">
+ <link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
+ {{#if print_enable}}
+ <link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
+ {{/if}}
+
+ <!-- Fonts -->
+ <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
+ {{#if copy_fonts}}
+ <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
+ {{/if}}
+
+ <!-- Highlight.js Stylesheets -->
+ <link rel="stylesheet" href="{{ path_to_root }}highlight.css">
+ <link rel="stylesheet" href="{{ path_to_root }}tomorrow-night.css">
+ <link rel="stylesheet" href="{{ path_to_root }}ayu-highlight.css">
+
+ <!-- Custom theme stylesheets -->
+ {{#each additional_css}}
+ <link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
+ {{/each}}
+
+ {{#if mathjax_support}}
+ <!-- MathJax -->
+ <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
+ {{/if}}
+ </head>
+ <body>
+ <!-- Provide site root to javascript -->
+ <script>
+ var path_to_root = "{{ path_to_root }}";
+ var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
+ </script>
+
+ <!-- Work around some values being stored in localStorage wrapped in quotes -->
+ <script>
+ try {
+ var theme = localStorage.getItem('mdbook-theme');
+ var sidebar = localStorage.getItem('mdbook-sidebar');
+
+ if (theme.startsWith('"') && theme.endsWith('"')) {
+ localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
+ }
-<head>
- <!-- Book generated using mdBook -->
- <meta charset="UTF-8">
- <title>{{ title }}</title>
- {{#if is_print }}
- <meta name="robots" content="noindex" />
- {{/if}}
- {{#if base_url}}
- <base href="{{ base_url }}">
- {{/if}}
-
-
- <!-- Custom HTML head -->
- {{> head}}
-
- <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
- <meta name="description" content="{{ description }}">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <meta name="theme-color" content="#ffffff" />
-
- {{#if favicon_svg}}
- <link rel="icon" href="{{ path_to_root }}favicon.svg">
- {{/if}}
- {{#if favicon_png}}
- <link rel="shortcut icon" href="{{ path_to_root }}favicon.png">
- {{/if}}
- <link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
- <link rel="stylesheet" href="{{ path_to_root }}css/general.css">
- <link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
- {{#if print_enable}}
- <link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
- {{/if}}
-
- <!-- Fonts -->
- <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
- {{#if copy_fonts}}
- <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
- {{/if}}
-
- <!-- Highlight.js Stylesheets -->
- <link rel="stylesheet" href="{{ path_to_root }}highlight.css">
- <link rel="stylesheet" href="{{ path_to_root }}tomorrow-night.css">
- <link rel="stylesheet" href="{{ path_to_root }}ayu-highlight.css">
-
- <!-- Custom theme stylesheets -->
- {{#each additional_css}}
- <link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
- {{/each}}
-
- {{#if mathjax_support}}
- <!-- MathJax -->
- <script async
- src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
- {{/if}}
-</head>
-
-<body>
- <!-- Provide site root to javascript -->
- <script>
- var path_to_root = "{{ path_to_root }}";
- var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
- </script>
-
- <!-- Work around some values being stored in localStorage wrapped in quotes -->
- <script>
- try {
- var theme = localStorage.getItem('mdbook-theme');
- var sidebar = localStorage.getItem('mdbook-sidebar');
-
- if (theme.startsWith('"') && theme.endsWith('"')) {
- localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
+ if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
+ localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
+ }
+ } catch (e) { }
+ </script>
+
+ <!-- Set the theme before any content is loaded, prevents flash -->
+ <script>
+ var theme;
+ try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
+ if (theme === null || theme === undefined) { theme = default_theme; }
+ var html = document.querySelector('html');
+ html.classList.remove('no-js')
+ html.classList.remove('{{ default_theme }}')
+ html.classList.add(theme);
+ html.classList.add('js');
+ </script>
+
+ <!-- Hide / unhide sidebar before it is displayed -->
+ <script>
+ var html = document.querySelector('html');
+ var sidebar = 'hidden';
+ if (document.body.clientWidth >= 1080) {
+ try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
+ sidebar = sidebar || 'visible';
}
+ html.classList.remove('sidebar-visible');
+ html.classList.add("sidebar-" + sidebar);
+ </script>
- if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
- localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
- }
- } catch (e) { }
- </script>
-
- <!-- Set the theme before any content is loaded, prevents flash -->
- <script>
- var theme;
- try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
- if (theme === null || theme === undefined) { theme = default_theme; }
- var html = document.querySelector('html');
- html.classList.remove('no-js')
- html.classList.remove('{{ default_theme }}')
- html.classList.add(theme);
- html.classList.add('js');
- </script>
-
- <!-- Hide / unhide sidebar before it is displayed -->
- <script>
- var html = document.querySelector('html');
- var sidebar = 'hidden';
- if (document.body.clientWidth >= 1080) {
- try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch (e) { }
- sidebar = sidebar || 'visible';
- }
- html.classList.remove('sidebar-visible');
- html.classList.add("sidebar-" + sidebar);
- </script>
-
- <nav id="sidebar" class="sidebar" aria-label="Table of contents">
- <div class="sidebar-scrollbox">
- {{#toc}}{{/toc}}
- </div>
- <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
- </nav>
-
- <div id="page-wrapper" class="page-wrapper">
-
- <div class="page">
- {{> header}}
- <div id="menu-bar-hover-placeholder"></div>
- <div id="menu-bar" class="menu-bar sticky bordered">
- <div class="left-buttons">
- <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents"
- aria-label="Toggle Table of Contents" aria-controls="sidebar">
- <i class="fa fa-bars"></i>
- </button>
- <button id="theme-toggle" class="icon-button" type="button" title="Change theme"
- aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
- <i class="fa fa-paint-brush"></i>
- </button>
- <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
- <li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light"
- }}</button></li>
- <li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust"
- }}</button></li>
- <li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal"
- }}</button></li>
- <li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy"
- }}</button></li>
- <li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button>
- </li>
- </ul>
- {{#if search_enabled}}
- <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)"
- aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S"
- aria-controls="searchbar">
- <i class="fa fa-search"></i>
- </button>
- {{/if}}
- </div>
+ <nav id="sidebar" class="sidebar" aria-label="Table of contents">
+ <div class="sidebar-scrollbox">
+ {{#toc}}{{/toc}}
+ </div>
+ <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
+ </nav>
- <h1 class="menu-title">{{ book_title }}</h1>
-
- <div class="right-buttons">
-
- <button id="lang-toggle" class="icon-button" type="button" title="Change language"
- aria-label="Change language" aria-haspopup="true" aria-expanded="false"
- aria-controls="lang-list">
- <!-- <i class="fa fa-globe"></i> -->
- <i>
- <svg t="1675840511805" class="icon" viewBox="0 0 1024 1024" version="1.1"
- xmlns="http://www.w3.org/2000/svg" p-id="2677" width="16" height="16">
- <path
- d="M511.573333 85.333333C276.053333 85.333333 85.333333 276.48 85.333333 512s190.72 426.666667 426.24 426.666667C747.52 938.666667 938.666667 747.52 938.666667 512S747.52 85.333333 511.573333 85.333333z m295.68 256h-125.866666a667.733333 667.733333 0 0 0-58.88-151.893333A342.613333 342.613333 0 0 1 807.253333 341.333333zM512 172.373333c35.413333 51.2 63.146667 107.946667 81.493333 168.96h-162.986666c18.346667-61.013333 46.08-117.76 81.493333-168.96zM18 [...]
- fill="#000000" fill-opacity=".87" p-id="2678"></path>
- </svg>
- <a id="lang_comment"></a>
- </i>
-
- </button>
- <ul id="lang-list" class="theme-popup" style="left: auto;" aria-label="Languages" role="menu">
- <li role="none"><button role="menuitem" class="theme" id="en">English</button></li>
- <li role="none"><button role="menuitem" class="theme" id="cn">中文</button></li>
- </ul>
-
- {{#if print_enable}}
- <a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
- <i id="print-button" class="fa fa-print"></i>
- </a>
- {{/if}}
- {{#if git_repository_url}}
- <a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
- <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
- </a>
- {{/if}}
- {{#if git_repository_edit_url}}
- <a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
- <i id="git-edit-button" class="fa fa-edit"></i>
- </a>
- {{/if}}
+ <div id="page-wrapper" class="page-wrapper">
+
+ <div class="page">
+ {{> header}}
+ <div id="menu-bar-hover-placeholder"></div>
+ <div id="menu-bar" class="menu-bar sticky bordered">
+ <div class="left-buttons">
+ <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
+ <i class="fa fa-bars"></i>
+ </button>
+ <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
+ <i class="fa fa-paint-brush"></i>
+ </button>
+ <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
+ <li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
+ </ul>
+ {{#if search_enabled}}
+ <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
+ <i class="fa fa-search"></i>
+ </button>
+ {{/if}}
+ </div>
+
+ <h1 class="menu-title">{{ book_title }}</h1>
+
+ <div class="right-buttons">
+
+ <button id="lang-toggle" class="icon-button" type="button" title="Change language" aria-label="Change language" aria-haspopup="true" aria-expanded="false" aria-controls="lang-list" >
+<!-- <i class="fa fa-globe"></i> -->
+ <i>
+ <svg t="1675840511805" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2677" width="16" height="16"><path d="M511.573333 85.333333C276.053333 85.333333 85.333333 276.48 85.333333 512s190.72 426.666667 426.24 426.666667C747.52 938.666667 938.666667 747.52 938.666667 512S747.52 85.333333 511.573333 85.333333z m295.68 256h-125.866666a667.733333 667.733333 0 0 0-58.88-151.893333A342.613333 342.613333 0 0 1 807.253333 341.333 [...]
+ <a id="lang_comment"></a>
+ </i>
+
+ </button>
+ <ul id="lang-list" class="theme-popup" style="left: auto;" aria-label="Languages" role="menu">
+ <li role="none"><button role="menuitem" class="theme" id="en">English</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="cn">中文</button></li>
+ </ul>
+
+ {{#if print_enable}}
+ <a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
+ <i id="print-button" class="fa fa-print"></i>
+ </a>
+ {{/if}}
+ {{#if git_repository_url}}
+ <a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
+ <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
+ </a>
+ {{/if}}
+ {{#if git_repository_edit_url}}
+ <a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
+ <i id="git-edit-button" class="fa fa-edit"></i>
+ </a>
+ {{/if}}
+ </div>
</div>
- </div>
- <script>
- window.onload = function () {
+ <script>
+ window.onload = function(){
var path_lang = window.location.pathname.split('/')[1];
- document.getElementById('lang_comment').innerHTML = path_lang == 'cn' ? "Language" : "语言";
+ document.getElementById('lang_comment').innerHTML = path_lang=='cn'?"Language":"语言";
};
- </script>
-
- {{#if search_enabled}}
- <div id="search-wrapper" class="hidden">
- <form id="searchbar-outer" class="searchbar-outer">
- <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..."
- aria-controls="searchresults-outer" aria-describedby="searchresults-header">
- </form>
- <div id="searchresults-outer" class="searchresults-outer hidden">
- <div id="searchresults-header" class="searchresults-header"></div>
- <ul id="searchresults">
- </ul>
+ </script>
+
+ {{#if search_enabled}}
+ <div id="search-wrapper" class="hidden">
+ <form id="searchbar-outer" class="searchbar-outer">
+ <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
+ </form>
+ <div id="searchresults-outer" class="searchresults-outer hidden">
+ <div id="searchresults-header" class="searchresults-header"></div>
+ <ul id="searchresults">
+ </ul>
+ </div>
+ </div>
+ {{/if}}
+
+ <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
+ <script>
+ document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
+ document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
+ Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
+ link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
+ });
+ </script>
+
+ <div id="content" class="content">
+ <main>
+ <!-- Page table of contents -->
+ <div class="sidetoc"><nav class="pagetoc"></nav></div>
+
+ {{{ content }}}
+ </main>
+
+ <nav class="nav-wrapper" aria-label="Page navigation">
+ <!-- Mobile navigation buttons -->
+ {{#previous}}
+ <a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
+ <i class="fa fa-angle-left"></i>
+ </a>
+ {{/previous}}
+
+ {{#next}}
+ <a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
+ <i class="fa fa-angle-right"></i>
+ </a>
+ {{/next}}
+
+ <div style="clear: both"></div>
+ </nav>
</div>
</div>
- {{/if}}
-
- <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
- <script>
- document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
- document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
- Array.from(document.querySelectorAll('#sidebar a')).forEach(function (link) {
- link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
- });
- </script>
-
- <div id="content" class="content">
- <main>
- <!-- Page table of contents -->
- <div class="sidetoc">
- <nav class="pagetoc"></nav>
- </div>
- {{{ content }}}
- </main>
-
- <nav class="nav-wrapper" aria-label="Page navigation">
- <!-- Mobile navigation buttons -->
- {{#previous}}
- <a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous"
- title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
+ <nav class="nav-wide-wrapper" aria-label="Page navigation">
+ {{#previous}}
+ <a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
- {{/previous}}
+ {{/previous}}
- {{#next}}
- <a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next"
- title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
+ {{#next}}
+ <a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
- {{/next}}
+ {{/next}}
+ </nav>
- <div style="clear: both"></div>
- </nav>
- </div>
</div>
- <nav class="nav-wide-wrapper" aria-label="Page navigation">
- {{#previous}}
- <a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter"
- aria-label="Previous chapter" aria-keyshortcuts="Left">
- <i class="fa fa-angle-left"></i>
- </a>
- {{/previous}}
-
- {{#next}}
- <a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter"
- aria-label="Next chapter" aria-keyshortcuts="Right">
- <i class="fa fa-angle-right"></i>
- </a>
- {{/next}}
- </nav>
-
- </div>
+ {{#if live_reload_endpoint}}
+ <!-- Livereload script (if served using the cli tool) -->
+ <script>
+ const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
+ const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
+ const socket = new WebSocket(wsAddress);
+ socket.onmessage = function (event) {
+ if (event.data === "reload") {
+ socket.close();
+ location.reload();
+ }
+ };
- {{#if live_reload_endpoint}}
- <!-- Livereload script (if served using the cli tool) -->
- <script>
- const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
- const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
- const socket = new WebSocket(wsAddress);
- socket.onmessage = function (event) {
- if (event.data === "reload") {
+ window.onbeforeunload = function() {
socket.close();
- location.reload();
}
- };
-
- window.onbeforeunload = function () {
- socket.close();
- }
- </script>
- {{/if}}
-
- {{#if google_analytics}}
- <!-- Google Analytics Tag -->
- <script>
- var localAddrs = ["localhost", "127.0.0.1", ""];
-
- // make sure we don't activate google analytics if the developer is
- // inspecting the book locally...
- if (localAddrs.indexOf(document.location.hostname) === -1) {
- (function (i, s, o, g, r, a, m) {
- i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
- (i[r].q = i[r].q || []).push(arguments)
- }, i[r].l = 1 * new Date(); a = s.createElement(o),
- m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
- })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
-
- ga('create', '{{google_analytics}}', 'auto');
- ga('send', 'pageview');
- }
- </script>
- {{/if}}
-
- {{#if playground_line_numbers}}
- <script>
- window.playground_line_numbers = true;
- </script>
- {{/if}}
-
- {{#if playground_copyable}}
- <script>
- window.playground_copyable = true;
- </script>
- {{/if}}
-
- {{#if playground_js}}
- <script src="{{ path_to_root }}ace.js" charset="utf-8"></script>
- <script src="{{ path_to_root }}editor.js" charset="utf-8"></script>
- <script src="{{ path_to_root }}mode-rust.js" charset="utf-8"></script>
- <script src="{{ path_to_root }}theme-dawn.js" charset="utf-8"></script>
- <script src="{{ path_to_root }}theme-tomorrow_night.js" charset="utf-8"></script>
- {{/if}}
-
- {{#if search_js}}
- <script src="{{ path_to_root }}elasticlunr.min.js" charset="utf-8"></script>
- <script src="{{ path_to_root }}mark.min.js" charset="utf-8"></script>
- <script src="{{ path_to_root }}searcher.js" charset="utf-8"></script>
- {{/if}}
-
- <script src="{{ path_to_root }}clipboard.min.js" charset="utf-8"></script>
- <script src="{{ path_to_root }}highlight.js" charset="utf-8"></script>
- <script src="{{ path_to_root }}book.js" charset="utf-8"></script>
-
- <!-- Custom JS scripts -->
- {{#each additional_js}}
- <script src="{{ ../path_to_root }}{{this}}"></script>
- {{/each}}
-
- {{#if is_print}}
- {{#if mathjax_support}}
- <script>
- window.addEventListener('load', function () {
- MathJax.Hub.Register.StartupHook('End', function () {
+ </script>
+ {{/if}}
+
+ {{#if google_analytics}}
+ <!-- Google Analytics Tag -->
+ <script>
+ var localAddrs = ["localhost", "127.0.0.1", ""];
+
+ // make sure we don't activate google analytics if the developer is
+ // inspecting the book locally...
+ if (localAddrs.indexOf(document.location.hostname) === -1) {
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+ })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+
+ ga('create', '{{google_analytics}}', 'auto');
+ ga('send', 'pageview');
+ }
+ </script>
+ {{/if}}
+
+ {{#if playground_line_numbers}}
+ <script>
+ window.playground_line_numbers = true;
+ </script>
+ {{/if}}
+
+ {{#if playground_copyable}}
+ <script>
+ window.playground_copyable = true;
+ </script>
+ {{/if}}
+
+ {{#if playground_js}}
+ <script src="{{ path_to_root }}ace.js" charset="utf-8"></script>
+ <script src="{{ path_to_root }}editor.js" charset="utf-8"></script>
+ <script src="{{ path_to_root }}mode-rust.js" charset="utf-8"></script>
+ <script src="{{ path_to_root }}theme-dawn.js" charset="utf-8"></script>
+ <script src="{{ path_to_root }}theme-tomorrow_night.js" charset="utf-8"></script>
+ {{/if}}
+
+ {{#if search_js}}
+ <script src="{{ path_to_root }}elasticlunr.min.js" charset="utf-8"></script>
+ <script src="{{ path_to_root }}mark.min.js" charset="utf-8"></script>
+ <script src="{{ path_to_root }}searcher.js" charset="utf-8"></script>
+ {{/if}}
+
+ <script src="{{ path_to_root }}clipboard.min.js" charset="utf-8"></script>
+ <script src="{{ path_to_root }}highlight.js" charset="utf-8"></script>
+ <script src="{{ path_to_root }}book.js" charset="utf-8"></script>
+
+ <!-- Custom JS scripts -->
+ {{#each additional_js}}
+ <script src="{{ ../path_to_root }}{{this}}"></script>
+ {{/each}}
+
+ {{#if is_print}}
+ {{#if mathjax_support}}
+ <script>
+ window.addEventListener('load', function() {
+ MathJax.Hub.Register.StartupHook('End', function() {
window.setTimeout(window.print, 100);
});
});
- </script>
- {{else}}
- <script>
- window.addEventListener('load', function () {
+ </script>
+ {{else}}
+ <script>
+ window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
- </script>
- {{/if}}
- {{/if}}
+ </script>
+ {{/if}}
+ {{/if}}
- <script type="text/javascript">
- let langs = [
- 'cn',
- 'en'
- ];
+ <script type="text/javascript">
+ let langs = [
+ 'cn',
+ 'en'
+ ];
(function langs() {
var html = document.querySelector('html');
@@ -393,22 +365,21 @@
});
langPopup.addEventListener('click', function (e) {
- let langs = [
- 'cn',
- 'en'
- ];
+ let langs = [
+ 'cn',
+ 'en'
+ ];
var lang = e.target.id || e.target.parentElement.id;
- var path_list = [];
+ var path_list=[];
window.location.pathname.split('/').map((s, idx) => {
- // FIXME: This is hard code to address the i18n problem, and it requires the base path must include two path elements.
- if (idx == 2) {
- if (!langs.includes(s)) {
+ if (idx == 1){
+ if(!langs.includes(s)){
path_list.push(lang);
path_list.push(s);
- } else {
+ }else{
path_list.push(lang);
}
- } else {
+ }else{
path_list.push(s);
}
});
@@ -416,7 +387,7 @@
window.location.pathname = path_list.join('/');
});
- langPopup.addEventListener('focusout', function (e) {
+ langPopup.addEventListener('focusout', function(e) {
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
if (!!e.relatedTarget && !langToggleButton.contains(e.relatedTarget) && !langPopup.contains(e.relatedTarget)) {
hideLangs();
@@ -424,16 +395,15 @@
});
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang-nursery/mdBook/issues/628
- document.addEventListener('click', function (e) {
+ document.addEventListener('click', function(e) {
if (langPopup.style.display === 'block' && !langToggleButton.contains(e.target) && !langPopup.contains(e.target)) {
hideLangs();
}
});
})();
- </script>
-
+ </script>
-</body>
-</html>
\ No newline at end of file
+ </body>
+</html>
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@horaedb.apache.org
For additional commands, e-mail: commits-help@horaedb.apache.org