You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pegasus.apache.org by gi...@apache.org on 2020/07/21 10:42:16 UTC

[incubator-pegasus-website] branch asf-site created (now b8f92c9)

This is an automated email from the ASF dual-hosted git repository.

git-site-role pushed a change to branch asf-site
in repository https://gitbox.apache.org/repos/asf/incubator-pegasus-website.git.


      at b8f92c9  Automatic Site Publish by Buildbot

This branch includes the following new commits:

     new b8f92c9  Automatic Site Publish by Buildbot

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pegasus.apache.org
For additional commands, e-mail: commits-help@pegasus.apache.org


[incubator-pegasus-website] 01/01: Automatic Site Publish by Buildbot

Posted by gi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

git-site-role pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/incubator-pegasus-website.git

commit b8f92c93cff5555c3fdcb0973f8620810d8542f2
Author: buildbot <us...@infra.apache.org>
AuthorDate: Tue Jul 21 10:42:11 2020 +0000

    Automatic Site Publish by Buildbot
---
 .asf.yaml                                          |    39 +
 .github/workflows/jekyll.yml                       |    16 +
 .gitignore                                         |     6 +
 .pegasus-kv.json                                   |    18 +
 .travis.yml                                        |    11 +
 Gemfile                                            |    10 +
 License.Apache2                                    |   202 +
 License.MIT                                        |    21 +
 _administration/backup-request.md                  |    36 +
 _administration/bad-disk.md                        |    49 +
 _administration/cold-backup.md                     |   278 +
 _administration/compression.md                     |    51 +
 _administration/config.md                          |   374 +
 _administration/deployment.md                      |   124 +
 _administration/duplication.md                     |   205 +
 _administration/experiences.md                     |    90 +
 _administration/index.md                           |    42 +
 _administration/manual-compact.md                  |   148 +
 _administration/membership-change.md               |    64 +
 _administration/meta-recovery.md                   |   258 +
 _administration/monitoring.md                      |   223 +
 _administration/partition-split.md                 |    98 +
 _administration/rebalance.md                       |   332 +
 _administration/remote-commands.md                 |   120 +
 _administration/replica-recovery.md                |    69 +
 _administration/resource-management.md             |    93 +
 _administration/rolling-update.md                  |   111 +
 _administration/table-env.md                       |   130 +
 _administration/table-migration.md                 |    45 +
 _administration/table-soft-delete.md               |   118 +
 _administration/throttling.md                      |    71 +
 _administration/usage-scenario.md                  |    88 +
 _administration/whitelist.md                       |    53 +
 _administration/zk-migration.md                    |   112 +
 _api/geo.md                                        |   320 +
 _api/http.md                                       |   396 +
 _api/index.md                                      |    30 +
 _api/redis.md                                      |   204 +
 _api/single-atomic.md                              |    55 +
 _api/ttl.md                                        |    71 +
 _clients/cpp-client.md                             |   730 ++
 _clients/go-client.md                              |    51 +
 _clients/index.md                                  |    15 +
 _clients/java-client.md                            |  2313 ++++
 _clients/node-client.md                            |   258 +
 _clients/python-client.md                          |   468 +
 _clients/scala-client.md                           |   372 +
 _community/coding-guides.md                        |   143 +
 _community/index.md                                |    55 +
 _community/proposal.md                             |   304 +
 _community/roadmap.md                              |    51 +
 _config.yml                                        |    71 +
 _data/administration_menu.yml                      |    54 +
 _data/api_menu.yml                                 |    13 +
 _data/clients_menu.yml                             |    15 +
 _data/navigation.yml                               |    37 +
 _data/overview_menu.yml                            |    17 +
 _includes/algolia.html                             |    23 +
 _includes/content-toc.html                         |    80 +
 _includes/footer.html                              |    21 +
 _includes/google-analytics.html                    |     8 +
 _includes/head.html                                |    12 +
 _includes/header.html                              |    67 +
 _includes/hero-page.html                           |     9 +
 _includes/latest-posts.html                        |     9 +
 _includes/menubar.html                             |    21 +
 _includes/pagination.html                          |    23 +
 _includes/rating.html                              |    14 +
 _includes/release-modal.html                       |    12 +
 _includes/review.html                              |    27 +
 _includes/tabs.html                                |    18 +
 _includes/toc.html                                 |    95 +
 _layouts/blog.html                                 |    39 +
 _layouts/community.html                            |    39 +
 _layouts/default.html                              |    41 +
 _layouts/front.html                                |    49 +
 _layouts/page.html                                 |     7 +
 _layouts/post.html                                 |    29 +
 _layouts/product-category.html                     |    42 +
 _layouts/product.html                              |    61 +
 _layouts/releases.html                             |   101 +
 _overview/architecture.md                          |    50 +
 _overview/background.md                            |    60 +
 _overview/benchmark.md                             |   243 +
 _overview/compilation.md                           |   181 +
 _overview/data-model.md                            |    43 +
 _overview/index.md                                 |    56 +
 _overview/onebox.md                                |    63 +
 _overview/shell.md                                 |  1121 ++
 _posts/2017-11-21-meta-server-design.md            |   119 +
 _posts/2017-11-21-replica-server-design.md         |   147 +
 _posts/2018-03-07-last_flushed_decree.md           |    41 +
 _posts/2019-04-29-threads-in-pegasus.md            |   111 +
 _posts/2019-06-09-duplication-design.md            |   312 +
 _posts/2019-06-09-how-i-build-pegasus-website.md   |    64 +
 _posts/2020-02-06-partition-split-design.md        |   211 +
 _posts/2020-02-18-bulk-load-design.md              |   280 +
 _posts/2020-06-19-pegasus-2.0.0-is-out.md          |    41 +
 _releases/index.md                                 |    98 +
 _releases/v1.11.4.md                               |    58 +
 _releases/v1.11.5.md                               |    31 +
 _releases/v1.11.6.md                               |    60 +
 _releases/v1.12.0.md                               |    58 +
 _releases/v1.12.1.md                               |    21 +
 _releases/v1.12.2.md                               |    28 +
 _releases/v1.12.3.md                               |    61 +
 _sass/_layout.scss                                 |    61 +
 _sass/_main.scss                                   |    16 +
 _sass/syntax.scss                                  |   243 +
 assets/css/app.scss                                |     5 +
 assets/images/benchmark/5-node-read.png            |   Bin 0 -> 58720 bytes
 assets/images/benchmark/5-node-write.png           |   Bin 0 -> 53890 bytes
 assets/images/benchmark/500-limit-auto-set.png     |   Bin 0 -> 33504 bytes
 assets/images/benchmark/500-limit-set.png          |   Bin 0 -> 42800 bytes
 assets/images/benchmark/io-limit-500MB-auto.png    |   Bin 0 -> 57452 bytes
 assets/images/benchmark/io-limit-500MB.png         |   Bin 0 -> 88865 bytes
 assets/images/benchmark/io-no-limit.png            |   Bin 0 -> 56772 bytes
 assets/images/benchmark/limit.png                  |   Bin 0 -> 71919 bytes
 assets/images/benchmark/no-limit-set.png           |   Bin 0 -> 35954 bytes
 assets/images/benchmark/node-qps-read.png          |   Bin 0 -> 63576 bytes
 assets/images/benchmark/node-qps-write.png         |   Bin 0 -> 55852 bytes
 assets/images/benchmark/partition.png              |   Bin 0 -> 59966 bytes
 assets/images/compression-comparation.png          |   Bin 0 -> 35092 bytes
 assets/images/ddd-diagnose.png                     |   Bin 0 -> 74120 bytes
 assets/images/favicon.ico                          |   Bin 0 -> 1775 bytes
 assets/images/geo_faces.png                        |   Bin 0 -> 510331 bytes
 assets/images/grafana-dashboard-pegasus.png        |   Bin 0 -> 226780 bytes
 assets/images/grafana-getAPIKey.png                |   Bin 0 -> 31309 bytes
 assets/images/grafana-import-panel-upload.png      |   Bin 0 -> 41916 bytes
 assets/images/grafana-import-panel.png             |   Bin 0 -> 49457 bytes
 assets/images/grafana-login.png                    |   Bin 0 -> 55507 bytes
 assets/images/hilbert.png                          |   Bin 0 -> 54978 bytes
 assets/images/nasa-53884-unsplash.jpg              |   Bin 0 -> 499652 bytes
 assets/images/network-partition.png                |   Bin 0 -> 33269 bytes
 assets/images/pegasus-architecture-components.png  |   Bin 0 -> 126310 bytes
 assets/images/pegasus-architecture-overview.png    |   Bin 0 -> 156496 bytes
 assets/images/pegasus-data-model-sample.png        |   Bin 0 -> 109912 bytes
 assets/images/pegasus-data-model.png               |   Bin 0 -> 73897 bytes
 assets/images/pegasus-healthy-status.png           |   Bin 0 -> 200865 bytes
 assets/images/pegasus-icon.png                     |   Bin 0 -> 7122 bytes
 assets/images/pegasus-logo.png                     |   Bin 0 -> 24792 bytes
 assets/images/pegasus-ttl.png                      |   Bin 0 -> 81531 bytes
 assets/images/perfect-failure-detector.png         |   Bin 0 -> 73525 bytes
 assets/images/prometheus-server.png                |   Bin 0 -> 34973 bytes
 assets/images/redis_proxy_arch.png                 |   Bin 0 -> 36247 bytes
 assets/images/s2_cap_1.png                         |   Bin 0 -> 75709 bytes
 assets/images/s2_cap_2.png                         |   Bin 0 -> 231587 bytes
 assets/js/app.js                                   |    36 +
 blogs.md                                           |    10 +
 index.md                                           |     7 +
 node_modules/bulma/CHANGELOG.md                    |  1254 +++
 node_modules/bulma/LICENSE                         |    21 +
 node_modules/bulma/README.md                       |   124 +
 node_modules/bulma/bulma.sass                      |     9 +
 node_modules/bulma/css/bulma.css                   | 10599 +++++++++++++++++++
 node_modules/bulma/css/bulma.css.map               |     1 +
 node_modules/bulma/css/bulma.min.css               |     1 +
 node_modules/bulma/package.json                    |    78 +
 node_modules/bulma/sass/.DS_Store                  |   Bin 0 -> 6148 bytes
 node_modules/bulma/sass/base/_all.sass             |     5 +
 node_modules/bulma/sass/base/generic.sass          |   142 +
 node_modules/bulma/sass/base/helpers.sass          |   281 +
 node_modules/bulma/sass/base/minireset.sass        |    85 +
 node_modules/bulma/sass/components/_all.sass       |    15 +
 node_modules/bulma/sass/components/breadcrumb.sass |    75 +
 node_modules/bulma/sass/components/card.sass       |    79 +
 node_modules/bulma/sass/components/dropdown.sass   |    81 +
 node_modules/bulma/sass/components/level.sass      |    77 +
 node_modules/bulma/sass/components/list.sass       |    39 +
 node_modules/bulma/sass/components/media.sass      |    48 +
 node_modules/bulma/sass/components/menu.sass       |    57 +
 node_modules/bulma/sass/components/message.sass    |    87 +
 node_modules/bulma/sass/components/modal.sass      |   113 +
 node_modules/bulma/sass/components/navbar.sass     |   443 +
 node_modules/bulma/sass/components/pagination.sass |   149 +
 node_modules/bulma/sass/components/panel.sass      |   103 +
 node_modules/bulma/sass/components/tabs.sass       |   151 +
 node_modules/bulma/sass/elements/_all.sass         |    15 +
 node_modules/bulma/sass/elements/box.sass          |    24 +
 node_modules/bulma/sass/elements/button.sass       |   305 +
 node_modules/bulma/sass/elements/container.sass    |    23 +
 node_modules/bulma/sass/elements/content.sass      |   155 +
 node_modules/bulma/sass/elements/form.sass         |     1 +
 node_modules/bulma/sass/elements/icon.sass         |    21 +
 node_modules/bulma/sass/elements/image.sass        |    69 +
 node_modules/bulma/sass/elements/notification.sass |    35 +
 node_modules/bulma/sass/elements/other.sass        |    39 +
 node_modules/bulma/sass/elements/progress.sass     |    67 +
 node_modules/bulma/sass/elements/table.sass        |   127 +
 node_modules/bulma/sass/elements/tag.sass          |   121 +
 node_modules/bulma/sass/elements/title.sass        |    70 +
 node_modules/bulma/sass/form/_all.sass             |     8 +
 node_modules/bulma/sass/form/checkbox-radio.sass   |    21 +
 node_modules/bulma/sass/form/file.sass             |   180 +
 node_modules/bulma/sass/form/input-textarea.sass   |    60 +
 node_modules/bulma/sass/form/select.sass           |    85 +
 node_modules/bulma/sass/form/shared.sass           |    55 +
 node_modules/bulma/sass/form/tools.sass            |   205 +
 node_modules/bulma/sass/grid/_all.sass             |     4 +
 node_modules/bulma/sass/grid/columns.sass          |   504 +
 node_modules/bulma/sass/grid/tiles.sass            |    34 +
 node_modules/bulma/sass/layout/_all.sass           |     5 +
 node_modules/bulma/sass/layout/footer.sass         |     9 +
 node_modules/bulma/sass/layout/hero.sass           |   143 +
 node_modules/bulma/sass/layout/section.sass        |    13 +
 node_modules/bulma/sass/utilities/_all.sass        |     8 +
 node_modules/bulma/sass/utilities/animations.sass  |     5 +
 node_modules/bulma/sass/utilities/controls.sass    |    50 +
 .../bulma/sass/utilities/derived-variables.sass    |    85 +
 node_modules/bulma/sass/utilities/functions.sass   |    62 +
 .../bulma/sass/utilities/initial-variables.sass    |    76 +
 node_modules/bulma/sass/utilities/mixins.sass      |   261 +
 output/2017/11/21/meta-server-design.html          |   401 +
 output/2017/11/21/replica-server-design.html       |   436 +
 output/2018/03/07/last_flushed_decree.html         |   289 +
 output/2019/04/29/threads-in-pegasus.html          |   365 +
 output/2019/06/09/duplication-design.html          |   581 +
 output/2019/06/09/how-i-build-pegasus-website.html |   316 +
 output/2020/02/06/partition-split-design.html      |   501 +
 output/2020/02/18/bulk-load-design.html            |   574 +
 output/2020/06/19/pegasus-2.0.0-is-out.html        |   300 +
 output/License.Apache2                             |   202 +
 output/License.MIT                                 |    21 +
 output/administration/backup-request.html          |   427 +
 output/administration/bad-disk.html                |   424 +
 output/administration/cold-backup.html             |   675 ++
 output/administration/compression.html             |   483 +
 output/administration/config.html                  |   751 ++
 output/administration/deployment.html              |   555 +
 output/administration/duplication.html             |   628 ++
 output/administration/experiences.html             |   504 +
 output/administration/index.html                   |   412 +
 output/administration/manual-compact.html          |   541 +
 output/administration/membership-change.html       |   454 +
 output/administration/meta-recovery.html           |   646 ++
 output/administration/monitoring.html              |   638 ++
 output/administration/partition-split.html         |   490 +
 output/administration/rebalance.html               |   769 ++
 output/administration/remote-commands.html         |   596 ++
 output/administration/replica-recovery.html        |   457 +
 output/administration/resource-management.html     |   466 +
 output/administration/rolling-update.html          |   532 +
 output/administration/table-env.html               |   642 ++
 output/administration/table-migration.html         |   420 +
 output/administration/table-soft-delete.html       |   511 +
 output/administration/throttling.html              |   457 +
 output/administration/usage-scenario.html          |   478 +
 output/administration/whitelist.html               |   439 +
 output/administration/zk-migration.html            |   499 +
 output/api/geo.html                                |   967 ++
 output/api/http.html                               |   685 ++
 output/api/index.html                              |   310 +
 output/api/redis.html                              |   501 +
 output/api/single-atomic.html                      |   338 +
 output/api/ttl.html                                |   365 +
 output/assets/css/app.css                          |  3477 ++++++
 output/assets/css/app.css.map                      |   124 +
 output/assets/images/benchmark/5-node-read.png     |   Bin 0 -> 58720 bytes
 output/assets/images/benchmark/5-node-write.png    |   Bin 0 -> 53890 bytes
 .../assets/images/benchmark/500-limit-auto-set.png |   Bin 0 -> 33504 bytes
 output/assets/images/benchmark/500-limit-set.png   |   Bin 0 -> 42800 bytes
 .../images/benchmark/io-limit-500MB-auto.png       |   Bin 0 -> 57452 bytes
 output/assets/images/benchmark/io-limit-500MB.png  |   Bin 0 -> 88865 bytes
 output/assets/images/benchmark/io-no-limit.png     |   Bin 0 -> 56772 bytes
 output/assets/images/benchmark/limit.png           |   Bin 0 -> 71919 bytes
 output/assets/images/benchmark/no-limit-set.png    |   Bin 0 -> 35954 bytes
 output/assets/images/benchmark/node-qps-read.png   |   Bin 0 -> 63576 bytes
 output/assets/images/benchmark/node-qps-write.png  |   Bin 0 -> 55852 bytes
 output/assets/images/benchmark/partition.png       |   Bin 0 -> 59966 bytes
 output/assets/images/compression-comparation.png   |   Bin 0 -> 35092 bytes
 output/assets/images/ddd-diagnose.png              |   Bin 0 -> 74120 bytes
 output/assets/images/favicon.ico                   |   Bin 0 -> 1775 bytes
 output/assets/images/geo_faces.png                 |   Bin 0 -> 510331 bytes
 output/assets/images/grafana-dashboard-pegasus.png |   Bin 0 -> 226780 bytes
 output/assets/images/grafana-getAPIKey.png         |   Bin 0 -> 31309 bytes
 .../assets/images/grafana-import-panel-upload.png  |   Bin 0 -> 41916 bytes
 output/assets/images/grafana-import-panel.png      |   Bin 0 -> 49457 bytes
 output/assets/images/grafana-login.png             |   Bin 0 -> 55507 bytes
 output/assets/images/hilbert.png                   |   Bin 0 -> 54978 bytes
 output/assets/images/nasa-53884-unsplash.jpg       |   Bin 0 -> 499652 bytes
 output/assets/images/network-partition.png         |   Bin 0 -> 33269 bytes
 .../images/pegasus-architecture-components.png     |   Bin 0 -> 126310 bytes
 .../images/pegasus-architecture-overview.png       |   Bin 0 -> 156496 bytes
 output/assets/images/pegasus-data-model-sample.png |   Bin 0 -> 109912 bytes
 output/assets/images/pegasus-data-model.png        |   Bin 0 -> 73897 bytes
 output/assets/images/pegasus-healthy-status.png    |   Bin 0 -> 200865 bytes
 output/assets/images/pegasus-icon.png              |   Bin 0 -> 7122 bytes
 output/assets/images/pegasus-logo.png              |   Bin 0 -> 24792 bytes
 output/assets/images/pegasus-ttl.png               |   Bin 0 -> 81531 bytes
 output/assets/images/perfect-failure-detector.png  |   Bin 0 -> 73525 bytes
 output/assets/images/prometheus-server.png         |   Bin 0 -> 34973 bytes
 output/assets/images/redis_proxy_arch.png          |   Bin 0 -> 36247 bytes
 output/assets/images/s2_cap_1.png                  |   Bin 0 -> 75709 bytes
 output/assets/images/s2_cap_2.png                  |   Bin 0 -> 231587 bytes
 output/assets/js/app.js                            |    36 +
 output/blogs.html                                  |   504 +
 output/clients/cpp-client.html                     |  1015 ++
 output/clients/go-client.html                      |   338 +
 output/clients/index.html                          |   311 +
 output/clients/java-client.html                    |  2932 +++++
 output/clients/node-client.html                    |   558 +
 output/clients/python-client.html                  |   755 ++
 output/clients/scala-client.html                   |   672 ++
 output/community/coding-guides.html                |   380 +
 output/community/index.html                        |   290 +
 output/community/proposal.html                     |   601 ++
 output/community/roadmap.html                      |   302 +
 output/feed.xml                                    |  1372 +++
 output/index.html                                  |   223 +
 output/overview/architecture.html                  |   345 +
 output/overview/background.html                    |   359 +
 output/overview/benchmark.html                     |  1041 ++
 output/overview/compilation.html                   |   462 +
 output/overview/data-model.html                    |   334 +
 output/overview/index.html                         |   353 +
 output/overview/onebox.html                        |   340 +
 output/overview/shell.html                         |  1709 +++
 output/package-lock.json                           |    11 +
 output/releases/index.html                         |   747 ++
 output/releases/v1.11.4.md                         |    58 +
 output/releases/v1.11.5.md                         |    31 +
 output/releases/v1.11.6.md                         |    60 +
 output/releases/v1.12.0.md                         |    58 +
 output/releases/v1.12.1.md                         |    21 +
 output/releases/v1.12.2.md                         |    28 +
 output/releases/v1.12.3.md                         |    61 +
 output/robots.txt                                  |     1 +
 output/sitemap.xml                                 |   253 +
 package-lock.json                                  |    11 +
 329 files changed, 72812 insertions(+)

diff --git a/.asf.yaml b/.asf.yaml
new file mode 100644
index 0000000..5ec6ed6
--- /dev/null
+++ b/.asf.yaml
@@ -0,0 +1,39 @@
+#  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.
+
+github:
+  description: "Apache Pegasus Website"
+  homepage: https://pegasus.apache.org/
+  features:
+    issues: false
+    projects: false
+    wiki: false
+  labels:
+    - distributed-database
+    - key-value-store
+    - c-plus-plus
+  enabled_merge_buttons:
+    squash:  true
+    merge:   false
+    rebase:  false
+
+publish:
+  whoami: asf-site
+
+jekyll:
+  whoami: master
+  target: asf-site
diff --git a/.github/workflows/jekyll.yml b/.github/workflows/jekyll.yml
new file mode 100644
index 0000000..edec522
--- /dev/null
+++ b/.github/workflows/jekyll.yml
@@ -0,0 +1,16 @@
+name: Jekyll site CI
+
+on: [push]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Build the site in the jekyll/builder container
+      run: |
+        docker run \
+        -v ${{ github.workspace }}:/srv/jekyll -v ${{ github.workspace }}/_site:/srv/jekyll/_site \
+        jekyll/builder:latest /bin/bash -c "chmod 777 /srv/jekyll && jekyll build --future"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..10667f0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+*.gem
+.bundle
+.sass-cache
+_site
+Gemfile.lock
+.jekyll-cache
\ No newline at end of file
diff --git a/.pegasus-kv.json b/.pegasus-kv.json
new file mode 100644
index 0000000..8f1f06d
--- /dev/null
+++ b/.pegasus-kv.json
@@ -0,0 +1,18 @@
+{
+  "index_name": "pegasus-kv",
+  "start_urls": [
+    "https://pegasus-kv.github.io/"
+  ],
+  "selectors": {
+    "lvl0": {
+      "selector": ".title",
+      "global": true
+    },
+    "lvl1": ".content h1",
+    "lvl2": ".content h2",
+    "lvl3": ".content h3",
+    "lvl4": ".content h4",
+    "lvl5": ".content h5",
+    "text": ".content p, .content li, .content p"
+  }
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..1aadc37
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,11 @@
+install:
+  - sudo apt-get install jq
+
+services:
+  - docker
+
+branches:
+  only:
+    - master
+
+script: docker run -it -e "APPLICATION_ID=${APPLICATION_ID}" -e "API_KEY=${API_KEY}" -e "CONFIG=$(cat .pegasus-kv.json | jq -r tostring)" algolia/docsearch-scraper
diff --git a/Gemfile b/Gemfile
new file mode 100755
index 0000000..2a4deb2
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+source "https://gems.ruby-china.com/"
+
+group :jekyll_plugins do
+    gem "jekyll-feed", "~> 0.6"
+    gem "jekyll-sitemap"
+    gem "jekyll-paginate"
+    gem "jekyll-seo-tag"
+end
diff --git a/License.Apache2 b/License.Apache2
new file mode 100644
index 0000000..d6790fe
--- /dev/null
+++ b/License.Apache2
@@ -0,0 +1,202 @@
+
+                             Apache License
+                       Version 2.0, January 2004
+                    http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+  "License" shall mean the terms and conditions for use, reproduction,
+  and distribution as defined by Sections 1 through 9 of this document.
+
+  "Licensor" shall mean the copyright owner or entity authorized by
+  the copyright owner that is granting the License.
+
+  "Legal Entity" shall mean the union of the acting entity and all
+  other entities that control, are controlled by, or are under common
+  control with that entity. For the purposes of this definition,
+  "control" means (i) the power, direct or indirect, to cause the
+  direction or management of such entity, whether by contract or
+  otherwise, or (ii) ownership of fifty percent (50%) or more of the
+  outstanding shares, or (iii) beneficial ownership of such entity.
+
+  "You" (or "Your") shall mean an individual or Legal Entity
+  exercising permissions granted by this License.
+
+  "Source" form shall mean the preferred form for making modifications,
+  including but not limited to software source code, documentation
+  source, and configuration files.
+
+  "Object" form shall mean any form resulting from mechanical
+  transformation or translation of a Source form, including but
+  not limited to compiled object code, generated documentation,
+  and conversions to other media types.
+
+  "Work" shall mean the work of authorship, whether in Source or
+  Object form, made available under the License, as indicated by a
+  copyright notice that is included in or attached to the work
+  (an example is provided in the Appendix below).
+
+  "Derivative Works" shall mean any work, whether in Source or Object
+  form, that is based on (or derived from) the Work and for which the
+  editorial revisions, annotations, elaborations, or other modifications
+  represent, as a whole, an original work of authorship. For the purposes
+  of this License, Derivative Works shall not include works that remain
+  separable from, or merely link (or bind by name) to the interfaces of,
+  the Work and Derivative Works thereof.
+
+  "Contribution" shall mean any work of authorship, including
+  the original version of the Work and any modifications or additions
+  to that Work or Derivative Works thereof, that is intentionally
+  submitted to Licensor for inclusion in the Work by the copyright owner
+  or by an individual or Legal Entity authorized to submit on behalf of
+  the copyright owner. For the purposes of this definition, "submitted"
+  means any form of electronic, verbal, or written communication sent
+  to the Licensor or its representatives, including but not limited to
+  communication on electronic mailing lists, source code control systems,
+  and issue tracking systems that are managed by, or on behalf of, the
+  Licensor for the purpose of discussing and improving the Work, but
+  excluding communication that is conspicuously marked or otherwise
+  designated in writing by the copyright owner as "Not a Contribution."
+
+  "Contributor" shall mean Licensor and any individual or Legal Entity
+  on behalf of whom a Contribution has been received by Licensor and
+  subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  copyright license to reproduce, prepare Derivative Works of,
+  publicly display, publicly perform, sublicense, and distribute the
+  Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  (except as stated in this section) patent license to make, have made,
+  use, offer to sell, sell, import, and otherwise transfer the Work,
+  where such license applies only to those patent claims licensable
+  by such Contributor that are necessarily infringed by their
+  Contribution(s) alone or by combination of their Contribution(s)
+  with the Work to which such Contribution(s) was submitted. If You
+  institute patent litigation against any entity (including a
+  cross-claim or counterclaim in a lawsuit) alleging that the Work
+  or a Contribution incorporated within the Work constitutes direct
+  or contributory patent infringement, then any patent licenses
+  granted to You under this License for that Work shall terminate
+  as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+  Work or Derivative Works thereof in any medium, with or without
+  modifications, and in Source or Object form, provided that You
+  meet the following conditions:
+
+  (a) You must give any other recipients of the Work or
+      Derivative Works a copy of this License; and
+
+  (b) You must cause any modified files to carry prominent notices
+      stating that You changed the files; and
+
+  (c) You must retain, in the Source form of any Derivative Works
+      that You distribute, all copyright, patent, trademark, and
+      attribution notices from the Source form of the Work,
+      excluding those notices that do not pertain to any part of
+      the Derivative Works; and
+
+  (d) If the Work includes a "NOTICE" text file as part of its
+      distribution, then any Derivative Works that You distribute must
+      include a readable copy of the attribution notices contained
+      within such NOTICE file, excluding those notices that do not
+      pertain to any part of the Derivative Works, in at least one
+      of the following places: within a NOTICE text file distributed
+      as part of the Derivative Works; within the Source form or
+      documentation, if provided along with the Derivative Works; or,
+      within a display generated by the Derivative Works, if and
+      wherever such third-party notices normally appear. The contents
+      of the NOTICE file are for informational purposes only and
+      do not modify the License. You may add Your own attribution
+      notices within Derivative Works that You distribute, alongside
+      or as an addendum to the NOTICE text from the Work, provided
+      that such additional attribution notices cannot be construed
+      as modifying the License.
+
+  You may add Your own copyright statement to Your modifications and
+  may provide additional or different license terms and conditions
+  for use, reproduction, or distribution of Your modifications, or
+  for any such Derivative Works as a whole, provided Your use,
+  reproduction, and distribution of the Work otherwise complies with
+  the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+  any Contribution intentionally submitted for inclusion in the Work
+  by You to the Licensor shall be under the terms and conditions of
+  this License, without any additional terms or conditions.
+  Notwithstanding the above, nothing herein shall supersede or modify
+  the terms of any separate license agreement you may have executed
+  with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+  names, trademarks, service marks, or product names of the Licensor,
+  except as required for reasonable and customary use in describing the
+  origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+  agreed to in writing, Licensor provides the Work (and each
+  Contributor provides its Contributions) on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+  implied, including, without limitation, any warranties or conditions
+  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+  PARTICULAR PURPOSE. You are solely responsible for determining the
+  appropriateness of using or redistributing the Work and assume any
+  risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+  whether in tort (including negligence), contract, or otherwise,
+  unless required by applicable law (such as deliberate and grossly
+  negligent acts) or agreed to in writing, shall any Contributor be
+  liable to You for damages, including any direct, indirect, special,
+  incidental, or consequential damages of any character arising as a
+  result of this License or out of the use or inability to use the
+  Work (including but not limited to damages for loss of goodwill,
+  work stoppage, computer failure or malfunction, or any and all
+  other commercial damages or losses), even if such Contributor
+  has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+  the Work or Derivative Works thereof, You may choose to offer,
+  and charge a fee for, acceptance of support, warranty, indemnity,
+  or other liability obligations and/or rights consistent with this
+  License. However, in accepting such obligations, You may act only
+  on Your own behalf and on Your sole responsibility, not on behalf
+  of any other Contributor, and only if You agree to indemnify,
+  defend, and hold each Contributor harmless for any liability
+  incurred by, or claims asserted against, such Contributor by reason
+  of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+  To apply the Apache License to your work, attach the following
+  boilerplate notice, with the fields enclosed by brackets "[]"
+  replaced with your own identifying information. (Don't include
+  the brackets!)  The text should be enclosed in the appropriate
+  comment syntax for the file format. We also recommend that a
+  file or class name and description of purpose be included on the
+  same "printed page" as the copyright notice for easier
+  identification within third-party archives.
+
+Copyright 2019 Xiaomi, Inc. All rights reserved.
+
+Licensed 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.
\ No newline at end of file
diff --git a/License.MIT b/License.MIT
new file mode 100644
index 0000000..c95d3ab
--- /dev/null
+++ b/License.MIT
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 chrisrhymes
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/_administration/backup-request.md b/_administration/backup-request.md
new file mode 100644
index 0000000..3eb0e1b
--- /dev/null
+++ b/_administration/backup-request.md
@@ -0,0 +1,36 @@
+---
+title: Backup Request
+layout: page
+menubar: administration_menu
+---
+
+# 背景
+在当前的Pegasus实现中,由于向secondary读取会导致不一致的情况发生,所以目前Pegasus仅仅支持对primary副本的读取。但是在某些情况下(例如:负载均衡、热点写入等)经常会导致primary不稳定。所以我们希望在primary不稳定时能够读取secondary,通过牺牲部分强一致性来降低读请求的长尾并提高系统的可用性。backup request便是用来实现此功能的。
+
+# 设计实现
+
+backup reqeust的实现原理比较简单:对于读操作(目前写操作不支持backup request),当client向primary发送请求后,如果经过一段时间延时(通常是p999)其response仍然没有返回,则随机选择一台secondary并向其发送backup request。最后获取最快返回来的response进行处理。
+
+这里发送secondary请求的延时我们建议选择p999,因为backup request操作是用来实现消除长尾的,并不是提升集群性能的。如果将该值设置过低,则会由于backup request的请求量过大而导致集群压力增大(假设选择p50作为其延时,这样便会有50%的请求向secondary发送请求,系统负载便会增大50%)。
+
+# 如何使用
+在Pegasus java client v2.0.0中,我们增加了一个接口,通过该接口可以打开某个表的backup reqeust功能。其实现如下:
+```java
+public PegasusTableInterface openTable(String tableName, int backupRequestDelayMs) throws PException;
+```
+
+相比于老版本的`openTable`接口,我们增加了一个`backupRequestDelayMs`参数。这个参数便是上文所指的时延,即:向primary发送请求,如果过了`backupRequestDelayMs`毫秒response仍没有返回,则向secondary发送backup request。需要注意的是,`backupRequestDelayMs <= 0`代表禁用backup reqeust功能。
+
+另外在老版本的`openTable`接口中,backup request功能默认是关闭的。
+
+# 性能测试
+
+下面表格里展示了是否打开backup request的性能对比,这里我们选取了未打开backup request时读请求的p999时间作为backup request的delay时间(138ms)。数据显示,打开backup request之后get请求的p999时延**基本没有变化**,而p9999时延却有了**数倍的降低**。
+
+另外,由于delay时间设置的是p999时间,大约1000个请求里只有1个请求会发送backup request,因此额外请求量(也就是开启backup request的额外开销)比例在0.1%左右。依此类推,若想要降低P999时延,则可以将 `backupRequestDelayMs` 设置为P99延迟,由此会增加1%的额外读流量。
+
+|  test case   | enable backup request  |  read p9999  |  
+| ---- | ---- | ---- |
+| 3-clients 15-threads | no | 988671 |
+| 3-clients 15-threads | yes| 153599 |
+
diff --git a/_administration/bad-disk.md b/_administration/bad-disk.md
new file mode 100644
index 0000000..0d91a87
--- /dev/null
+++ b/_administration/bad-disk.md
@@ -0,0 +1,49 @@
+---
+title: 坏盘检修
+layout: page
+menubar: administration_menu
+---
+
+磁盘故障时有发生,通常有下列检查方式:
+
+- 可能是某节点的延迟明显高于其他节点,追其原因,如果看到某个 SSD 的 IO await 明显较高,
+  那基本说明该磁盘是“慢盘”。
+
+- 平时的周期运维检修也容易发现潜在的磁盘故障。
+
+在Pegasus中,我们如何进行坏盘维修的操作?
+
+## 坏盘黑名单
+
+Pegasus 支持磁盘黑名单,如果你要下线某块磁盘,首先要把它定义在其所在 Replica 节点的黑名单文件中,
+黑名单文件的所在路径依据配置:
+
+```ini
+[replication]
+    data_dirs_black_list_file = /home/work/.pegasus_data_dirs_black_list
+```
+
+接着你登录对应节点,编辑 /home/work/.pegasus_data_dirs_black_list:
+
+```txt
+/home/work/ssd2
+/home/work/ssd3
+```
+
+上面标注磁盘 ssd2 与 ssd3 需要下线。
+
+## 重启节点
+
+在你标注好坏盘名单后,你可以通过 [高可用升级](rolling-update) 单独重启对应节点的服务进程。
+通常你在程序日志中能够看到下列记录,表示黑名单内的磁盘的确被忽略了:
+
+```log
+D2019-07-10 21:54:28.879 (1562766868879176673 9e8d) replica.default0.00009e5b00010001: replication_common.cpp:177:initialize(): data_dirs_black_list_file[/home/work/.pegasus_data_dirs_black_list] found, apply it
+D2019-07-10 21:54:28.879 (1562766868879300907 9e8d) replica.default0.00009e5b00010001: replication_common.cpp:194:initialize(): black_list[1] = [/home/work/ssd2/]
+D2019-07-10 21:54:28.879 (1562766868879312394 9e8d) replica.default0.00009e5b00010001: replication_common.cpp:194:initialize(): black_list[2] = [/home/work/ssd3/]
+W2019-07-10 21:54:28.879 (1562766868879404635 9e8d) replica.default0.00009e5b00010001: replication_common.cpp:218:initialize(): replica data dir /home/work/ssd2/pegasus/c3tst-dup2/replica is in black list, ignore it
+W2019-07-10 21:54:28.879 (1562766868879411121 9e8d) replica.default0.00009e5b00010001: replication_common.cpp:218:initialize(): replica data dir /home/work/ssd3/pegasus/c3tst-dup2/replica is in black list, ignore it
+D2019-07-10 21:54:28.879 (1562766868879415865 9e8d) replica.default0.00009e5b00010001: replication_common.cpp:220:initialize(): data_dirs[0] = /home/work/ssd4/pegasus/c3tst-dup2/replica, tag = ssd4
+D2019-07-10 21:54:28.879 (1562766868879422843 9e8d) replica.default0.00009e5b00010001: replication_common.cpp:220:initialize(): data_dirs[1] = /home/work/ssd5/pegasus/c3tst-dup2/replica, tag = ssd5
+D2019-07-10 21:54:28.879 (1562766868879428846 9e8d) replica.default0.00009e5b00010001: replication_common.cpp:220:initialize(): data_dirs[2] = /home/work/ssd6/pegasus/c3tst-dup2/replica, tag = ssd6
+```
diff --git a/_administration/cold-backup.md b/_administration/cold-backup.md
new file mode 100755
index 0000000..902a743
--- /dev/null
+++ b/_administration/cold-backup.md
@@ -0,0 +1,278 @@
+---
+title: 冷备份
+layout: page
+menubar: administration_menu
+---
+
+## 介绍
+
+Pegasus的冷备份功能用来将Pegasus中的数据定期生成快照文件,并备份到其他存储介质上,从而为数据容灾多提供一层保障。但由于备份的是某个时间点的数据快照文件,所以冷备份并不保证可以保留所有最新的数据,也就是说,恢复的时候可能会丢失最近一段时间的数据。
+
+具体来看,冷备份过程要涉及到如下一些参数:
+
+* 存储介质(backup_provider): 指其他的文件存储系统或服务,如本地文件系统或者HDFS。
+* 数据冷备份的周期(backup_interval):周期的长短决定了备份数据的覆盖范围。如果周期是1个月,那么恢复数据时,就可能只恢复一个月之前的数据。但如果周期设的太短,备份就会太频繁,从而使得备份开销很大。在小米内部,冷备份的周期通常是1天。
+* 保留的冷备份个数(backup_history_count):保留的备份个数越多,存储的空间开销就越大。在小米内部,一般保留最近的3个冷备份。
+* 进行冷备份的表的集合(backup_app_ids):并不是所有的表都值得进行冷备份。在小米内部,对于经常重灌全量数据的表,我们是不进行冷备份的。
+
+在Pegasus中,以上这几个参数的组合称为一个冷备份策略(backup_policy)。数据的冷备份就行按照policy为单位进行的。
+
+## 存储介质
+
+目前Pegasus支持本地文件系统和FDS两种冷备份介质。如果需要支持更多的存储介质(如S3),请联系我们或者直接把pull request砸过来。
+
+### FDS
+
+FDS是小米生态云提供的存储产品,目前仅面向小米的生态链业务开放。更多详细的介绍[请戳这里](https://cnbj6.cloud.mi.com/#/index/product/fds?_k=rq2znr)。
+
+### 本地文件系统
+
+通过使用Pegasus本地文件系统(local service)的备份接口,可以把Pegasus的数据备份到某个目录下。如果第三方存储介质支持以nfs或者Linux Fuse的方式挂载到本地某个目录下的话,该介质就可以通过local service来作为pegasus的备份介质。目前HDFS就是通过fuse的方式来支持的。
+
+我们接下来会以本地文件系统为例,介绍冷备的创建和恢复的相关操作。然后,我们会介绍如何用fuse的方式将HDFS挂载到本地目录下。
+
+## 怎样开始冷备份
+
+### 配置
+需要先设定一些参数来配置系统的冷备份功能:
+
+1. 配置 [meta_server].cold_backup_disable :
+
+   ```
+   [meta_server]
+   ...
+   ;; 设置为false, 不然冷备份功能不会打开
+   cold_backup_disabled = false
+   ...
+   ```
+
+2. 配置 [apps.meta].pools 和 [apps.replica].pools :
+
+   ```
+   [apps.meta]
+   ...
+   ;; 需增加THREAD_POOL_LOCAL_SERVICE,不然会没有可用的线程池而coredump
+   pools = ...,THREAD_POOL_LOCAL_SERVICE
+   
+   [apps.replica]
+   ...
+   ;; 需增加THREAD_POOL_LOCAL_SERVICE,不然会没有可用的线程池而coredump
+   pools = ...,THREAD_POOL_LOCAL_SERVICE
+   ```
+
+3. 配置 [replication].cold_backup_root 和 [replication].max_concurrent_uploading_file_count :
+
+   ```
+   [replication]
+   ;; 这个参数指定了冷备数据要保存到存储介质的什么文件夹下,一般建议填写集群名字
+   cold_backup_root = onebox
+   ;; 这个参数指定了冷备份上传文件的并发数,根据自己的网络情况进行设置,建议参数为5
+   max_concurrent_uploading_file_count = 5
+   ```
+
+4. 添加或修改 [block_service.xxxx] 配置段:
+
+   ```
+   ;; 这样的一个section, 就指定了一种存储介质。可以按照自己的需求添加
+   [block_service.my_backup_media]
+   ;; 指定这种存储介质使用的存储接口是local_service
+   type = local_service
+   ;; 存储介质的初始化参数。对于local_service, 该参数要设置成一个本地目录,表示所有的备份全在此目录下。
+   args = /home/weijiesun/pegasus_cold_backup
+   ```
+
+在上面的配置中,一定要区分清楚[replication].cold_backup_root和[block_service.my_backup_media].args所配置的两个路径:
+* 前者指定了数据在某种存储介质下的存放目录。
+* 后者是存储介质本身的初始化参数,是和type相关的。对于local_service而言,这个参数是一个绝对路径。换种说法,**本地文件系统的某个目录**,就是一种存储介质。
+* 对于当前的配置情况而言,冷备数据会保存到`/home/weijiesun/pegasus_cold_backup/onebox`这个目录下。
+
+### 创建冷备份策略
+
+通过[Shell工具的add_backup_policy命令](/overview/shell#冷备份管理)可以创建一个冷备份策略:
+
+```
+>>> add_backup_policy -p policy1 -b my_backup_media -a 1 -i 300 -s 16:00 -c 3
+add backup policy succeed, policy_name = policy1
+```
+
+几个参数的含义如下:
+* -p: policy的名字,创建后就不能修改,某个集群只能有唯一的名字
+* -b: 存储介质的名称。系统会按指定的名称寻找对应的section, 并初始化存储介质。在上例中,系统从[block_service.my_backup_media]这一配置section中进行初始化。
+* -a: policy下app_id的集合。如果有多个,用逗号隔开。
+* -i: 备份周期,以秒为单位。
+* -s: 开始时间,精确到分钟。这个参数含义略显复杂,后文专门介绍。
+* -c: 冷备份的保留个数
+
+对于-s参数指定的备份开始时间,其含义和-i指定备份周期是有联系的
+* 如果备份周期是86400秒,也就是一天。那么-s就表示备份每天的启动时间。
+* 如果备份周期不是一整天。那么开始时间就代表的着第一次备份的开启时间点。而这个时间点,指的是当前时间朝后推的第一个相对的时间点。
+
+比如上面的"-i 60 -s 8:00",就是说下次备份从8:00开始,然后每60秒开启一轮备份。而"-i 86400 -s 8:30",指的是每天的8:30开启一轮备份。
+
+对于-a参数指定的app_id。目前pegasus并没有强制约束某个app_id只能属于一个policy, 但原则上不建议把同一个表加到不同的policy中。
+
+### 查询冷备份策略
+可以用ls_backup_policy列举系统当前所有的策略:
+```
+>>> ls_backup_policy
+[1]
+    name                 : another_policy
+    backup_provider_type : my_backup_media
+    backup_interval      : 600s
+    app_ids              : {2}
+    start_time           : 16:10
+    status               : enabled
+    backup_history_count : 2
+
+[2]
+    name                 : policy1
+    backup_provider_type : my_backup_media
+    backup_interval      : 300s
+    app_ids              : {1}
+    start_time           : 16:00
+    status               : enabled
+    backup_history_count : 3
+
+
+ls backup policy succeed
+```
+
+也可以用query_backup_policy查询某个指定policy的信息:
+```
+>>> query_backup_policy -p policy1
+policy_info:
+    name                 : policy1
+    backup_provider_type : my_backup_media
+    backup_interval      : 300s
+    app_ids              : {1}
+    start_time           : 16:00
+    status               : enabled
+    backup_history_count : 3
+
+backup_infos:
+[1]
+    id         : 1533801635364
+    start_time : 2018-08-09 16:00:35
+    end_time   : 2018-08-09 16:01:15
+    app_ids    : {1}
+
+query backup policy succeed
+```
+如上图所示,当query某个特定的policy时,该policy下已经完成或正在进行的备份也会显示出来。
+
+### 修改冷备份策略
+冷备份策略的一些参数,可以用modify_backup_policy命令进行修改:
+```
+>>> modify_backup_policy -p policy1 -s 17:00 -i 600
+Modify policy result: ERR_OK
+>>> query_backup_policy -p policy1
+policy_info:
+    name                 : policy1
+    backup_provider_type : my_backup_media
+    backup_interval      : 600s
+    app_ids              : {1}
+    start_time           : 17:00
+    status               : enabled
+    backup_history_count : 3
+
+backup_infos:
+.....
+query backup policy succeed
+```
+可以修改的参数有:
+* app集合: 通过-a和-r参数进行增删app_id
+* backup_interval: 通过-i修改周期
+* start_time: 通过-s参数修改开始时间
+
+### 禁用和重新开始冷备份策略
+
+可以用disable_backup_policy和enable_backup_policy来对某个policy进行禁用和开启
+```
+>>> disable_backup_policy -p policy1
+disable policy result: ERR_OK
+>>> ls_backup_policy
+..........
+[2]
+    name                 : policy1
+    backup_provider_type : my_backup_media
+    backup_interval      : 600s
+    app_ids              : {1}
+    start_time           : 17:00
+    status               : disabled
+    backup_history_count : 3
+
+
+ls backup policy succeed
+>>> enable_backup_policy -p policy1
+enable policy result: ERR_OK
+>>> ls_backup_policy
+......
+[2]
+    name                 : policy1
+    backup_provider_type : my_backup_media
+    backup_interval      : 600s
+    app_ids              : {1}
+    start_time           : 17:00
+    status               : enabled
+    backup_history_count : 3
+
+
+ls backup policy succeed
+>>>
+```
+
+### 查看备份的结果
+
+对于我们配置好的local_service, 备份存在/home/weijiesun/pegasus_cold_backup/onebox的目录下。
+
+这里稍微做展示,详细的文件内容大家可以自行翻看:
+```
+weijiesun@weijiesun-kubuntu ~/pegasus_cold_backup/onebox/policy1 $ pwd
+/home/weijiesun/pegasus_cold_backup/onebox/policy1
+weijiesun@weijiesun-kubuntu ~/pegasus_cold_backup/onebox/policy1 $ ls
+1533802216079  1533802236170  1533802256259
+```
+
+## 从冷备中恢复数据
+
+可以用restore_app从冷备中恢复表数据
+```
+>>> restore_app -c onebox -p policy1 -a temp -i 1 -t 1533802236170 -b my_backup_media -n result
+sleep 1 second to wait complete...
+        new app_id = 3
+```
+其中各个参数的含义如下(这些参数都可以通过在原集群上执行shell命令`query_backup_policy`得到):
+* -c:旧集群的名称
+* -p:旧的policy名称
+* -a:旧表名称
+* -i:旧表的id
+* -t:旧的备份的时间戳编号
+* -b:存储介质名称
+* -n:新表的名称,如果不指定就使用旧名字,如果和新的名字冲突则报错
+* -s:是否跳过损坏的partition, 默认不跳过
+
+对于恢复进度,可以用query_restore_status进行查看
+```
+>>> query_restore_status 3 -d
+pid           progress(%)   restore_status
+0             100           ok
+1             100           ok
+2             100           ok
+3             100           ok
+4             100           ok
+5             100           ok
+6             100           ok
+7             100           ok
+
+the overall progress of restore is 100%
+
+annotations:
+    ok : mean restore complete
+    ongoing : mean restore is under going
+    skip : data on cold backup media is damaged, but skip the damaged partition
+    unknown : invalid, should login server and check it
+```
+
+## 怎样将数据备份到HDFS
+
+采用HDFS fuse即可,可以参考[这篇文章](https://www.jianshu.com/p/1beb5325c6d8)。
diff --git a/_administration/compression.md b/_administration/compression.md
new file mode 100755
index 0000000..512a973
--- /dev/null
+++ b/_administration/compression.md
@@ -0,0 +1,51 @@
+---
+title: 数据压缩
+layout: page
+menubar: administration_menu
+---
+
+# 客户端压缩
+
+请参考 [Java客户端文档#数据序列化](/clients/java-client#数据序列化) 和 [Java客户端文档#数据压缩](/clients/java-client#数据压缩) 。
+
+# 服务端压缩
+
+**把总结放在最前面:**
+* 对于CPU比较空闲的场景,建议采用压缩率高的`zstd`算法。
+* 对于CPU比较繁忙的场景,建议采用综合性能比较优异的`lz4`算法。
+
+Pegasus服务端支持的压缩算法:
+* snappy
+* lz4 (从[v1.11.2版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.11.2)开始支持)
+* zstd (从[v1.11.2版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.11.2)开始支持)
+
+通过[配置文件](config)来配置压缩算法,譬如:
+```ini
+[pegasus.server]
+    rocksdb_compression_type = lz4
+```
+
+不同压缩算法的比较(数据来自[zstd官方的benchmark](https://facebook.github.io/zstd/)):
+
+Compressor name | Ratio | Compression(MB/s) | Decompress(MB/s)
+-- | -- | -- | --
+zstd 1.3.4 -1 | 2.877 | 470 | 1380
+zlib 1.2.11 -1 | 2.743 | 110 | 400
+brotli 1.0.2 -0 | 2.701 | 410 | 430
+quicklz 1.5.0 -1 | 2.238 | 550 | 710
+lzo1x 2.09 -1 | 2.108 | 650 | 830
+lz4 1.8.1 | 2.101 | 750 | 3700
+snappy 1.1.4 | 2.091 | 530 | 1800
+lzf 3.6 -1 | 2.077 | 400 | 860
+
+![compression-comparation.png](/assets/images/compression-comparation.png){:class="img-responsive"}
+
+这个结果与[lz4官方的benchmark](https://github.com/lz4/lz4#benchmarks)也是一致的。
+
+附上[RocksDB的压缩建议](https://github.com/facebook/rocksdb/wiki/Compression):
+> Use options.compression to specify the compression to use. By default it is Snappy. We believe LZ4 is almost always better than Snappy. We leave Snappy as default to avoid unexpected compatibility problems to previous users. LZ4/Snappy is lightweight compression so it usually strikes a good balance between space and CPU usage.
+
+> If you want to further reduce the in-memory and have some free CPU to use, you can try to set a heavy-weight compression in the latter by setting options.bottommost_compression. The bottommost level will be compressed using this compression style. Usually the bottommost level contains majority of the data, so users get almost optimal space setting, without paying CPU for compress all the data ever flowing to any level. We recommend ZSTD. If it is not available, Zlib is the second choice.
+
+> If you want have a lot of free CPU and want to reduce not just space but write amplification too, try to set options.compression to heavy weight compression type. We recommend ZSTD. Use Zlib if it is not available.
+
diff --git a/_administration/config.md b/_administration/config.md
new file mode 100644
index 0000000..372c105
--- /dev/null
+++ b/_administration/config.md
@@ -0,0 +1,374 @@
+---
+title: 配置说明
+layout: page
+menubar: administration_menu
+---
+
+## 配置组成部分
+
+Pegasus的配置为ini格式,主要有以下组成部分:
+
+* core:一个Pegasus Service内核引擎运行时的相关参数配置。
+* network:RPC组件的相关参数配置。
+* 线程池相关:Pegasus进程中启动的各个线程池的相关参数配置。
+* app相关:app是rDSN中的一个概念,可以理解成分布式系统中的“组件”或者“job”,例如Pegasus中的MetaServer、ReplicaServer就各是一个app。一个进程内可以启动多个app,针对每个app,可以分别配置其行为,譬如名字、端口、线程池等。
+* task相关:task也是rDSN中的一个概念,可以理解成“异步任务”。比如一个RPC异步调用、一个异步文件IO操作、一个超时事件,都是一个task。每种task都有定义一个唯一的名字。针对每种task,都可以配置其相关的行为,譬如trace、profiler等。
+* 一致性协议相关:一致性replication协议的相关参数配置。
+* RocksDB相关:Pegasus所依赖的RocksDB的参数配置。
+* 其他杂项:Pegasus中一些组件模块的参数配置,譬如日志、监控、Zookeeper配置等。
+
+配置文件中会涉及到一些rDSN的概念,对这些概念的进一步理解,请参见[rDSN项目](https://github.com/XiaoMi/rdsn)。
+
+下面列举出了Pegasus配置文件的部分说明。这些配置项有些是和client通用的,比如app、task、threadpool等,其他是server端所独有的。要理解这些配置的真正含义,建议先阅读PacificA论文,并了解清楚rDSN项目和Pegasus架构。
+
+## 配置文件部分说明
+
+```ini
+;;;; 各个app配置项的默认模板
+[apps..default]
+run = true
+count = 1
+
+;;;; meta app的配置项
+[apps.meta]
+type = meta
+name = meta
+arguments = 
+;meta的运行端口
+ports = 34601
+
+;meta app运行时需要的线程池
+pools = THREAD_POOL_DEFAULT,THREAD_POOL_META_SERVER,THREAD_POOL_META_STATE,THREAD_POOL_FD,THREAD_POOL_DLOCK,THREAD_POOL_FDS_SERVICE
+run = true
+;meta app的实例个数,每个实例的运行端口依次为ports, ports+1...
+;可以用参数-app_list meta@1的方式启动指定的app
+count = 3 
+
+;;;; replica app的配置项目
+[apps.replica] 
+type = replica
+name = replica
+arguments =
+ports = 34801
+pools = THREAD_POOL_DEFAULT,THREAD_POOL_REPLICATION_LONG,THREAD_POOL_REPLICATION,THREAD_POOL_LOCAL_APP,THREAD_POOL_FD,THREAD_POOL_FDS_SERVICE,THREAD_POOL_COMPACT
+run = true
+count = 1
+
+;;;; pegasus内核引擎运行参数
+[core] 
+;rDSN相关概念,参见rDSN文档
+tool = nativerun
+;rDSN相关概念,参见rDSN文档
+toollets = profiler
+;启动时是否暂停以等待交互输入
+pause_on_start = false
+
+;logging级别
+logging_start_level = LOG_LEVEL_DEBUG 
+;logging的实现类
+logging_factory_name = dsn::tools::simple_logger 
+;进程退出时是否将缓存的日志数据刷出到文件系统
+logging_flush_on_exit = true
+
+;默认的数据文件夹
+data_dir = /home/work/data/pegasus/@cluster@
+
+;;;; 网络相关配置
+[network]
+;负责网络IO的线程个数
+io_service_worker_count = 4
+;每个客户端IP限制的连接数
+conn_threshold_per_ip = 0
+
+;;;; 线程池相关配置的默认模板
+[threadpool..default]
+;线程池的默认线程数
+worker_count = 4
+
+;;;; 线程池THREAD_POOL_REPLICATION的配置
+[threadpool.THREAD_POOL_REPLICATION]
+;线程池名称
+name = replica
+;rDSN相关概念,partitioned = ture表示每个线程有一个自己的任务队列,
+;且task会根据hash分派到特定的线程执行
+partitioned = true 
+;线程在OS中的调度优先级
+worker_priority = THREAD_xPRIORITY_NORMAL
+;线程池中的线程数,如果没有配置则使用默认模板的值
+worker_count = 23
+
+;;;; 线程池XXXXX的相关配置
+[threadpool.XXXXX]
+.....
+
+;;;; meta_server的相关配置
+[meta_server] 
+;MetaServer的地址列表
+server_list = 127.0.0.1:34601,127.0.0.1:34602,127.0.0.1:34603  
+;MetaServer在元数据存储服务上的根目录,
+;一个集群的不同meta_server要配成相同的值,不同的集群用不同的值
+cluster_root = /pegasus/my-cluster 
+;元数据存储服务的实现类
+meta_state_service_type = meta_state_service_zookeeper 
+;元数据存储服务的初始化参数
+meta_state_service_parameters = 
+;分布式锁服务的实现类
+distributed_lock_service_type = distributed_lock_service_zookeeper 
+;分布式锁服务的初始化参数
+distributed_lock_service_parameters = /pegasus/onebox/127.0.0.1 
+;判断一个ReplicaServer是不是稳定运行的时间阈值
+stable_rs_min_running_seconds = 600
+;一个ReplicaServer最多可以崩溃的次数,
+;如果崩溃太频繁,就会上MetaServer的黑名单
+max_succssive_unstable_restart = 5 
+;负载均衡器的实现类
+server_load_balancer_type = greedy_load_balancer
+;当一个secondary被移除后,等待它回来的最长时间阈值
+replica_assign_delay_ms_for_dropouts = 300000
+;如果不可用节点比例太高,MetaServer会进入freezed的保护状态
+node_live_percentage_threshold_for_update = 50
+;如果不可用的节点个数太少,MetaServer也会进入freezed的保护状态
+min_live_node_count_for_unfreeze = 3
+;表删除后在回收站中默认的保留时间
+hold_seconds_for_dropped_app = 604800
+;MetaServer启动时的默认function_level状态,
+;steady表示不进行负载均衡的稳定状态
+meta_function_level_on_start = steady
+;如果为true, 集群启动时就会进入“元数据恢复”流程
+recover_from_replica_server = false
+;一个replica group中最多保留的副本数(可用副本+尸体)
+max_replicas_in_group = 4 
+
+;;;; 集群在bootstrap时默认要创建的表
+[replication.app]
+;表名
+app_name = temp
+;type表征了存储引擎,pegasus表示我们基于rocksdb实现的存储引擎
+app_type = pegasus
+;分片数
+partition_count = 8
+;每个分片的副本个数
+max_replica_count = 3
+;rDSN参数,对于app_type = pegasus需要设置为true
+stateful = true
+package_id = 
+
+;;;; 一致性协议相关配置,很多概念和PacificA相关
+[replication] 
+;shared log存储的文件夹路径
+slog_dir = /home/work/ssd1/pegasus/@cluster@ 
+;replica数据存储的文件夹路径列表,建议一块磁盘配置一个项,
+;tag为磁盘的标记名
+data_dirs = tag1:/home/work/ssd2/pegasus/@cluster@,tag2:/home/work/ssd3/pegasus/@cluster@ 
+;黑名单文件,文件中每行是一个需忽略掉的文件夹,主要用于过滤坏盘
+data_dirs_black_list_file = /home/mi/.pegasus_data_dirs_black_list
+
+;ReplicaServer启动时是否要拒绝掉客户端写
+deny_client_on_start = false
+;ReplicaServer启动时是否等待一段时间后才开始连接MetaServer
+delay_for_fd_timeout_on_start = false 
+;是否打印commit log的调试信息
+verbose_log_on_commit = false 
+;primary会定期生成一个空的写操作以检查group状态,是否禁止该特性
+empty_write_disabled = false
+
+;prepare的超时时间
+prepare_timeout_ms_for_secondaries = 1000
+;给learner发prepare的超时时间
+prepare_timeout_ms_for_potential_secondaries = 3000 
+
+;是否禁止掉客户端写请求的batch功能
+batch_write_disabled = false
+;保留多少个已经commit的写请求在队列中
+staleness_for_commit = 20
+;prepare_list的容量
+max_mutation_count_in_prepare_list = 110 
+;想要成功进行一次写操作,最少需要多少个副本
+mutation_2pc_min_replica_count = 2 
+
+;primary会定期推送group状态给其他成员,是否禁用该特性
+group_check_disabled = false 
+;group check的时间间隔
+group_check_interval_ms = 100000 
+
+;是否禁用定期checkpoint的生成
+checkpoint_disabled = false 
+;checkpoint的尝试触发时间间隔,尝试触发并不一定生成checkpoint
+checkpoint_interval_seconds = 100 
+checkpoint_min_decree_gap = 10000
+;checkpoint的强制触发时间间隔,强制触发会将memtable的数据刷出
+checkpoint_max_interval_hours = 1 
+
+;是否禁用WAL的垃圾回收
+gc_disabled = false
+gc_interval_ms = 30000
+;如果一个replica需要关闭,在内存中保留多长时间
+gc_memory_replica_interval_ms = 300000
+;一个因为IO错误关闭掉的replica,在磁盘保留多长时间
+gc_disk_error_replica_interval_seconds = 172800000
+
+;failure detector是否禁止
+fd_disabled = false
+fd_check_interval_seconds = 5
+fd_beacon_interval_seconds = 3
+fd_lease_seconds = 10
+fd_grace_seconds = 15
+
+;每一个private log多大,超过了该阈值就滚动到下一个文件
+log_private_file_size_mb = 32 
+;private log一个batch的最小容量
+log_private_batch_buffer_kb = 512 
+;private log一个batch的最小条数
+log_private_batch_buffer_count = 512 
+;超过该事件没写private log, 则强制刷一次
+log_private_batch_buffer_flush_interval_ms = 100000 
+log_private_reserve_max_size_mb = 0
+log_private_reserve_max_time_seconds = 0
+
+;shared log多大,超过该阈值就滚动到下一个文件
+log_shared_file_size_mb = 32 
+log_shared_file_count_limit = 32
+log_shared_batch_buffer_kb = 0
+log_shared_force_flush = false
+
+;replica server会定期和meta server同步本机所服务的replica, 
+;该配置表示是否禁止这一特性
+config_sync_disabled = false 
+;同步的时间间隔
+config_sync_interval_ms = 30000 
+
+;meta server跑负载均衡的周期
+lb_interval_ms = 10000 
+
+[pegasus.server]
+;;; rocksdb相关配置
+;是否打印Pegasus中反应RocksDB运行情况的调试日志
+rocksdb_verbose_log = false
+;RocksDB Get操作的慢日志阈值,如果操作时长超过了该数值,
+;那么将会被写入日志。0代表不会写入
+rocksdb_abnormal_get_time_threshold_ns = 100000000
+;RocksDB Get操作的大key日志阈值,如果操作获取的value的长度大于该数值,
+;那么将会被写入日志。0代表不会写入
+rocksdb_abnormal_get_size_threshold = 1000000
+;RocksDB Multi-Get操作的慢日志阈值,如果操作时长超过了该数值,
+;那么将会被写入日志。0代表不会写入
+rocksdb_abnormal_multi_get_time_threshold_ns = 100000000
+;如果RocksDB Multi-Get操作的所有key-value的长度之和大于该数值,
+;那么将会被写入日志。0代表不会写入
+rocksdb_abnormal_multi_get_size_threshold = 10000000
+;如果RocksDB Multi-Get操作的key-value对数量超过了该数值,
+;那么将会被写入日志。0代表不会写入
+rocksdb_abnormal_multi_get_iterate_count_threshold = 1000
+;单个memtable的最大size。
+;一旦memtable大小超过该数值,将会被标记为不可修改,
+;并且会创建一个新的memtable。
+;然后,一个后台线程会把memtable的内容落盘到一个SST文件
+rocksdb_write_buffer_size = 67108864
+;memtable的最大数量,包括active-memtable和immutable-memtable,
+;如果active memtable被填满,并且memtable的总数量大于该数值,
+;那么将会被延缓写入
+rocksdb_max_write_buffer_number = 3
+;后台flush线程数量。flush线程在高优先级的线程池中
+rocksdb_max_background_flushes = 4
+;后台compaction线程数量。compaction线程在低优先级的线程池中
+rocksdb_max_background_compactions = 12
+;RocksDB LSM tree层数
+rocksdb_num_levels = 6 
+;level 1层的文件大小最大为target_file_size_base字节
+rocksdb_target_file_size_base = 67108864
+;每一层文件大小是其上一层的rocksdb_target_file_size_multiplier倍。
+;默认情况下rocksdb_target_file_size_multiplier是1,
+;也就是说每层的文件大小相同
+rocksdb_target_file_size_multiplier = 1
+;level 1层中的所有文件的总大小
+rocksdb_max_bytes_for_level_base = 671088640
+;每一层其所有总文件大小是其上一层的rocksdb_target_file_size_multiplier倍。
+;默认情况下rocksdb_target_file_size_multiplier是1,
+;也就是说每层的文件大小相同。
+rocksdb_target_file_size_multiplier = 10 
+;如果level 0中的文件数量超过了该指定数值,L0->L1 compaction将会被触发
+rocksdb_level0_file_num_compaction_trigger = 4
+;如果level 0中的文件数量超过了该指定数值,那么写入速度将会被降低
+rocksdb_level0_slowdown_writes_trigger = 30
+;如果level 0中的文件数量超过了该指定数值,那么写入将会被禁止
+rocksdb_level0_stop_writes_trigger = 60
+;压缩算法类型, 支持 none,snappy,lz4,zstd几种选项。
+;支持为每一层单独配置压缩算法,用逗号分隔,如:
+;“none,none,snappy,zstd” 表示L0,L1不进行压缩,
+;L2使用snappy压缩,L3往下使用zstd压缩。
+rocksdb_compression_type = lz4 
+;如果该值被设置为true,则表示禁用block cache功能
+rocksdb_disable_table_block_cache = false
+;进程中所有RocksDB实例共享的 Block Cache 内存容量,以 bytes为单位
+rocksdb_block_cache_capacity = 10737418240
+;shard id的bit位数。
+;为了并发操作,block cache被分成很多shard, 
+;数量为2^rocksdb_block_ache_num_shard_bits个
+rocksdb_block_cache_num_shard_bits = -1 
+;如果该值被设置为true,则表示禁用bloom filter功能
+rocksdb_disable_bloom_filter = false
+
+;;; 监控相关配置,部分和小米的开源监控系统open-falcon相关
+perf_counter_cluster_name = onebox
+;监控项的汇报周期
+perf_counter_update_interval_seconds = 10 
+;是否允许直接向service拉去监控项
+perf_counter_enable_stat = true
+;是否允许把所有监控项打印到日志
+perf_counter_enable_logging = false
+;是否允许把监控项推送到falcon
+perf_counter_enable_falcon = false
+
+falcon_host = 127.0.0.1
+falcon_port = 1988
+falcon_path = /v1/push
+
+;;;; task相关配置模板,里面的概念都和rDSN相关
+[task..default]
+is_trace = false
+is_profile = false
+allow_inline = false
+rpc_call_header_format = NET_HDR_DSN
+rpc_call_channel = RPC_CHANNEL_TCP
+rpc_timeout_milliseconds = 5000	
+disk_write_fail_ratio = 0.0
+disk_read_fail_ratio = 0.0
+
+;;;; task RPC_L2_CLIENT_READ的相关配置,
+;选项均继承模板,自定义的部分表示该task需要监控的内容
+[task.RPC_L2_CLIENT_READ] 
+is_profile = true
+profiler::inqueue = false
+profiler::queue = false
+profiler::exec = false
+profiler::qps = false
+profiler::cancelled = false
+profiler::latency.server = false
+
+;;;;;;; 以下是各种各样的杂项配置
+;;;; 监控实现类相关
+[components.pegasus_perf_counter_number_percentile_atomic]
+counter_computation_interval_seconds = 10
+
+;;;; zookeeper相关
+[zookeeper]
+hosts_list = 127.0.0.1:22181
+timeout_ms = 10000
+logfile = zoo.log
+
+;;;; logger的实现类相关
+[tools.simple_logger]
+short_header = false
+fast_flush = false
+max_number_of_log_files_on_disk = 500
+stderr_start_level = LOG_LEVEL_ERROR
+```
+
+## 配置建议
+
+一些配置建议:
+
+* 配置文件中所有需要使用机器名的地方,都建议使用IP地址。
+* 大部分配置项,建议使用默认值。
+* 对于高级用户,可以根据需要自行配置,但是前提是需要理解配置项的作用和影响。
+* 由于文档不能面面俱到,对配置项的作用不太清楚的,可以直接查看源代码。
diff --git a/_administration/deployment.md b/_administration/deployment.md
new file mode 100755
index 0000000..73343d2
--- /dev/null
+++ b/_administration/deployment.md
@@ -0,0 +1,124 @@
+---
+title: 集群部署
+layout: page
+menubar: administration_menu
+---
+
+# 集群部署
+
+## 准备机器
+Pegasus分布式集群至少需要准备这些机器:
+* MetaServer:2~3台机器,无需SSD盘。
+* ReplicaServer:至少3台机器,建议挂SSD盘。譬如一台服务器挂着8块或者12块SSD盘。这些机器要求是同构的,即具有相同的配置。
+* Collector:可选角色,1台机器,无需SSD盘。该进程主要用于收集和汇总集群的统计信息,负载很小,建议放在MetaServer的其中一台机器上。
+
+## 准备Zookeeper
+Pegasus集群依赖Zookeeper进行元数据存储和MetaServer抢锁,因此需要一个Zookeeper服务:
+* 如果在公司内部维护着Zookeeper集群,直接使用该集群就可以了。
+* 如果没有,就自己搭建一个Zookeeper集群,建议在Pegasus集群机器所在的同机房搭建。
+
+记下Zookeeper的服务地址列表,后面配置文件要用。
+
+## 准备配置文件
+我们提供了配置文件[src/server/config.ini](https://github.com/XiaoMi/pegasus/blob/master/src/server/config.ini)(从1.7.1版本开始支持),你需要修改该文件,替换所有``%{xxx}``形式的变量为合适的值,如下:
+
+| 变量  | 说明 | 示例 | 
+| ------------- | ------------- | ------------- |
+| %{cluster.name}  | 集群名称。  | my_cluster  |
+| %{home.dir}  | HOME路径。  | /home/work  |
+| %{app.dir}  | 程序工作路径,默认数据文件和日志文件都会放在这下面。  | /home/work/app/pegasus  |
+| %{slog.dir}  | 存放Shared Commit Log文件的路径,建议放在一个独享的SSD盘上。如果没有可用的SSD盘,可以设置为空字符串,表示默认使用%{app.dir}。  | /home/work/ssd1/pegasus  |
+| %{data.dirs}  | 存放各Replica数据的路径列表,可以用逗号分隔指定多个盘,每个路径需要指定一个名称,格式为``name1:path1,name2:path2``。如果没有可用的独立SSD盘,可以设置为空字符串,表示默认使用%{app.dir}。  | ssd2:/home/work/ssd2/pegasus |
+| %{meta.server.list}  | MetaServer地址列表,用逗号分隔,格式为``ip1:port1,ip2:port2``。**注意只能用IP地址,不能用hostname**。  | 1.2.3.4:34601,1.2.3.5:34601  |
+| %{zk.server.list}  | Zookeeper地址列表,用逗号分隔,格式为``ip1:port1,ip2:port2``。  | 1.2.3.4:2181,1.2.3.5:2181  |
+
+注意:同一个变量可能出现在多个地方,要保证所有的``%{xxx}``变量都被替换掉。
+
+### 多个SSD盘如何配置
+如果有多个SSD盘,推荐使用一个SSD盘专门用于shared log,其他盘用于存储replica数据。
+
+譬如,假设机器有4个盘,挂载路径为/home/work/ssd{id},其中{id}=1,2,3,4。那么可以将ssd1用于shared log,可配置如下:
+```ini
+[replication]
+  slog_dir = /home/work/ssd1/pegasus
+  data_dirs = ssd2:/home/work/ssd2/pegasus,ssd3:/home/work/ssd3/pegasus,ssd4:/home/work/ssd4/pegasus
+```
+
+如果只有一个SSD盘,那么就没得选择,只能都用这一个盘。假设SSD盘挂载路径为/home/work/ssd,可配置如下:
+```ini
+[replication]
+  slog_dir = /home/work/ssd/pegasus/{cluster.name}
+  data_dirs = ssd:/home/work/ssd/pegasus/{cluster.name}
+```
+
+### 多个网卡如何配置
+在配置文件中有以下section:
+```ini
+[network]
+  primary_interface =
+  io_service_worker_count = 4
+```
+
+通过primary_interface指定网卡:
+* 如果只有一个网卡,可以设置为空字符串,表示自动获取合适的网卡地址。具体策略就是在ifconfig的列表中查找第一个符合10.\*.\*.\*/172.16.\*.\*/192.168.\*.\*的地址,这样就会忽略回环地址和虚拟地址。
+* 如果有多个网卡,请指定网卡名。如果不指定,则会获取第一个符合10.\*.\*.\*/172.16.\*.\*/192.168.\*.\*的地址。
+
+譬如,如果有多个网卡,想使用eth2所在网卡,可配置如下:
+```ini
+[network]
+  primary_interface = eth2
+  io_service_worker_count = 4
+```
+
+## 准备部署包
+ReplicaServer/MetaServer/Collector三种角色的Server共用一套server程序和配置文件。
+
+首先[编译Pegasus](/overview/compilation),编译完成后运行以下命令可以打包生产server端部署包:
+```
+./run.sh pack_server
+```
+运行成功后,会在本地文件夹下生产``pegasus-server-{version}-{platform}-{buildType}``的文件夹以及tar.gz包。在文件夹里面有个bin/文件夹,里面包含pegasus_server程序及依赖库,还包括**刚刚修改好的config.ini文件**。
+
+将部署tar.gz包拷贝到各个机器上,并解压。你可以使用合适的分布式分发工具来完成这件事情。
+
+## 启动服务
+
+在启动程序之前,需要先把程序所依赖的动态链接库的路径加入到`LD_LIBRARY_PATH`中:
+```
+export LD_LIBRARY_PATH=/path/to/your/pegasus/bin:$LD_LIBRARY_PATH
+```
+
+启动MetaServer:
+```
+cd bin/
+./pegasus_server config.ini -app_list meta
+```
+
+启动ReplicaServer:
+```
+cd bin/
+./pegasus_server config.ini -app_list replica
+```
+
+启动Collector:
+```
+cd bin/
+./pegasus_server config.ini -app_list collector
+```
+
+集群启动成功后,会默认创建一个``temp``表,该表也用于Collector的集群可用度检查,最好不要删除。
+
+你可以使用[Shell工具](/overview/shell)查看集群的各种状态。如果启动失败,可以到``%{app.dir}/log``下面查看错误日志,排查问题。
+
+# 分布式部署工具
+
+## Minos部署
+
+我们在小米内部使用Minos工具部署,该工具也已经开源,参见[XiaoMi/minos](https://github.com/XiaoMi/minos)。Minos工具能够基于配置模板动态生成合适的配置文件,操作简单,推荐使用。关于如何使用Minos的流程与细节,后续会补充相关文档。
+
+# 常见问题
+
+## 集群清理重建
+如果想完全重新部署集群,**以前的数据都不要了**,需要清理以下环境,否则可能出现启动新集群失败的情况:
+* 清理MetaServer/ReplicaServer/Collector的```%{app.dir}```、```%{slog.dir}```和```%{data.dirs}```文件夹
+* 删除Zookeeper的```/pegasus/%{cluster.name}```节点
diff --git a/_administration/duplication.md b/_administration/duplication.md
new file mode 100644
index 0000000..220f5f8
--- /dev/null
+++ b/_administration/duplication.md
@@ -0,0 +1,205 @@
+---
+title: 跨机房同步
+layout: page
+menubar: administration_menu
+---
+
+在 pegasus 中,跨机房同步又被称为 **_热备份_**,或 **_duplication_**,简称 **_dup_**。这一功能的主要目的是保证 **数据中心级别的可用性**。当业务需要保证服务与数据能够容忍机房故障时,可以考虑使用此功能。
+
+此外,当 Pegasus 客户端在多机房分布时,时常会遇到跨机房访问 Pegasus 服务带来的高延时问题,这时我们可以将 Pegasus 的服务与客户端部署在相同的机房内,客户端可以只读写本地机房的服务,然后由热备份功能将写同步到各个机房上。这种做法既能保证各个机房都有完整数据,又能避免跨机房的延时开销。
+
+```
+        client               client               client
+           +                    +                   +
+ +---------v-------+   +--------v--------+   +------v-----------+
+ |                 |   |                 |   |                  |
+ | pegasus-beijing <---> pegasus-tianjin <---> pegasus-shanghai |
+ |                 |   |                 |   |                  |
+ +----------^------+   +-----------------+   +---------^--------+
+            |                                          |
+            +------------------------------------------+
+```
+
+我们能够做到**一主一备(single-master)**,也能提供**多机房多主(multi-master)**,用户可以根据需要进行配置。
+
+这里需要注意的是,跨机房同步是**异步**的数据复制,并非完全实时。与单机房不同,我们不提供跨机房 *read-after-write* 的一致性保证。目前在跨机房网络健康的环境下,数据延时大概在 10s 左右,即 A 机房的写数据大概在 10s 后会写入 B 机房。
+
+## Get Started
+
+假设我们有两个 pegasus 集群 _bjsrv-account_ 和 _tjsrv-account_ 分别位于北京与天津的两个机房内,表 `account_xiaomi` 由于存储了极其关键的用户帐号数据,需要能够在双集群保证可用,所以我们为它实施热备份:
+
+```
+./run.sh shell -n bjsrv-account
+
+Type "help" for more information.
+Type "Ctrl-D" or "Ctrl-C" to exit the shell.
+
+The cluster name is: bjsrv-account
+The cluster meta list is: ***
+
+>>> ls
+app_id    status              app_name
+12        AVAILABLE           account_xiaomi
+
+>>> add_dup account_xiaomi tjsrv-account
+Success for adding duplication [appid: 12, dupid: 1535008534]
+
+>>> query_dup account_xiaomi
+duplications of app [account_xiaomi] are listed as below:
+|     dup_id     |   status   |     remote cluster     |       create time       |
+|   1535008534   |  DS_START  |      tjsrv-account     |   2018-08-23 15:15:34   |
+```
+
+通过 `add_dup` 命令,bjsrv-account 集群的表 account_xiaomi 将会近实时地把数据复制到 tjsrv-account 上,这意味着,每一条在北京机房的写入,最终都一定会复制到天津机房。
+
+热备份使用日志异步复制的方式来实现跨集群的同步,可与 mysql 的 binlog 复制和 hbase replication 类比。每个 replica 单独发送自己的日志到远端集群上,保证了 replica 之间互不干扰。
+
+热备份的两集群的表名需要保持一致,但 partition 的个数不需要相同。例如用户可以建表如下:
+```
+>>> cc bjsrv-account
+>>> create account_xiaomi -p 128
+>>>
+>>> cc tjsrv-account
+>>> create account_xiaomi -p 32
+```
+
+## 线上表开启热备
+
+有时一个线上表可能在设计之初未考虑到跨机房同步的需求,而在服务一段时间后,才决定进行热备份。此时我们需要进行全量数据拷贝,例如将 bj 已有的全部数据复制到 tj。因为是线上表,拷贝过程中**不可以停止服务**,同时拷贝过程中的**写增量数据也不能丢**。
+
+面对这个需求,我们的思路是:首先 bj 保留从此刻开始的所有写增量(即 wal),将 bj 的全量快照(冷备份)上传至 HDFS / xiaomi-FDS 上,然后恢复到 tj。此后 bj 开启热备份,并重放此前堆积的写增量,复制到远端 tj 机房。
+
+如何保留写增量?pegasus 如此进行操作:
+
+- 首先使用 `add_dup [--freezed/-f]` 表示不进行日志复制,它的原理就是阻止当前日志 GC(log compaction)。该操作 **必须最先执行**,否则无法保证数据完整性。
+
+```
+>>> cc bjsrv-account
+>>> add_dup account_xiaomi tjsrv-account --freezed
+```
+
+- 虽然不进行复制,但每个分片都会记录**当前确认点(confirmed_decree)**(初始为 `private_log->max_committed_decree`),并持久化到 meta server 上。注意需等待所有的 replica 都将当前确认点更新至 meta server 后,才可进行下一步操作,这是该功能正确性的前提。
+
+confirme_decree 值为 -1 即表示该分片的确认点尚未同步。
+
+```
+>>> query_dup_detail account_xiaomi 1535008534
+>>> {"dupid":1548442533,"status":"DS_START","remote":"c4srv-feedhistory","create_ts":1548442533763,"progress":[{"pid":0,"confirmed":-1},{"pid":1,"confirmed":276444333},{"pid":2,"confirmed":-1},{"pid":3,"confirmed":-1},{"pid":4,"confirmed":-1},{"pid":5,"confirmed":-1},{"pid":6,"confirmed":-1},{"pid":7,"confirmed":279069949},{"pid":8,"confirmed":-1}}
+>>> query_dup_detail account_xiaomi 1535008534
+>>> {"dupid":1548442533,"status":"DS_START","remote":"c4srv-feedhistory","create_ts":1548442533763,"progress":[{"pid":0,"confirmed":276444111},{"pid":1,"confirmed":276444333},{"pid":2,"confirmed":276444332},{"pid":3,"confirmed":276444222},{"pid":4,"confirmed":276444111},{"pid":5,"confirmed":276444377},{"pid":6,"confirmed":276444388},{"pid":7,"confirmed":279069949},{"pid":8,"confirmed":276444399}}
+```
+
+- 使用冷备份将数据快照上传至远端存储,再使用恢复功能在 tjsrv-account 恢复该表。示例命令如下:
+
+```
+# 立刻对表(app_id = 12)进行冷备
+./run.sh shell -n bjsrv-account
+>>> add_backup_policy -p dup_transfer -b fds_wq -a 12 -i 86400 -s 12:01 -c 1
+
+# 耐心等待备份生成
+>>> query_backup_policy -p dup_transfer
+policy_info:
+    name                 : dup_transfer
+    backup_provider_type : fds_wq
+    backup_interval      : 86400s
+    app_ids              : {12}
+    start_time           : 12:01
+    status               : enabled
+    backup_history_count : 1
+backup_infos:
+[1]
+    id         : 1541649698875
+    start_time : 2018-11-08 12:01:38
+    end_time   : 2018-11-08 12:03:51
+    app_ids    : {60}
+
+# 在天津机房恢复表
+./run.sh shell -n tjsrv-account
+>>> restore_app -c bjsrv-account -p dup_transfer -a account_xiaomi -i 12 -t 1541649698875 -b fds_wq
+
+# 开启日志复制
+>>> start_dup account_xiaomi <dupid>
+
+# 至此热备份已经完全可用。
+```
+
+当 `start_dup` 时,热备份任务会从之前的确认点开始复制,这样我们就保证了写增量的完整性。
+
+另外需注意的是,由于写增量的长时间堆积,一时可能有大量日志复制,热备份流量会突增,从而导致服务不稳定。因此,我们需要在远端机房设置限流(write throttling)。
+
+```
+>>> get_app_envs
+get app envs succeed, count = 7
+=================================
+replica.write_throttling = 30000*delay*100,40000*reject*200
+=================================
+```
+
+### 元信息存储
+
+热备份的元信息会经由 meta server 持久化于 zookeeper 上,其存储路径如下:
+
+```
+                                    <cluster_root>                     <app_id>          <dupid>
+                                          |                                |                |
+                                          |                                |                |
+[zk: 127.0.0.1:22181(CONNECTED) 0] get /pegasus/bjsrv-account/0.0.x.x/apps/1/duplication/1537336970 
+
+{"remote":"tjsrv-account","status":"DS_START","create_timestamp_ms":1537336970483}
+```
+
+## 配置项
+
+- `duplication_enabled`:如果遇到紧急情况想要手动关闭热备份,可以将该项设置为 false,默认为 true。
+
+- 开启热备份的集群必须配置远端机房的具体地址
+
+```
+  [pegasus.clusters]
+     tjsrv-account = 127.0.0.1:51601,127.0.0.1:51601
+```
+
+- 热备份要求 `allow_non_idempotent_write` 必须为 false(默认值)。因为开启热备份的集群 **不支持 “幂等操作”** 如 CHECK_AND_MUTATE, CHECK_AND_SET,INCR 等。
+
+- 如果有多机房同时写的需求,配置 `verify_timetag` 需修改为 true :
+```
+  [pegasus.server]
+    verify_timetag = true
+```
+
+- 热备份的集群需要登记其 cluster_id:
+
+```
+  # Configuration for cluster_id.
+  # This is required for every cluster that enables duplication.
+  [duplication-group]
+    tjsrv-account = 1
+    bjsrv-account = 2
+```
+
+我们在每条数据前都会加上 `timestamp+cluster_id` 的前缀,timestamp 即数据写到 pegasus 的时间戳,cluster_id 即上面 duplication-group 中所配置的,tjsrv 的 cluster_id 为 1,bjsrv 的 cluster_id 为 2。
+
+cluster_id 具有两个作用:
+
+一旦出现写冲突,例如 tjsrv 和 bjsrv 同时写 key `"user_1"`,系统首先会检查两次写的时间戳,以时间戳大的为最终值。当极罕见地遇到时间戳相同的情况时,以 cluster_id 大的为最终值。使用这种机制我们可以保证两集群的最终值一定相同。
+
+cluster_id 的另一个作用就是保证热备份不会
+
+## 相关监控项
+
+| 监控项 | 描述 |
+|-------|-----|
+| dup.log_read_in_bytes_rate | |
+| dup.log_mutations_read_rate | |
+| dup.shipped_size_in_bytes_rate | |
+| dup.time_lag(ms) | |
+| dup.shipped_ops | |
+| dup.failed_shipping_ops | |
+| duplicated_put_qps | |
+| duplicated_remove_qps | |
+| duplicated_multi_put_qps | |
+| duplicated_multi_remove_qps | |
+
+## Known Limitations
+
+- 热备份暂时不建议两机房同时写一份数据。在我们的业务经验看来,通常这是可以接受的。用户可以将数据均分在 tjsrv 和 bjsrv 两机房内,热备份能保证当任一机房宕机,只有数秒的数据丢失(假设机房之间网络稳定)。
diff --git a/_administration/experiences.md b/_administration/experiences.md
new file mode 100644
index 0000000..5b351ee
--- /dev/null
+++ b/_administration/experiences.md
@@ -0,0 +1,90 @@
+---
+title: 运维经验
+layout: page
+menubar: administration_menu
+---
+
+任何一个分布式系统的运维工作都少不了周期巡检,问题排查,故障报警,人工审核。它们是保证服务稳定运行的关键。
+这里整理Pegasus的监控指标,你可以根据需要接入到你的运维工具中。
+
+## 周期巡检
+
+* **可用度**:正常时可用度会保持在100%,发生节点故障等异常偶尔会有可用度低于100%的情况
+
+* **总QPS**:异常流量的突增或者突降有时会导致服务抖动
+
+* **读写延迟**:P99读延迟和P99写延迟可能有异常毛刺的情况,对用户会造成影响
+
+* **内存使用**:关注内存使用是否正常,譬如memory是否出现暴涨、是否达到了警戒线
+
+* **存储使用**:关注磁盘存储使用是否正常,预估存储是否够用
+
+## 问题排查
+
+* 集群基础信息是否正常:`cluster_info`
+  * meta_servers列表是否正确
+  * primary_meta_server是否为第一个(因为推荐使用第一个,第二个节点上可能部署有数据节点)
+  * meta_function_level是否是steady状态
+* 各Table、各Partition是否健康:`ls -d`
+  * Table数量是否正常
+  * 所有Table的unhealthy_num(没有达到一主一备的partition数量)和partly_healthy_num(没有达到一主两备的partition数量)是否都为0
+* 各节点是否健康:`nodes -d`
+  * 所有节点是否都是ALIVE状态
+  * 数据分布是否倾斜严重,如果倾斜严重,可以选择集群流量比较小的时间段将meta_function_level设置为lively进行负载均衡调整,并在调整完成后设置回steady状态
+  * 注意:负载均衡只有在必要的时候才进行,前提是不要影响服务稳定性,因此不要频繁操作;在调整过程中要全程监控集群状态
+* 各节点的基本信息是否正常:`server_info`
+  * Server版本是否正确
+  * 通过Start Time判断是否发生过重启
+* 各节点的实时统计信息是否正常:`server_stat`
+  * 读写QPS、读写延迟
+  * SharedLog大小
+  * 内存使用量
+* 各Table的实时统计信息是否正常:`app_stat`
+  * 各操作的QPS情况是否正常
+  * 各Table的存储用量是否正常
+* 检查机器的socket连接数:
+  * 到MetaServer所在机器上使用netstat命令检查连接数:
+
+  ```bash
+  netstat -na | grep '601\>' | grep ESTABLISHED | wc -l
+  ```
+
+  * 检查与该机器建立连接的远程节点,按照连接数排序:
+
+  ```bash
+  netstat -na | grep '601\>' | grep ESTABLISHE | awk '{print $5}' | sed 's/:.*//' | sort | uniq -c | sort -k1 -n -r | head
+  ```
+
+  * 如果连接数太多(譬如单节点连接数超过100),就需要进一步分析原因。
+
+常见故障处理办法:
+
+* 如果节点挂掉重启,需要登录到对应机器上,检查原因:
+  * 通过server的日志
+  * 通过core文件;如果没有core文件,需要检查ulimit配置是否正确,或者通过dmesg或者/var/log/messages查看是否因为OutOfMemory原因被系统杀死
+* 如果出故障机器较多,可以考虑将meta置为freezed状态,避免雪崩
+* 进程不停重启,可以考虑停止进程
+* 机器无法从relay连接,有可能是宕机了,快速联系系统运维人员
+* 注意系统的参数:CPU情况、diskIO负载和latency、network负载和latency、socket个数
+* 通过dmesg查看内核报错
+
+## 需求审核
+
+Pegasus和多数数据库一样,以表的方式管理资源。
+每个表需要的资源量需要提前告知,这样我们才能为需求分配合适的计算存储资源。
+除此外,与业务深度交流,定制最合适的存储方案也有助于后期服务的稳定运行。
+
+有哪些重要的需求需要提前审核:
+
+* 表名
+* 读峰值(QPS)
+* 读总量(条/天)
+* 写峰值(QPS)
+* 写总量(条/天)
+* 单条数据平均大小(KB/条)
+* 数据总量预估 (GB)
+* 增长预估(6个月/1年/3年与目前相比倍数)
+* 读延迟需求(毫秒/P99)
+* 写延迟需求(毫秒/P99)
+* 访问特征(如定时批量写入)
+* 是否存在既有数据需导入/数据规模
diff --git a/_administration/index.md b/_administration/index.md
new file mode 100644
index 0000000..e76d369
--- /dev/null
+++ b/_administration/index.md
@@ -0,0 +1,42 @@
+---
+title: 高效运维
+layout: page
+menubar: administration_menu
+---
+
+Pegasus 不仅仅只提供简单的 key value 存储接口,我们还基于稳定性考虑,增加了许多有助于高效运维的特性。
+
+如果你刚开始使用 Pegasus,想要把已编译好的系统部署在你的机器上,你首先需要参考 [配置管理](config),
+根据环境来调整参数。然后你应该参考 [集群部署](deployment) 将 Pegasus 搭载在你的机器集群上。
+
+在成功部署后,你应该着重观察系统运行情况,参考 [可视化监控](monitoring) 来查看相关指标。
+
+在服务运行后,偶然的异常(可能是网络,磁盘,系统故障,潜在 Bug 导致的异常)会使集群抖动。如果造成了节点负载不均衡,你可以参照 [负载均衡](rebalance) 进行操作。
+
+如果是随着业务量增大,超出了你的服务资源所能承受的能力,你可以参照 [集群扩容缩容](membership-change) 来增加节点数。
+如果有机器发生持久性的故障,你也可以参照 [集群扩容缩容](membership-change) 剔除这个坏节点。
+如果是机器的某个SSD盘出故障,可以参照 [坏盘检修](bad-disk) 剔除这个坏盘。
+
+如果需要升级集群,请参照 [集群升级](rolling-update)。
+
+集群运行过程中,你需要时刻关注资源(磁盘、内存、网络)的使用情况,并及时做出运维调整,请参照 [资源管理](resource-management)。
+
+如果业务数据量很大或者单条value很长,你可以参照 [数据压缩](compression) 来提高磁盘利用率和提升读写性能。
+
+对一些重要的业务,你可以将相关的表定期进行 [冷备份](cold-backup) 来保证数据安全性。冷备份的功能也常常用作于快速的大量数据迁移。
+
+对于一些极端情况,譬如元数据丢失、多个节点同时宕机,可能会造成集群数据的不一致。
+如果元数据丢失,建议你参考 [元数据恢复](meta-recovery);对于数据备份丢失造成的不一致,建议你参考 [Replica数据恢复](replica-recovery)。
+
+如果集群要依赖一个新的Zookeeper,需要迁移Zookeeper数据,请参考 [Zookeeper迁移](zk-migration)。
+
+通常一个集群内会服务于许多表,所有的表都有相同的优先级,换言之,有某个表的请求量过大,
+其他表的服务质量就会下滑,这通常被称为 “多租户” 问题,你可以通过 [流量控制](throttling) 满足每个表的 SLA。
+
+如果要迁移一个表的数据到另外一个表,请参考 [Table迁移](table-migration)。
+
+表级的运维操作还包括 [Table软删除](table-soft-delete) 、[Table环境变量](table-env)、[Partition Split](partition-split)、[Manual Compact功能](manual-compact)、[Usage Scenario功能](usage-scenario) 等。
+
+除此之外,我们还支持了 [远程命令](remote-commands),方便运维人员对Server进行直接控制,并提供了 [HTTP接口](http)。
+
+我们在运维集群服务业务的过程中,也积累了很多 [运维经验](experiences),你可以参考。
diff --git a/_administration/manual-compact.md b/_administration/manual-compact.md
new file mode 100644
index 0000000..9a95d1f
--- /dev/null
+++ b/_administration/manual-compact.md
@@ -0,0 +1,148 @@
+---
+title: Manual Compact功能
+layout: page
+menubar: administration_menu
+---
+
+注:manual compact功能从[v1.8.1版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.8.1)开始支持。
+# 原理
+
+RocksDB除了根据需要自动触发compaction外,还能通过接口手动触发compaction,这个功能称之为[Manual Compaction](https://github.com/facebook/rocksdb/wiki/Manual-Compaction)。其提供了`CompactRange()`接口,如下:
+```C++
+// CompactRangeOptions is used by CompactRange() call.
+struct CompactRangeOptions {
+  // If true, no other compaction will run at the same time as this
+  // manual compaction
+  bool exclusive_manual_compaction = true;
+  // If true, compacted files will be moved to the minimum level capable
+  // of holding the data or given level (specified non-negative target_level).
+  bool change_level = false;
+  // If change_level is true and target_level have non-negative value, compacted
+  // files will be moved to target_level.
+  int target_level = -1;
+  // Compaction outputs will be placed in options.db_paths[target_path_id].
+  // Behavior is undefined if target_path_id is out of range.
+  uint32_t target_path_id = 0;
+  // By default level based compaction will only compact the bottommost level
+  // if there is a compaction filter
+  BottommostLevelCompaction bottommost_level_compaction =
+      BottommostLevelCompaction::kIfHaveCompactionFilter;
+};
+
+// Compact the underlying storage for the key range [*begin,*end].
+// The actual compaction interval might be superset of [*begin, *end].
+// In particular, deleted and overwritten versions are discarded,
+// and the data is rearranged to reduce the cost of operations
+// needed to access the data.  This operation should typically only
+// be invoked by users who understand the underlying implementation.
+//
+// begin==nullptr is treated as a key before all keys in the database.
+// end==nullptr is treated as a key after all keys in the database.
+// Therefore the following call will compact the entire database:
+//    db->CompactRange(options, nullptr, nullptr);
+// Note that after the entire database is compacted, all data are pushed
+// down to the last level containing any data. If the total data size after
+// compaction is reduced, that level might not be appropriate for hosting all
+// the files. In this case, client could set options.change_level to true, to
+// move the files back to the minimum level capable of holding the data set
+// or a given level (specified by non-negative options.target_level).
+virtual Status CompactRange(const CompactRangeOptions& options,
+                            ColumnFamilyHandle* column_family,
+                            const Slice* begin, const Slice* end) = 0;
+```
+
+对应地,Pegasus对该功能在上层进行了封装,提供了表级别的Manual Compact功能,其作用是:
+* 通过compaction去掉垃圾数据,减少数据量,降低文件层数,提升读操作的性能。
+* 对最高层做compaction,可以清理掉Delete标记数据。
+* 配合[Usage Scenario功能](usage-scenario)中表的bulk_load模式,可以在灌数据完成后执行一次Manual Compact,去除垃圾数据,整理数据和文件夹结构,提升读性能。
+
+实现方式:
+* 扩展RocksDB,在Manifest中记录上一次执行Manual Compact的时间,并提供`GetLastManualCompactFinishTime()`方法来获取该时间。
+* 利用[Table环境变量](table-env),来设置两类Manual Compect的环境变量:
+  * 单次Manual Compact:
+    * `manual_compact.once.trigger_time`:格式为Unix时间戳的秒数,可通过shell命令`date +%s`获取当前时间戳。如果LastManualCompactFinishTime旧于该trigger_time,就触发Manual Compaction的执行。
+    * `manual_compact.once.target_level`:用于设置`CompactRangeOptions::target_level`。如果不设置,则使用默认值-1。
+    * `manual_compact.once.bottommost_level_compaction`:可设置为`skip`或者`force`。如果是`skip`,则不对最高层做compaction;如果是`force`,则强制对最高层做compaction。如果不设置,则默认为`skip`。
+  * 周期Manual Compact:
+    * `manual_compact.periodic.trigger_time`:格式为逗号分隔的时钟,譬如`3:00,21:00`,表示每一天的3:00和21:00都触发一次Manual Compaction的执行。
+    * `manual_compact.periodic.target_level`:用于设置`CompactRangeOptions::target_level`。如果不设置,则使用默认值-1。
+    * `manual_compact.periodic.bottommost_level_compaction`:可设置为`skip`或者`force`。如果是`skip`,则不对最高层做compaction;如果是`force`,则强制对最高层做compaction。如果不设置,则默认为`skip`。
+  * Manual Compact总开关:
+    * `manual_compact.disabled`(从[v1.9.0版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.9.0)开始支持):如果为true,则关闭Manual Compact功能,并且取消正在执行中的Manual Compact动作。如果不设置,默认为false。
+    * `manual_compact.max_concurrent_running_count`(从[v1.11.3版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.11.3)开始支持):指定最大并发数。实际上,可执行的最大并发数由`该env参数`和`服务端MANUAL_COMPACT_THRAD_POOL的线程数`共同决定,取两者的较小值。
+
+注意:
+* Manual Compact功能是分派到独立的Compact线程池中执行的,每个线程同一时刻只能处理一个replica的full compaction,因为并发处理量与Compact线程池的线程数量有关,可以通过配置文件的`worker_count`进行配置,如果使用Manual Compact比较频繁,建议调大线程数量(譬如设置为cpu core数量接近):
+  ```
+  [threadpool.THREAD_POOL_COMPACT]
+  name = compact
+  partitioned = false
+  max_input_queue_length = 128
+  worker_priority = THREAD_xPRIORITY_NORMAL
+  worker_count = 16
+  ```
+* Manual Compact属于CPU和IO密集型操作,处理过程中会使CPU使用率长期处于高位,容易对集群的读写性能造成影响,所以**建议在流量低峰时段进行操作**。如果启动后发现读写性能下降影响了业务,可以立即通过设置该表的环境变量`manual_compact.disabled=true`来中止。
+* Manual Compact过程中可能需要较多的额外磁盘空间。因为compaction前后文件变化较大,而Pegasus一般又会保留最近3个版本的checkpoint,所以基本上额外需要的磁盘空间量大约等于执行Manual Compact的表的数据存储量。所以,在执行Manual Compact前需**确认集群有足够的存储空间**,同时在执行过程中**关注磁盘空间使用情况**,避免因为磁盘空间耗尽导致集群节点宕机,影响集群可用度。
+
+# 如何设置
+
+## 通过shell设置
+
+既然Manual Compact功能是利用[Table环境变量](table-env)触发的,那么可以直接通过shell功能的[set_app_envs命令](/overview/shell#set_app_envs)来设置。需要设置的环境变量参照上面的描述,环境变量设置后不会立即生效,大约需要等几十秒后才能在所有replica上生效。
+
+由于需要设置的环境变量比较多,且对数据格式有要求,所以强烈建议不要自己直接设置,而是通过我们提供的脚本工具来设置,如下所示。
+
+## 通过脚本设置
+我们提供了一个脚本工具[scripts/pegasus_manual_compact.sh](https://github.com/XiaoMi/pegasus/blob/master/scripts/pegasus_manual_compact.sh)来方便地设置,用法:
+```
+$ ./scripts/pegasus_manual_compact.sh 
+This tool is for manual compact specified table(app).
+USAGE: ./scripts/pegasus_manual_compact.sh -c cluster -a app-name [-t periodic|once] [-w] [-g trigger_time] [...]
+Options:
+  -h|--help                   print help message
+
+  -c|--cluster <str>          cluster meta server list, default is "127.0.0.1:34601,127.0.0.1:34602"
+
+  -a|--app_name <str>         target table(app) name
+
+  -t|--type <str>             manual compact type, should be periodic or once, default is once
+
+  -w|--wait_only              this option is only used when the type is once!
+                              not trigger but only wait the last once compact to finish
+
+  -g|--trigger_time <str>     this option is only used when the type is periodic!
+                              specify trigger time of periodic compact in 24-hour format,
+                              e.g. "3:00,21:00" means 3:00 and 21:00 everyday
+
+  --target_level <num>        number in range of [-1,num_levels], -1 means automatically, default is -1
+
+  --bottommost_level_compaction <skip|force>
+                              skip or force, default is skip
+                              more details: https://github.com/facebook/rocksdb/wiki/Manual-Compaction
+
+  --max_concurrent_running_count <num>
+                              max concurrent running count limit, should be positive integer.
+                              if not set, means no limit.
+
+for example:
+
+  1) Start once type manual compact with default options:
+
+      ./scripts/pegasus_manual_compact.sh -c 127.0.0.1:34601,127.0.0.1:34602 -a temp
+
+  2) Only wait last once type manual compact to finish:
+
+      ./scripts/pegasus_manual_compact.sh -c 127.0.0.1:34601,127.0.0.1:34602 -a temp -w
+
+  3) Config periodic type manual compact with specified options:
+
+      ./scripts/pegasus_manual_compact.sh -c 127.0.0.1:34601,127.0.0.1:34602 -a temp -t periodic -g 3:00,21:00 \
+           --target_level 2 --bottommost_level_compaction force
+```
+
+该工具不仅会调用shell命令设置Table环境变量,对于once类型还会等待所有的replica上的操作都执行完成,使用起来十分方便。
+
+譬如,在[bulk load](usage-scenario#支持场景)完成后执行once manual compact如下:
+```
+$ ./scripts/pegasus_manual_compact.sh -c 127.0.0.1:34601,127.0.0.1:34602 -a temp
+```
diff --git a/_administration/membership-change.md b/_administration/membership-change.md
new file mode 100644
index 0000000..1dd0c6e
--- /dev/null
+++ b/_administration/membership-change.md
@@ -0,0 +1,64 @@
+---
+title: 集群扩容缩容
+layout: page
+show_sidebar: false
+menubar: administration_menu
+---
+
+# 功能目标
+
+当集群容量不够或者读写压力太大了,需要通过增加节点来扩容;当集群承担的业务规模缩减时,可以通过减少节点来缩容。
+
+扩容和缩容时,需要考虑这些点:
+* 不停服
+* 尽量不要影响可用性
+* 尽量减少不必要的数据拷贝
+
+# 扩容流程
+
+扩容流程非常简单:
+* 要扩容多个机器,就在这些新增机器上启动replica server进程,启动后replica server会主动联系meta server,加入节点列表中。
+* 在meta level为steady的情况下,不会进行[负载均衡](rebalance),因此用shell工具的`nodes -d`命令查看,可以看到新节点的状态为ALIVE,但是服务的replica个数为0。
+* 通过shell工具的`set_meta_level lively`启动负载均衡,meta server会逐渐将部分replica迁移到新节点上。
+* 通过shell工具的`nodes -d`命令查看个节点服务replica的情况,在达到均衡状态后,通过`set_meta_level steady`关闭负载均衡,扩容完成。
+
+# 缩容流程
+
+缩容相对扩容来说要考虑的点就多些,主要包括:
+* 如果同时要下线多个节点,需要一个一个进行,等一个下线完成后再下线另一个,避免影响集群的可用度和数据的安全性。
+* 如果同时要下线多个节点,在下线一个节点时,meta server补充备份要避免将备份分派在即将要下线的其他节点上,不然等后面下线其他节点时,又要重新补充备份,造成不必要的跨节点数据拷贝。我们提供了black_list来支持这个功能。
+* **需要注意**:节点下线后,在meta server上的状态会变成UNALIVE,可能会造成ALIVE的节点比例低于配置参数`node_live_percentage_threshold_for_update`,如果低于了限制,meta server就会自动降级为freezed状态,此时所有的`reconfiguration`操作都无法进行,缩容流程也进行不下去。所以在缩容之前需要计算一下是否会造成这种情况,如果会,就先升级meta server的配置,将`node_live_percentage_threshold_for_update`修改至足够低,保证在缩容过程中meta server不会自动降级为freezed状态。
+
+推荐的缩容流程:
+* 计算缩容后ALIVE的节点比例会不会低于配置参数`node_live_percentage_threshold_for_update`,如果低于,就将该配置参数改小些,然后升级meta server。
+* 使用shell工具将集群的meta * level设置为steady,关闭[负载均衡功能](rebalance),避免不必要的replica迁移。
+  ```
+  >>> set_meta_level steady
+  ```
+* 使用shell工具向meta server发送[远程命令](remote-commands#meta-server),设置black_list:
+  ```
+  >>> remote_command -t meta-server meta.lb.assign_secondary_black_list $address_list
+  ```
+  其中`address_list`是要下线节点的ip:port地址列表,用逗号分隔。
+* 使用shell工具将`assign_delay_ms`设为10,这样做的目的是让节点下线后,立即在其他节点上补充备份:
+  ```
+  >>> remote_command -t meta-server meta.lb.assign_delay_ms 10
+  ```
+* 逐个下线replica server。单个replica server下线流程:
+  * kill掉replica server进程。
+  * 使用shell的`ls -d`命令查看集群状态,等待所有partition都完全恢复健康(所有表的unhealthy数都为0)。
+  * 清理该节点上的数据,释放存储空间。
+  * 继续下线下一个replica server。
+* 重启meta server:
+  * 重启是为了重置上面动态修改过的配置,并且让shell的`nodes -d`不再显示已经下线的节点。
+  * 如果之前调整过配置参数`node_live_percentage_threshold_for_update`,重启时需要修改配置文件,再其调整为合适的值。
+
+以上过程可以自动化,我们提供了集群升级脚本[scripts/pegasus_offline_node_list.sh](https://github.com/XiaoMi/pegasus/blob/master/scripts/pegasus_offline_node_list.sh)。不过这个脚本并不能直接使用,因为其依赖minos部署工具来完成进程的远程stop操作。`pegasus_offline_node_list.sh`调用`pegasus_offline_node.sh`,因此这两个脚本的minos_client_dir都需要更改。你可以针对你们自己的部署系统,修改脚本中minos相关部分,使其可以正常工作。如需帮助,请联系我们。
+
+注意:在使用集群升级脚本的时候,也要保证配置参数`node_live_percentage_threshold_for_update`的值足够小(有必要可以先升级meta-server),避免使集群进入freezed状态。
+
+# 节点迁移
+通过先**扩容**后**缩容**,可以实现集群的节点迁移。为了尽量减少数据的重复拷贝和移动,建议按照如下步骤:
+* 先扩容:把需要扩容的机器加入到集群中,但是在加入后**暂时不进行负载均衡**。
+* 再缩容:将需要缩容的机器通过上面的[缩容流程](#缩容流程)进行下线,**特别注意参数`node_live_percentage_threshold_for_update`的配置**。
+* 进行[负载均衡](rebalance)。
diff --git a/_administration/meta-recovery.md b/_administration/meta-recovery.md
new file mode 100644
index 0000000..b15e48d
--- /dev/null
+++ b/_administration/meta-recovery.md
@@ -0,0 +1,258 @@
+---
+title: 元数据恢复
+layout: page
+show_sidebar: false
+menubar: administration_menu
+---
+
+# 功能目标
+在Pegasus bootstrap的过程中,meta server需要先从zookeeper上拉取table的元信息以及所有replica的拓扑结构,再开始服务。
+
+元数据恢复的目标就是:**让Pegasus可以不依赖zookeeper的任何信息,完成系统的bootstrap**。
+
+具体流程就是:用户只需提供集群有效的replica server的集合;meta server通过和这些replica server交互,尝试重建出table元信息和replica拓扑结构,并写入到新的zookeeper节点,完成bootstrap。
+
+**注意:元数据恢复功能只是zookeeper数据损坏或者丢失之后的补救措施,运维人员要尽力避免这种情况的发生。**
+
+# 操作流程
+## 使用onebox集群演示
+1. 初始化onebox集群
+
+   只启动一个meta server:
+   ```bash
+   ./run.sh clear_onebox
+   ./run.sh start_onebox -m 1 -w
+   ```
+
+   此时通过shell的`cluster_info`命令,可以看到zookeeper的节点路径:
+   ```
+   zookeeper_root      : /pegasus/onebox/x.x.x.x
+   ```
+
+2. 使用bench工具灌数据
+
+   灌数据是为了测试集群元数据恢复前后数据的完整性:
+   ```bash
+   ./run.sh bench --app_name temp -t fillseq_pegasus -n 10000
+   ```
+
+3. 修改配置文件
+
+   使用以下命令修改meta server的配置文件:
+   ```bash
+   sed -i 's@/pegasus/onebox@/pegasus/onebox_recovery@' onebox/meta1/config.ini
+   sed -i 's@recover_from_replica_server = false@recover_from_replica_server = true@' onebox/meta1/config.ini
+   ```
+
+   以上命令将配置文件`onebox/meta1/config.ini`中的zookeeper路径进行修改,并设置为recovery模式:
+   * `cluster_root = /pegasus/onebox/x.x.x.x` 改为 `cluster_root = /pegasus/onebox_recovery/x.x.x.x`
+   * `distributed_lock_service_parameters = /pegasus/onebox/x.x.x.x` 改为 `distributed_lock_service_parameters = /pegasus/onebox_recovery/x.x.x.x`
+   * `recover_from_replica_server = false` 改为 `recover_from_replica_server = true`
+
+4. 重启meta
+
+   ```bash
+   ./run.sh stop_onebox_instance -m 1
+   ./run.sh start_onebox_instance -m 1
+   ```
+
+   重启成功后,meta server会进入recovery模式,此时除了start_recovery请求,所有其他RPC请求都会返回ERR_UNDER_RECOVERY,譬如,使用shell的`ls`命令得到结果如下:
+   ```
+   >>> ls
+   list apps failed, error=ERR_UNDER_RECOVERY
+   ```
+
+5. 通过shell发送recover命令
+
+   首先准备`recover_node_list`文件,用于指定有效的replica server节点,格式为每行一个节点,譬如:
+   ```
+   # comment line
+   x.x.x.x:34801
+   x.x.x.x:34802
+   x.x.x.x:34803
+   ```
+
+   通过shell的`recover`命令,向meta server发送start_recovery请求:
+   ```
+   >>> recover -f recover_node_list
+   Wait seconds: 100
+   Skip bad nodes: false
+   Skip lost partitions: false
+   Node list:
+   =============================
+   x.x.x.x:34801
+   x.x.x.x:34802
+   x.x.x.x:34803
+   =============================
+   Recover result: ERR_OK
+   ```
+
+   当返回结果为ERR_OK时,表示恢复成功,可以通过shell的`ls`命令看到正常的表信息。
+
+   通过shell的`cluster_info`命令,可以看到zookeeper节点路径已经改变:
+   ```
+   zookeeper_root      : /pegasus/onebox_recovery/x.x.x.x
+   ```
+
+6. 检查数据完整性
+
+   使用bench工具查询之前写入的数据是否完整存在:
+   ```bash
+   ./run.sh bench --app_name temp -t readrandom_pegasus -n 10000
+   ```
+
+   在最后的统计结果中能看到`(10000 of 10000 found)`,表示恢复后数据完整存在。
+
+7. 修改配置文件并重启meta
+
+   恢复成功后,需要修改配置文件,重新改回非recovery模式:
+   * `recover_from_replica_server = true` 改为 `recover_from_replica_server = false`
+
+   重启meta server:
+   ```bash
+   ./run.sh stop_onebox_instance -m 1
+   ./run.sh start_onebox_instance -m 1
+   ```
+
+   这样做是避免meta server发生重启时,再次进入recovery模式,使集群变得不可用。
+
+## 线上集群恢复
+
+对线上集群进行元数据恢复时,请遵循上面的`3~7`步骤,需注意以下几点:
+* 在`recover_node_list`中指定有效的replica server节点时,请保证所有节点都是正常可用的。
+* 恢复之前不要忘记将配置文件中`recover_from_replica_server`设置为true。
+* 只能恢复到zookeeper的新节点或者空节点。
+* 恢复完成后重新将配置文件中`recover_from_replica_server`设置为false。
+
+## 常见问题整理
+
+* 恢复到zookeeper的非空节点
+
+  此时MetaServer应当启动失败并coredump:
+  ```
+  F12:16:26.793 (1488341786793734532 26cc)   meta.default0.0000269c00010001: /home/mi/git.xiaomi/Pegasus/pegasus/rdsn/src/dist/replication/meta_server/server_state.cpp:698:initialize_data_structure(): assertion expression: false
+  F12:16:26.793 (1488341786793754317 26cc)   meta.default0.0000269c00010001: /home/mi/git.xiaomi/Pegasus/pegasus/rdsn/src/dist/replication/meta_server/server_state.cpp:698:initialize_data_structure(): find apps from remote storage, but [meta_server].recover_from_replica_server = true
+  ```
+
+* 忘记设置recover_from_replica_server为true
+
+  meta server会进入正常启动逻辑,从zookeeper上获取的apps为空,但是在config sync过程中发现replica server上有无法识别的replica,出现元数据不一致的情况,会coredump:
+  ```
+  F12:22:21.228 (1488342141228270056 2764)   meta.meta_state0.0102000000000001: /home/mi/git.xiaomi/Pegasus/pegasus/rdsn/src/dist/replication/meta_server/server_state.cpp:823:on_config_sync(): assertion expression: false
+  F12:22:21.228 (1488342141228314857 2764)   meta.meta_state0.0102000000000001: /home/mi/git.xiaomi/Pegasus/pegasus/rdsn/src/dist/replication/meta_server/server_state.cpp:823:on_config_sync(): gpid(2.7) on node(10.235.114.240:34801) is not exist on meta server, administrator should check consistency of meta data
+  ```
+
+* 恢复时连不上replica server
+
+  如果恢复时连不上replica server,recover命令就会执行失败:
+  ```
+  >>> recover -f recover_node_list
+  Wait seconds: 100
+  Skip bad nodes: false
+  Skip lost partitions: false
+  Node list:
+  =============================
+  x.x.x.x:34801
+  x.x.x.x:34802
+  x.x.x.x:34803
+  x.x.x.x:34804
+  =============================
+  Recover result: ERR_TRY_AGAIN
+  =============================
+  ERROR: collect app and replica info from node(x.x.x.x:34804) failed with err(ERR_NETWORK_FAILURE), you can skip it by set skip_bad_nodes option
+  =============================
+  ```
+
+  可以通过指定`--skip_bad_nodes`参数,强制忽略有问题的节点。但是要注意,忽略问题节点可能造成部分partition的备份数不全,有丢数据风险。
+  ```
+  >>> recover -f recover_node_list --skip_bad_nodes
+  Wait seconds: 100
+  Skip bad nodes: true
+  Skip lost partitions: false
+  Node list:
+  =============================
+  x.x.x.x:34801
+  x.x.x.x:34802
+  x.x.x.x:34803
+  =============================
+  Recover result: ERR_OK
+  =============================
+  WARNING: collect app and replica info from node(x.x.x.x:34804) failed with err(ERR_NETWORK_FAILURE), skip the bad node
+  WARNING: partition(1.0) only collects 2/3 of replicas, may lost data
+  WARNING: partition(1.1) only collects 2/3 of replicas, may lost data
+  WARNING: partition(1.3) only collects 2/3 of replicas, may lost data
+  WARNING: partition(1.5) only collects 2/3 of replicas, may lost data
+  WARNING: partition(1.7) only collects 2/3 of replicas, may lost data
+  =============================
+  ```
+
+* 恢复时发现某个partition的备份数不全
+
+  当遇到partition备份数不全的情况时,依然能正常恢复,但是会打印告警提示:
+  ```
+  >>> recover -f recover_node_list
+  Wait seconds: 100
+  Skip bad nodes: false
+  Skip lost partitions: false
+  Node list:
+  =============================
+  x.x.x.x:34801
+  x.x.x.x:34802
+  x.x.x.x:34803
+  =============================
+  Recover result: ERR_OK
+  =============================
+  WARNING: partition(1.0) only collects 1/3 of replicas, may lost data
+  =============================
+  ```
+
+* 恢复时发现某个partition没有可用的replica
+
+  当某个partition完全没有收集到可用的replica备份时,恢复会失败:
+  ```
+  >>> recover -f recover_node_list
+  Wait seconds: 100
+  Skip bad nodes: false
+  Skip lost partitions: false
+  Node list:
+  =============================
+  x.x.x.x:34801
+  x.x.x.x:34802
+  x.x.x.x:34803
+  =============================
+  Recover result: ERR_TRY_AGAIN
+  =============================
+  ERROR: partition(1.0) has no replica collected, you can force recover it by set skip_lost_partitions option
+  =============================
+  ```
+
+  可以通过指定`--skip_lost_partitions`参数,强制其继续执行恢复,此时partition(1.0)会初始化为空replica。**此操作需慎重,需确定能够容忍数据丢失**:
+  ```
+  >>> recover -f recover_node_list --skip_lost_partitions
+  Wait seconds: 100
+  Skip bad nodes: false
+  Skip lost partitions: true
+  Node list:
+  =============================
+  x.x.x.x:34801
+  x.x.x.x:34802
+  x.x.x.x:34803
+  =============================
+  Recover result: ERR_OK
+  =============================
+  WARNING: partition(1.0) has no replica collected, force recover the lost partition to empty
+  =============================
+  ```
+
+* 恢复软删除的表
+
+  对于已经删除的表,由于有[Table软删除](table-soft-delete)功能,只要没有超过保留时间,replica server上的replica数据就不会被清理,所以该表能被恢复,且被认为是一个正常的未删除的表,也就是说丢掉了删除信息,但是不会丢数据。
+
+  由于表删除后可以创建新的同名表,所以在恢复过程中可能会发现多个表都使用相同表名,出现表名冲突。此时,id最大的那个表会使用原始表名,其他表的表名都改为`{name}-{id}`的形式。
+
+# 设计与实现
+元数据恢复功能的设计与实现:
+* meta server提供一个配置项,用来标识“当从zookeeper上获取不到任何元数据信息时,是否进入元数据恢复的模式”。
+* shell端提供一个recovery的命令,来触发meta启动元数据恢复流程。
+* 如果进入了元数据恢复流程,meta server会接收replica server的探活心跳,并只响应一个特殊的`start_recovery`RPC,而不响应任何其他类型的RPC。
+* 用户需要指定一个replica server的集合;meta server只和该集合中节点进行通信,响应其`start_recovery`RPC,以进行信息收集,用于bootstrap;meta server和任何节点的交互失败都会导致恢复流程的失败。
diff --git a/_administration/monitoring.md b/_administration/monitoring.md
new file mode 100644
index 0000000..2e6c5d1
--- /dev/null
+++ b/_administration/monitoring.md
@@ -0,0 +1,223 @@
+---
+title: 可视化监控
+layout: page
+menubar: administration_menu
+---
+
+## 组件
+
+从[v1.12.0](https://github.com/XiaoMi/pegasus/releases)开始, Pegasus 支持使用 [Prometheus](https://prometheus.io/) 和 [Grafana](https://grafana.com/) 进项监控项的采集和展示。
+
+- Prometheus
+
+> Prometheus 是一款开源的系统监控和报警套件。它可以通过将采集被监控系统的监控项存入自身的时序数据库中,并且通过丰富的多维数据查询语言,满足用户的不同数据展示需求。
+
+- Grafana
+
+> Grafana 是一款开源的数据分析和展示平台。支持包括 Prometheus 在内的多个主流时序数据库源。通过对应的数据库查询语句,从数据源中获取展现数据。通过灵活可配置的 Dashboard,快速的将这些数据以图表的形式展示给用户。
+
+**注意***
+
+本文档仅提供一种使用 Prometheus 和 Grafana 进行 Pegasus 监控数据采集和展示的方式。Pegasus**不包含、不维护这些组件**。更多关于这些组件的详细介绍,请移步对应官方文档进行查阅。
+
+## 配置 Prometheus
+
+本小节介绍如何配置 Prometheus。
+
+### 第一步: 配置Pegasus服务
+
+Pegasus默认不向任何外部系统推送监控信息, 你需要修改配置文件以启用prometheus推送。具体如下:
+
+```ini
+[pegasus.server]
+  perf_counter_sink = prometheus
+  prometheus_port = 9091
+```
+
+> **注**: 如果你想要测试你的Pegasus节点是否正确开启了Prometheus推送, 可以检查 `http://{pegasus_host}:{prometheus_port}/metrics` 是否能正常访问.
+
+#### Onebox使用Prometheus
+
+如果使用onebox, 请首先修改配置文件src/server/config.min.ini以开启Prometheus推送. 但不需要改动 `prometheus_port`.
+
+```ini
+[pegasus.server]
+  perf_counter_sink = prometheus
+  prometheus_port = @PROMETHEUS_PORT@
+```
+
+由于onebox模式下多个Pegasus服务进程部署在一台机器上,因此各replica、meta、collector的prometheus端口存在冲突问题. 当前我们的解决办法是对每个进程配置单独的prometheus port:
+
+- collector : 9091
+- meta: [9092, 9093, 9094...]
+- replica: [9092+{META_COUNT}, 9093+{META_COUNT}, 9094+{META_COUNT}...]
+
+例如一个2 meta, 3 replica, 1 collector的onebox集群, 其端口对应如下:
+
+```sh
+./run.sh start_onebox -r 3 -m 2 -c
+```
+
+- meta1: 9092, meta2: 9093
+- replica1: 9094, replica2: 9095, replica3: 9096
+
+### 第二步: 安装及运行 Prometheus
+
+```sh
+wget https://github.com/prometheus/prometheus/releases/download/v2.15.2/prometheus-2.15.2.linux-amd64.tar.gz
+tar xvfz prometheus-2.15.2.linux-amd64.tar.gz
+cd prometheus-2.15.2.linux-amd64
+```
+
+修改prometheus目录下的prometheus.yml文件,配置模板如下所示:
+
+```yaml
+global:
+  scrape_interval: 5s
+
+scrape_configs:
+  - job_name: 'pegasus'
+    static_configs:
+      - targets: ['collector_host:9091']
+        labels:
+          group: collector
+
+      - targets: ['meta_host1:9091', 'meta_host2:9091', 'meta_host3:9091']
+        labels:
+          group: meta
+
+      - targets: ['replica_host1:9091', 'replica_host2:9091', 'replica_host3:9091']
+        labels:
+          group: replica
+      #
+      # NOTE: Add the following lines if node exporter is deployed.
+      # - targets:
+      #     [
+      #       'node_exporter_host1:9100',
+      #       'node_exporter_host2:9100',
+      #       ...
+      #       'node_exporter_hostn:9100',
+      #     ]
+      #   labels:
+      #     group: node_exporter
+```
+
+对于上述通过 `./run.sh start_onebox -r 3 -m 2 -c` 启动的onebox集群, 实际配置如下:
+
+```yaml
+global:
+  scrape_interval: 5s
+
+scrape_configs:
+  - job_name: "pegasus"
+    static_configs:
+      - targets: ["0.0.0.0:9091"]
+        labels:
+          group: collector
+
+      - targets: ["0.0.0.0:9092", "0.0.0.0:9093"]
+        labels:
+          group: meta
+
+      - targets: ["0.0.0.0:9094", "0.0.0.0:9095", "0.0.0.0:9096"]
+        labels:
+          group: replica
+```
+
+修改完prometheus.yml之后,启动prometheus:
+
+```sh
+./prometheus --config.file=prometheus.yml
+```
+
+进入网址 [localhost:9090](http://localhost:9090) 看到如下界面即表示到这一步为止是成功的。
+
+![prometheus-server](/assets/images/prometheus-server.png)
+
+> **注**: 如果你想要测试Prometheus是否正确配置, 可以检查 `http://{prometheus_host}:9090/targets` 查看各节点的监控上报状态.
+
+在Expression框内输入需要查找的内容,点击Excute即可在Element中展示查找到的内容,当选择Graph时可以显示该内容一段时间内数值变化情况。
+
+***注意***
+
+1. 实际运维过程中, 我们通常需要获取一些机器及操作系统的监控指标, 如cpu.busy, disk.iostat等等, 所以在部署Pegasus集群的时候,可以考虑在每一台机器上部署一个node exporter后台实例,具体可参考: [Node Exporter](https://github.com/prometheus/node_exporter)
+
+2. [Alert Manager](https://github.com/prometheus/alertmanager) 为 Prometheus 报警组件,需单独部署(暂不提供方案,可参照官方文档自行搭建)。通过 Alert Manager,用户可以配置报警策略,接收邮件、短信等报警。
+
+3. 目前我们的prometheus.yml使用的是静态配置的方式(static_configs),其缺点是当动态扩容缩容的时候需要手动去修改该静态配置。当前Prometheus支持多种动态服务发现方式,例如k8s、consul和dns等等,用户也可以根据自己需求去定制实现。详情请参考文档:[配置文件说明](https://prometheus.io/docs/prometheus/latest/configuration/configuration/)、[实现动态服务发现](https://prometheus.io/blog/2018/07/05/implementing-custom-sd/)
+
+## 配置 Grafana
+
+本小节介绍如何配置 Grafana。
+
+### 第一步: 安装及运行 Grafana
+
+首先下载grafana二进制包:
+
+```sh
+wget https://dl.grafana.com/oss/release/grafana-6.0.0.linux-amd64.tar.gz //如果报错,可以尝试在后面添加--no-check-certificate
+tar -zxvf grafana-6.0.0.linux-amd64.tar.gz
+cd grafana-6.0.0
+```
+
+启动Grafana
+
+```sh
+./bin/grafana-server web
+```
+
+观察到如下输出,即为启动成功
+
+```Linux
+INFO[07-24|14:36:59] Starting Grafana                         logger=server version=6.0.0 commit=34a9a62 branch=HEAD compiled=2019-02-25T22:47:26+0800
+...
+INFO[07-24|14:37:00] HTTP Server Listen                       logger=http.server address=0.0.0.0:3000 protocol=http subUrl= socket=
+INFO[07-24|14:37:00] cleanup of expired auth tokens done      logger=auth count=2
+```
+
+### 第二步: 添加 Prometheus 数据源
+
+1. 登录 Grafana 界面。如下图所示:
+
+    ![grafana-login](/assets/images/grafana-login.png)
+
+    - 默认地址:[http://localhost:3000](http://localhost:3000)
+    - 默认账户:admin
+    - 默认密码:admin
+
+    > **注意:**
+    >
+    > Change Password 步骤可以选择 Skip。
+
+2. 点击 Grafana 侧边栏菜单 **Configuration** 中的 **Data Source**。
+
+3. 点击 **Add data source**。
+
+4. 指定数据源的相关信息:
+
+    - 在 **Name** 处,为数据源指定一个名称。
+    - 在 **Type** 处,选择 **Prometheus**。
+    - 在 **URL** 处,指定 Prometheus 的 IP 地址。
+    - 根据需求指定其它字段。
+
+5. 点击 Add 保存新的数据源。
+
+### 第三步: 导入 Pegasus DashBoard
+
+目前Pegasus拥有一个DashBoard,用于提供一些基本的监控信息。其相应的json文件: [Pegasus json文件](https://github.com/XiaoMi/pegasus-common/releases/download/deps/grafana-dashboard.json)
+
+下载了json文件后,可以通过import的方式将其导入进去。其步骤如下:
+
+进入grafana,点击左边框的"+",选择import,进入import页面
+
+![grafana-import-panel](/assets/images/grafana-import-panel-upload.png)
+
+点击右上角的"Upload .json File",然后选择文件。选择之后进入如下页面
+
+![grafana-import-panel](/assets/images/grafana-import-panel.png)
+
+然后点击左下角的"import"按钮完成导入,并进入到Pegasus相应的DashBoard,其页面如下所示
+
+![grafana-import-panel](/assets/images/grafana-dashboard-pegasus.png)
+
+从图中可以看出,Pegasus的DashBoard分为两个row: Pegasus-Cluster和Pegasus-Table,分别代表集群级别监控和表级监控。在左上角的cluster_name后输入具体的集群名字,便可以查看该集群相应的各种监控信息。
diff --git a/_administration/partition-split.md b/_administration/partition-split.md
new file mode 100644
index 0000000..b733e4b
--- /dev/null
+++ b/_administration/partition-split.md
@@ -0,0 +1,98 @@
+---
+title: Partition Split
+layout: page
+menubar: administration_menu
+---
+
+# 功能简介
+在pegasus中,表的partition个数是在创建时指定的,并且不会动态改变,但随着数据量不断增大,可能出现partition数据过大的情况,这样可能会导致读写效率下降,因此需要人工增大partition个数来保障服务质量。
+
+在pegasus的设计中,partition个数为2的幂次,目前split功能会将partition个数翻倍,partition(i)将会被分裂为partition(i)和partition(i+original_count) 。例如,原表有4个partition,在split之后将有8个partition,partition(0)将会分裂为partition(0)和partition(4),partition(1)将会分裂为partition(1)和partition(5)……以此类推。我们将partition(i)称为parent partition,partition(i+original_count)称为 child partition
+
+# 接口描述
+`partition_split <app_name> <new_partition_count>`
+* 若当前表不可用,返回 ERR_APP_NOT_EXIST
+* 若new_partition_count != old_partition_count*2,返回 ERR_INVALID_PARAMETERS
+* 若表正在进行split,返回 ERR_BUSY
+* 若split成功返回 ERR_OK
+
+# 设计与实现
+
+## 整体流程
+partition split过程可分为以下几个步骤:
+1. client发送partition split请求给meta server。
+2. replica server通过与meta server的config_sync发现表partition个数发生变化。
+3. 每个parent partition通过异步learn的方式复制自己所有数据得到child partition。
+4. 当一个group中的所有child partition就绪后,primary给meta server发送注册child partition的请求。
+5. meta server注册child partition。
+6. 所有child partition被注册完成后,split过程结束。
+7. 清理无效数据,详见[如何删除无效数据](#如何删除无效数据)。
+
+## Partition-Split过程中的读写
+在partition split过程中,读写流程可保持正常进行,在注册child partition期间有短暂拒绝服务。
+
+为了方便说明,假设app的partition个数为4,分裂后为8,而client希望访问的数据,在分裂前由partition(1)服务,分裂后由partition(5)服务。从split开始到partition进行异步learn,都仍将由partition(1)为client进行服务,但当primary给meta server发送注册child partition请求后,partition(1)将拒绝client的读写请求,直到meta server注册完成。当注册完成后,client并不知道将由partition(5)为其服务,仍将请求发送给partition(1),这时partition(1)会提示client更新访问路由表,而更新路由表是对用户透明的。总体来说服务不可用时间非常短。
+
+## 为什么需要指定partition个数
+由于partition split功能不能取消,且没有减小partition的功能,因此执行partition split需谨慎,虽然目前单次split只能使partition个数翻倍,但仍需指定partition count,这是为了防止client多次重试这个非幂等操作导致partition个数非预期增大。
+
+## 如何删除无效数据
+执行partition
+split前需要保证磁盘空间可用超过50%,内存可用充足,因为split操作首先需要对每个partition进行复制,在split完成后,pegasus会通过rocksdb提供的filter功能在后台删除split造成的无效数据。若磁盘资源紧张或者希望尽快删除无效数据,可在集群CPU空闲期间执行manual_compact功能,手动触发filter,命令详情参见[操作示例](#操作示例)。
+
+## Partition-Split与热点问题
+split功能主要是为了保障在数据量非预期增长情况下的服务质量,并不能完全解决单个partition过热的问题,pegasus的数据模式是hash分片,在split完成后流量并不能保障是被平分在两个partition上,这个是依赖于用户的hashkey决定的,只能说可以缓解热点问题,并且partition split是表级命令,暂时不支持针对单个partition的partition split。
+
+# 操作示例
+## 执行Partition-Split
+在split前,建议先通过`app_stat`命令查看待split表的大小,再执行如下命令:
+```
+>>> partition_split split_table 8
+split app split_table succeed
+```
+通过shell工具执行partition split,将split_table partition count从4设置为8
+
+## Partition-Split过程中
+```
+>>> app split_table -d
+[Parameters]
+app_name: split_table
+detailed: true
+
+[Result]
+app_name          : split_table
+app_id            : 2
+partition_count   : 8
+max_replica_count : 3
+details           :
+pidx      ballot    replica_count       primary                                 secondaries                             
+0         3         3/3                 10.239.35.234:34802                     [10.239.35.234:34803,10.239.35.234:34801]
+1         3         3/3                 10.239.35.234:34803                     [10.239.35.234:34801,10.239.35.234:34802]
+2         3         3/3                 10.239.35.234:34801                     [10.239.35.234:34803,10.239.35.234:34802]
+3         3         3/3                 10.239.35.234:34802                     [10.239.35.234:34801,10.239.35.234:34803]
+4         -1        0/0                 -                                       []
+5         -1        0/0                 -                                       []
+6         -1        0/0                 -                                       []
+7         -1        0/0                 -                                       []
+
+node                                    primary   secondary total     
+10.239.35.234:34801                     1         3         4         
+10.239.35.234:34802                     2         2         4         
+10.239.35.234:34803                     1         3         4         
+                                        4         8         12        
+
+fully_healthy_partition_count   : 4
+unhealthy_partition_count       : 4
+write_unhealthy_partition_count : 4
+read_unhealthy_partition_count  : 4
+
+list app split_table succeed
+```
+通过`app <table_name> -d`命令查看当前表的详情,ballot=-1表示该partition还没有被meta server注册
+
+## Partition-Split完成
+同样通过`app <table_name> -d`查看表详情,当发现所有ballot都大于0时表示所有partition都被注册,若当前流量不大可以将meta server设置为lively状态,进行负载均衡,并且通过`app_stat`命令查看表的大小,应该是split前的2倍左右
+
+## 手动触发Manual-Compact
+关于Manual compact详情可参见[Manual compact功能](manual-compact),在集群CPU空闲时进行操作,建议命令示例如下:
+`./scripts/pegasus_manual_compact.sh -c <meta_list> -a <table_name>` 
diff --git a/_administration/rebalance.md b/_administration/rebalance.md
new file mode 100644
index 0000000..2576e3b
--- /dev/null
+++ b/_administration/rebalance.md
@@ -0,0 +1,332 @@
+---
+title: 负载均衡
+layout: page
+menubar: administration_menu
+---
+
+本文档主要介绍Pegasus负载均衡的概念、使用和设计。
+
+## 概念篇
+
+在Pegasus中,负载均衡主要包括以下几个方面的内容:
+
+1. 如果某个partition分片不满足一主两备,要选择一个机器将缺失的分片补全。这个过程在Pegasus中叫做`cure`。
+2. 当所有的分片都满足一主两备份后,对集群各个replica server上分片的个数做调整,尽量让每个机器上服务的分片数都维持在一个相近的水平上。这个过程在Pegasus中叫做`balance`。
+3. 如果一个replica server上挂载了多个磁盘,并且通过配置文件`data_dirs`提供给Pegasus使用。replica server要尽量让每个磁盘上分片的数量都维持在一个相近的水平上。
+
+围绕这几点内容,Pegasus引入了一些概念方便描述这些情况:
+
+1. Partition的健康状况
+
+   Pegasus为Partition定义了几种健康状况:
+   * 【fully healthy】: 健康的,完全满足一主两备
+   * 【unreadable】: 分片不可读了。指的是分片缺少primary, 但有一个或两个secondary。
+   * 【readable but unwritable】: 分片可读但是不可写。指的是只剩下了一个primary,两个secondary副本全部丢失
+   * 【readable and writable but unhealthy】: 分片可读可写,但仍旧不健康。指的是三副本里面少了一个secondary
+   * 【dead】: partition的所有副本全不可用了,又称之为DDD状态。
+
+![pegasus-healthy-status](/assets/images/pegasus-healthy-status.png){:class="img-responsive"}
+
+   当通过pegasus shell来查看集群、表以及分片的状态时,会经常看到对分片健康情况的整体统计或单个描述。譬如通过`ls -d`命令,可以看到各个表处于不同健康状况的partition的个数,包括这些:
+   * fully_healthy:完全健康。
+   * unhealthy:不完全健康。
+   * write_unhealthy:不可写,包括上面的readable but unwritable和dead。
+   * read_unhealthy:不可读,包括上面的unreadable和dead。
+
+2. Meta server的运行level
+
+   meta server的运行level决定了meta server会对整个分布式系统做到何种程度的管理。最常用的运行level包括:
+   * blind:在这种运行level之下,meta_server拒绝任何可能会修改元数据状态的操作。一般在做zookeeper迁移的时候会用到这个level。
+   * steady:在这种运行level下,meta server只做`cure`,即只处理unhealthy的partition。
+   * lively:在这种运行level下,一旦所有partion都进入了healthy, meta server就会尝试进行`balance`,来调整各个机器的分片数。
+
+## 操作篇
+
+### 观察系统情况
+
+可以通过pegasus的shell客户端来观察系统的Partition情况:
+
+1. nodes -d
+
+   可以用来观察系统中每个节点的partition个数: 
+
+   ```
+   >>> nodes -d
+   address              status              replica_count       primary_count       secondary_count
+   10.132.5.1:32801     ALIVE               54                  18                  36
+   10.132.5.2:32801     ALIVE               54                  18                  36
+   10.132.5.3:32801     ALIVE               54                  18                  36
+   10.132.5.5:32801     ALIVE               54                  18                  36
+   ```
+
+   如果节点间的partition个数分布差异太大,可以采用"set_meta_level lively"的命令来进行调整。
+
+2. app <table_name> -d
+   
+   可以用来某张表的所有partition的分布情况:可以观察到某个具体partition的组成,也可以汇总每个节点服务该表的partition个数。
+
+   ```
+   >>> app temp -d
+   [Parameters]
+   app_name: temp
+   detailed: true
+   
+   [Result]
+   app_name          : temp
+   app_id            : 14
+   partition_count   : 8
+   max_replica_count : 3
+   details           :
+   pidx      ballot    replica_count       primary                                 secondaries
+   0         22344     3/3                 10.132.5.2:32801                        [10.132.5.3:32801,10.132.5.5:32801]
+   1         20525     3/3                 10.132.5.3:32801                        [10.132.5.2:32801,10.132.5.5:32801]
+   2         19539     3/3                 10.132.5.1:32801                        [10.132.5.3:32801,10.132.5.5:32801]
+   3         18819     3/3                 10.132.5.5:32801                        [10.132.5.3:32801,10.132.5.1:32801]
+   4         18275     3/3                 10.132.5.5:32801                        [10.132.5.2:32801,10.132.5.1:32801]
+   5         18079     3/3                 10.132.5.3:32801                        [10.132.5.2:32801,10.132.5.1:32801]
+   6         17913     3/3                 10.132.5.2:32801                        [10.132.5.1:32801,10.132.5.5:32801]
+   7         17692     3/3                 10.132.5.1:32801                        [10.132.5.3:32801,10.132.5.2:32801]
+   
+   node                                    primary   secondary total
+   10.132.5.1:32801                        2         4         6
+   10.132.5.2:32801                        2         4         6
+   10.132.5.3:32801                        2         4         6
+   10.132.5.5:32801                        2         4         6
+                                           8         16        24
+   
+   fully_healthy_partition_count   : 8
+   unhealthy_partition_count       : 0
+   write_unhealthy_partition_count : 0
+   read_unhealthy_partition_count  : 0
+   
+   list app temp succeed
+   ```
+
+3. server_stat
+   
+   可以用来观察各个replica server当前的一些监控数据。如果想分析流量的均衡程度,要重点观察各个操作的qps和latency。对于数据值明显异常的节点(和其他节点差异太大),需要排查下partition个数是不是分布不均,或者是不是出现了某个分片的读写热点。
+
+   ```
+   >>> server_stat -t replica-server
+   COMMAND: server-stat
+   
+   CALL [replica-server] [10.132.5.1:32801] succeed: manual_compact_enqueue_count=0, manual_compact_running_count=0, closing_replica_count=0, disk_available_max_ratio=88, disk_available_min_ratio=78, disk_available_total_ratio=85, disk_capacity_total(MB)=8378920, opening_replica_count=0, serving_replica_count=54, commit_throughput=0, learning_count=0, shared_log_size(MB)=4, memused_res(MB)=2499, memused_virt(MB)=4724, get_p99(ns)=0, get_qps=0, multi_get_p99(ns)=0, multi_get_qps=0, multi_ [...]
+   CALL [replica-server] [10.132.5.2:32801] succeed: manual_compact_enqueue_count=0, manual_compact_running_count=0, closing_replica_count=0, disk_available_max_ratio=88, disk_available_min_ratio=79, disk_available_total_ratio=86, disk_capacity_total(MB)=8378920, opening_replica_count=0, serving_replica_count=54, commit_throughput=0, learning_count=0, shared_log_size(MB)=4, memused_res(MB)=2521, memused_virt(MB)=4733, get_p99(ns)=0, get_qps=0, multi_get_p99(ns)=0, multi_get_qps=0, multi_ [...]
+   CALL [replica-server] [10.132.5.3:32801] succeed: manual_compact_enqueue_count=0, manual_compact_running_count=0, closing_replica_count=0, disk_available_max_ratio=90, disk_available_min_ratio=78, disk_available_total_ratio=85, disk_capacity_total(MB)=8378920, opening_replica_count=0, serving_replica_count=54, commit_throughput=0, learning_count=0, shared_log_size(MB)=4, memused_res(MB)=2489, memused_virt(MB)=4723, get_p99(ns)=0, get_qps=0, multi_get_p99(ns)=0, multi_get_qps=0, multi_ [...]
+   CALL [replica-server] [10.132.5.5:32801] succeed: manual_compact_enqueue_count=0, manual_compact_running_count=0, closing_replica_count=0, disk_available_max_ratio=88, disk_available_min_ratio=82, disk_available_total_ratio=85, disk_capacity_total(MB)=8378920, opening_replica_count=0, serving_replica_count=54, commit_throughput=0, learning_count=0, shared_log_size(MB)=4, memused_res(MB)=2494, memused_virt(MB)=4678, get_p99(ns)=0, get_qps=0, multi_get_p99(ns)=0, multi_get_qps=0, multi_ [...]
+   
+   Succeed count: 4
+   Failed count: 0
+   ```
+
+4. app_stat -a <app_name>
+
+   可以用来观察某个表中,各个partition的统计信息。对于数据值明显异常的分片,要关注是不是出现了分片热点。
+
+   ```
+   >>> app_stat -a temp
+   pidx                 GET   MULTI_GET         PUT   MULTI_PUT         DEL   MULTI_DEL        INCR         CAS        SCAN     expired    filtered    abnormal  storage_mb  file_count
+   0                      0           0           0           0           0           0           0           0           0           0           0           0           0           3
+   1                      0           0           0           0           0           0           0           0           0           0           0           0           0           1
+   2                      0           0           0           0           0           0           0           0           0           0           0           0           0           4
+   3                      0           0           0           0           0           0           0           0           0           0           0           0           0           2
+   4                      0           0           0           0           0           0           0           0           0           0           0           0           0           3
+   5                      0           0           0           0           0           0           0           0           0           0           0           0           0           2
+   6                      0           0           0           0           0           0           0           0           0           0           0           0           0           1
+   7                      0           0           0           0           0           0           0           0           0           0           0           0           0           3
+                          0           0           0           0           0           0           0           0           0           0           0           0           0          19
+   ```
+
+### 控制集群的负载均衡
+
+Peagsus提供以下几种命令来控制集群的负载均衡:
+
+1. set_meta_level
+
+   这个命令用来控制meta的运行level,支持以下几种level:
+   * freezed:meta server会停止unhealthy partition的cure工作,一般在集群出现较多节点宕机或极其不稳定的情况下使用,另外如果集群的节点数掉到一个数量或者比例以下(通过配置文件`min_live_node_count_for_unfreeze`和`node_live_percentage_threshold_for_update`控制)就会自动变为freezed,等待人工介入。
+   * steady:meta server的默认level, 只做cure,不做balance。
+   * lively:meta server会调整分片数,力求均衡。
+   
+   可以使用`cluster_info`或者`get_meta_level`查看当前集群的运行level。
+
+   关于调整的一些建议:
+   * 先用shell的`nodes -d`命令查看集群是否均衡,当不均衡时再进行调整。通常在以下几种情况发生后,需要开启lively进行调整:
+     * 新创建了表,这个时候分片数目可能不均匀。
+     * 集群上线、下线、升级了节点,这时候分片数目也可能不均匀。
+     * 有节点宕机,一些replica迁移到了别的节点上。
+   * 调整过程会触发replica迁移,影响集群可用度,虽然影响不大,但是如果对可用度要求很高,并且调整需求不紧急,建议在**低峰时段**进行调整。
+   * 调整完成后通过`set_meta_level steady`将level重置为steady状态,避免在平时进行不必要的replica迁移,减少集群抖动。
+   * Pegasus还提供了一些精细控制balance的命令,参见[负载均衡的高级选项](#负载均衡的高级选项)。
+
+2. balance
+
+   balance命令用来手动发送分片迁移的命令。支持的迁移类型:
+   * move_pri:把某个partition下的primary和secondary做调换(本质上为两步:1.将from降级;2.将to升级。如果meta server在第1步完成后挂掉,新的meta server不会继续进行第2步,可以视为move_pri命令被中断了)
+   * copy_pri:把某个partition下的primary迁移到一个新节点下
+   * copy_sec:把某个partition下的secondary迁移到一个新节点下
+
+   **注意在使用时,请保证meta server处在steady状态,不然命令无法生效。**
+
+   参见以下样例(不相关的输出已经被删去):
+   ```
+   >>> get_meta_level
+   current meta level is fl_steady
+   
+   >>> app temp -d
+   pidx      ballot    replica_count       primary                                 secondaries
+   0         3         3/3                 10.231.58.233:34803                     [10.231.58.233:34802,10.231.58.233:34801]
+   
+   list app temp succeed
+   
+   >>> balance -g 1.0 -p move_pri -f 10.231.58.233:34803 -t 10.231.58.233:34802
+   send balance proposal result: ERR_OK
+   
+   >>> app temp -d
+   pidx      ballot    replica_count       primary                                 secondaries
+   0         5         3/3                 10.231.58.233:34802                     [10.231.58.233:34801,10.231.58.233:34803]
+   list app temp succeed
+   ```
+
+3. propose
+   
+   propose命令用来发送更低原语的分片调整命令,主要有以下几种:
+   * assign_primary:把某个partition的primary分配到某台机器上
+   * upgrade_to_primary:把某个partition的secondary升级为primary
+   * add_secondary: 为某个partition添加secondary
+   * upgrade_to_secondary: 把某个partition下的某个learner升级为secondary
+   * downgrade_to_secondary:把某个partition下的primary降级为secondary
+   * downgrade_to_inactive:把某个partition下的primary/secondary降级为inactive状态
+   * remove:移除掉某个partition下的某个副本
+
+   ```
+   >>> app temp -d
+   pidx      ballot    replica_count       primary                                 secondaries                             
+   0         5         3/3                 10.231.58.233:34802                     [10.231.58.233:34801,10.231.58.233:34803]
+   list app temp succeed
+   >>> propose -g 1.0 -p downgrade_to_inactive -t 10.231.58.233:34802 -n 10.231.58.233:34801
+   send proposal response: ERR_OK
+   >>> app temp -d
+   pidx      ballot    replica_count       primary                                 secondaries                             
+   0         7         3/3                 10.231.58.233:34802                     [10.231.58.233:34803,10.231.58.233:34801]
+   list app temp succeed
+   ```
+
+   在上面的例子中,propose命令希望把10.231.38.233:34801降级。所以需要把这个命令发给partition的primary(10.231.58.233:34802),由它来执行具体某个副本降级的事宜。注意这里体现了pegasus系统的设计理念:**meta server负责管理primary , pimary负责管理partition下的其他副本**。
+   
+   上面的例子也许看不出10.231.38.233:34801被降级的痕迹。这是因为系统cure功能的存在,对于unhealthy的partition会迅速修复。你可以通过观察ballot的变化来确认这个命令已经生效了。
+
+   正常情况下,你应该不会需要使用到propose命令。
+
+### 负载均衡的高级选项
+
+meta server提供了一些更细粒度的参数用来做负载均衡的控制。这些参数是通过**remote_command**命令来调整的:
+
+#### 通过help查看所有的remote_command
+
+```
+>>> remote_command -l 127.0.0.1:34601 help
+COMMAND: help
+
+CALL [user-specified] [127.0.0.1:34601] succeed: help|Help|h|H [command] - display help information
+repeat|Repeat|r|R interval_seconds max_count command - execute command periodically
+...
+meta.lb.assign_delay_ms [num | DEFAULT]
+meta.lb.assign_secondary_black_list [<ip:port,ip:port,ip:port>|clear]
+meta.lb.balancer_in_turn <true|false>
+meta.lb.only_primary_balancer <true|false>
+meta.lb.only_move_primary <true|false>
+meta.lb.add_secondary_enable_flow_control <true|false>
+meta.lb.add_secondary_max_count_for_one_node [num | DEFAULT]
+...
+
+Succeed count: 1
+Failed count: 0
+```
+
+[remote_command](https://github.com/XiaoMi/rdsn/blob/a8c2d46568889902df820ac65e045a1e78aa84d4/include/dsn/tool-api/command_manager.h)是pegasus的一个特性, 允许一个server注册一些命令,然后命令行可以通过rpc调用这些命令。这里我们使用**help**来访问meta server leader,获取meta server端支持的所有命令。例子中已经略掉了所有不相关的行,只留下以"meta.lb"开头的所有和负载均衡相关的命令。
+
+由于文档和代码的不一致问题,文档里不一定覆盖了当前meta所有的lb控制命令。如果想获取最新的命令列表,请用最新的代码手动执行一下help。
+
+#### assign_delay_ms
+
+assign_delay_ms用来控制**当partition缺少一个secondary时,我们要延时多久才选择一个新的**。之所以这么做,是因为一个副本的掉线可能是临时性的,如果不给予一定的缓冲就选择新的secondary, 可能会导致巨量的数据拷贝。
+
+```
+>>> remote_command -t meta-server meta.lb.assign_delay_ms
+COMMAND: meta.lb.assign_delay_ms
+CALL [meta-server] [127.0.0.1:34601] succeed: 300000
+CALL [meta-server] [127.0.0.1:34602] succeed: unknown command 'meta.lb.assign_delay_ms'
+CALL [meta-server] [127.0.0.1:34603] succeed: unknown command 'meta.lb.assign_delay_ms'
+Succeed count: 3
+Failed count: 0
+>>> remote_command -t meta-server meta.lb.assign_delay_ms 10
+COMMAND: meta.lb.assign_delay_ms 10
+CALL [meta-server] [127.0.0.1:34601] succeed: OK
+CALL [meta-server] [127.0.0.1:34602] succeed: unknown command 'meta.lb.assign_delay_ms'
+CALL [meta-server] [127.0.0.1:34603] succeed: unknown command 'meta.lb.assign_delay_ms'
+Succeed count: 3
+Failed count: 0
+>>> remote_command -t meta-server meta.lb.assign_delay_ms
+COMMAND: meta.lb.assign_delay_ms
+CALL [meta-server] [127.0.0.1:34601] succeed: 10
+CALL [meta-server] [127.0.0.1:34602] succeed: unknown command 'meta.lb.assign_delay_ms'
+CALL [meta-server] [127.0.0.1:34603] succeed: unknown command 'meta.lb.assign_delay_ms'
+Succeed count: 3
+Failed count: 0
+```
+
+如例所示,命令不加参数表示返回当前设定的值。加参数表示期望的新值。
+
+#### assign_secondary_black_list
+
+该命令用来设定**添加secondary的黑名单**。这个命令在批量下线集群节点的时候非常有用, 例如: 
+
+#### add secondary时候的流控
+
+在某些时候, 负载均衡的决策算法可能会要求一个机器上要新增不少secondary副本, 如
+* 一个或多个节点的宕机,会导致正常的节点瞬时接受很多的芬片
+* 新节点的加入,可能会有大量的涌入。
+
+但在执行这些增加分片的决策动作时,我们应该避免同一时刻有大量的secondary分片同时添加, 因为
+* 添加secondary副本基本会涉及数据拷贝, 如果量太大可能会对正常读写造成影响
+* 带宽总量是有限的, 如果由分给多个添加分片的任务去分享这些带宽, 那么每个任务执行的时常都会拉长, 从而让系统长期处在一个**大量分片都不健康的状态下**, 增加了稳定性的风险。
+
+所以, pegasus用两个命令来对流控做支持:
+1. meta.lb.add_secondary_enable_flow_control: 表示是否开启流控的feature。
+2. meta.lb.add_secondary_max_count_for_one_node: 表示对于每个节点,同时执行多少个add_secondary的动作。
+
+#### 精细控制balancer
+
+balancer表示把各节点个数调匀的过程。在目前的pegasus实现中,balancer过程大概可以用四点来概括:
+1. 尽量通过角色互换来做到primary均衡
+2. 如果1做不到让primary变均匀,通过拷数据来做到primary均衡
+3. 在2做完后,通过拷数据做到secondary的均衡
+4. 分别针对每个表做1-2-3的动作
+
+Pegasus提供了一些控制参数给些过程可以提供更精细的控制:
+* meta.lb.only_primary_balancer: 对于每个表,只进行1和2(减少copy secondary带来的数据拷贝)
+* meta.lb.only_move_primary: 对于每个表,primary调节的时候只考虑方法1(减少copy primary带来的数据拷贝)
+* meta.lb.balancer_in_turn:各个表的balancer用串行的方式做,而不是并行进行(用于调试,观察系统行为)
+
+### 一些命令的使用案例
+
+通过把上面的这些负载均衡原语结合起来,pegasus提供了一些一些脚本来执行滚动升级、节点下线等一些操作,如:
+
+1. [scripts/migrate_node.sh](https://github.com/XiaoMi/pegasus/blob/master/scripts/migrate_node.sh)
+
+   这个脚本用来把某个节点上服务的所有primary都赶走
+
+2. [scripts/pegasus_rolling_update.sh](https://github.com/XiaoMi/pegasus/blob/master/scripts/pegasus_rolling_update.sh)
+
+   用来对集群中的节点做在线滚动升级
+
+3. [scripts/pegasus_offline_node_list.sh](https://github.com/XiaoMi/pegasus/blob/master/scripts/pegasus_offline_node_list.sh)
+
+   用来做一批节点的下线
+
+不过有部分脚本的逻辑依赖小米的[minos部署系统](https://github.com/XiaoMi/minos)。这里希望大家可以帮助pegasus, 可以支持更多的部署系统。
+
+## 设计篇
+
+待补充。
diff --git a/_administration/remote-commands.md b/_administration/remote-commands.md
new file mode 100644
index 0000000..7406e51
--- /dev/null
+++ b/_administration/remote-commands.md
@@ -0,0 +1,120 @@
+---
+title: 远程命令
+layout: page
+show_sidebar: false
+menubar: administration_menu
+---
+
+# 功能目标
+
+Pegasus基于rDSN框架构建,可以利用到rDSN框架的很多有用的功能,远程命令就是其中一个。
+
+rDSN框架通过RPC对外提供服务,除了开发者注册的用于业务逻辑的RPC服务,还提供了内建的RPC服务`RPC_CLI_CLI_CALL`,接口定义如下:
+```idl
+struct command
+{
+    1:string       cmd;
+    2:list<string> arguments;
+}
+
+service cli
+{
+    string call(1:command c);
+}
+```
+RPC的请求参数为command结构体,指定远程命令的`cmd`和`arguments`;RPC的返回结果是`string`。
+
+开发者可以注册各种远程命令,对远程命令处理并返回结果。你可以通过shell的`remote_command`接口向Pegasus的进程发送远程命令,以执行某些操作。
+
+通过远程命令执行操作有这些好处:
+* 直接。命令直接发给目标进程。
+* 快速生效。命令一般都是立即执行。
+* 开发简单。注册和开发过程都很容易。
+
+# 支持命令
+
+Pegasus不同角色的进程支持不同的远程命令。但是collector没有监听端口,所以不支持远程命令。
+
+## rdsn内建命令
+
+| 命令 | 功能 
+| ----- | ---- | 
+| engine | 获取rdsn框架引擎的信息,主要是包含哪些线程池、每个线程池有多少个线程
+| system.queue | 获取各线程池执行队列的排队长度
+| server-info | 获取进程的基本信息,包括版本号、启动时间,对应shell的`server_info`子命令
+| server-stat | 获取进程的简要统计信息,包括get/put等操作的QPS和延迟、机器的内存和存储使用情况,对应shell的`server_stat`子命令
+| task-code | 获取该进程注册的task code列表
+| flush_log | 将最近缓冲区中的日志数据刷出到日志文件中,对应shell的`flush_log`子命令
+| reset-log-start-level | 动态修改日志的级别
+| perf-counters | 获取最近一个统计周期内的perf counter数据
+| config-dump | 获取该进程启动时的配置文件的信息
+
+## meta-server
+
+| 命令 | 功能 
+| ----- | ---- | 
+| meta.lb.assign_delay_ms | 动态修改配置`replica_assign_delay_ms_for_dropouts`
+| meta.lb.assign_secondary_black_list | 动态修改`add_secondary`操作的黑名单,名单中的节点在负载均衡中不再分派replica
+| meta.lb.balancer_in_turn | 动态修改配置`balancer_in_turn`,控制负载均衡app时是one-by-one执行还是并行执行
+| meta.lb.only_primary_balancer | 动态修改配置`only_primary_balancer`,控制负载均衡时是否只要求各机器的primary replica个数达到平衡
+| meta.lb.only_move_primary | 动态修改配置`only_move_primary`,控制负载均衡时是否只做primary replica迁移,不做replica数据拷贝
+| meta.lb.add_secondary_enable_flow_control | 动态修改配置`add_secondary_enable_flow_control`,控制负载均衡时是否对`add_secondary`操作进行流控
+| meta.lb.add_secondary_max_count_for_one_node | 动态修改配置`add_secondary_max_count_for_one_node`,控制负载均衡时如果进行流控,单个机器最多并发执行`add_secondary`操作的个数
+
+## replica-server
+
+| 命令 | 功能 
+| ----- | ---- | 
+| replica.kill_partition | 将指定的replica关闭,停止提供服务
+| replica.deny-client | 动态修改配置`deny_client_on_start`,控制是否拒绝客户端的读写请求
+| replica.verbose-client-log | 动态修改配置`verbose_client_log_on_start`,控制回复客户端的请求时是否打印ERROR日志
+| replica.verbose-commit-log | 动态修改配置`verbose_commit_log_on_start`,控制在提交写请求时是否打印DEBUG日志
+| replica.trigger-checkpoint | 对指定的replica手动触发`async_checkpoint`操作
+| replica.query-compact | 对指定的replica查询其执行[Manual-Compact](manual-compact)操作的状态
+| replica.query-app-envs | 对指定的replica查询其当前的[Table环境变量](table-env)
+| useless-dir-reserve-seconds | 动态修改无用文件夹的保留时间,方便快速释放存储空间,从[1.11.3版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.11.3)开始支持,参见[垃圾文件夹管理](#resource-management#垃圾文件夹管理)
+
+# 如何使用
+
+通过shell的`remote_command`子命令,可以向指定的一个或者多个进程发送远程命令。用法:
+```
+USAGE:  remote_command         [-t all|meta-server|replica-server] [-l ip:port,ip:port...] <command>
+                               [arguments...]
+```
+其中需要通过`-t`或者`-l`来指定目标进程:
+* `-t`:只向指定角色的所有进程发送。
+* `-l`:只向指定的地址发送,可以通过列表指定多个地址。
+
+如果你不知道目标进程支持哪些远程命令,可以发送`help`命令查看,譬如:
+```
+>>> remote_command -l 127.0.0.1:34801 help
+COMMAND: help
+
+CALL [user-specified] [127.0.0.1:34801] succeed: help|Help|h|H [command] - display help information
+repeat|Repeat|r|R interval_seconds max_count command - execute command periodically
+engine - get engine internal information
+system.queue - get queue internal information
+server-info - query server information
+server-stat - query selected perf counters
+task-code - query task code containing any given keywords
+flush-log - flush log to stderr or log file
+reset-log-start-level - reset the log start level
+perf-counters - query perf counters, supporting filter by POSIX basic regular expressions
+profile|Profile|p|P - performance profiling
+profiler data - get appointed data, using by pjs
+profiler.query|pq - query profiling data, output in json format
+config-dump - dump configuration
+replica.kill_partition [app_id [partition_index]]
+replica.deny-client <true|false>
+replica.verbose-client-log <true|false>
+replica.verbose-commit-log <true|false>
+replica.trigger-checkpoint [id1,id2,...] (where id is 'app_id' or 'app_id.partition_id')
+replica.query-compact [id1,id2,...] (where id is 'app_id' or 'app_id.partition_id')
+replica.query-app-envs [id1,id2,...] (where id is 'app_id' or 'app_id.partition_id')
+
+
+Succeed count: 1
+Failed count: 0
+```
+
+如果指定多个进程,就会并发地向所有进程发送命令,等待命令的返回结果,然后打印出来。
diff --git a/_administration/replica-recovery.md b/_administration/replica-recovery.md
new file mode 100644
index 0000000..b3e2ac2
--- /dev/null
+++ b/_administration/replica-recovery.md
@@ -0,0 +1,69 @@
+---
+title: Replica数据恢复
+layout: page
+menubar: administration_menu
+---
+
+# 原理
+
+通常来说,Pegasus的数据会存储3个副本。对于每个partition,正常情况下应当都有一主两备3个replica提供服务。
+
+但是,集群不可避免会发生节点宕机、网络异常、心跳失联等情况,造成副本丢失,对服务可用性产生影响。副本丢失的程度会影响读写的能力(在[负载均衡](rebalance#概念篇)里也有介绍):
+* 一主两备都可用:partition完全健康,可以**正常读和写**。
+* 一主一备可用:根据PacificA一致性协议,同样可以**安全地读和写**。
+* 只有一主可用:此时**不可写**,但是**可读**。
+* 全部不可用:此时**不可读写**。这种情况我们称之为**DDD**,即Dead-Dead-Dead的缩写,表示3个副本都不可用。
+
+以上情况中,除了**全部不可用的DDD状态**,其他情况下MetaServer都能自动补充副本,并最终恢复至完全健康状态。但是如果partition进入DDD状态,MetaServer就无法对其进行自动恢复,需要进行人工干预。
+
+[这个讨论](https://github.com/XiaoMi/rdsn/issues/80)中给出了进入DDD状态的例子。实际上,**只要某个partition进入DDD状态,且LastDrop的最后两个节点中有一个节点无法正常启动,就会进入需要人工干预的DDD状态**。而在线上集群多个节点的起起停停过程中,这种情况是很容易出现的。
+
+可以通过Shell工具的`ls -d`命令查看健康状况,如果`read_unhealthy`的个数大于0,就表示有partition进入了DDD状态。
+
+# DDD诊断工具
+
+从[v1.11.0版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.11.0)开始,Pegasus在Shell工具中提供了`ddd_diagnose`命令,以支持DDD自动诊断功能。
+
+命令用法:
+```
+ddd_diagnose [-g|--gpid appid|appid.pidx] [-d|--diagnose] [-a|--auto_diagnose]
+             [-s|--skip_prompt] [-o|--output file_name]
+```
+
+参数说明:
+* `-g`:指定app_id或者partition_id,譬如`-g 1`或者`-g 1.3`;如果不指定,则对所有表执行操作。
+* `-d`:进入诊断模式;如果不指定,则只显示DDD情况,但不进行诊断。
+* `-a`:开启自动诊断,即在保证数据一致性前提下,如果诊断工具能寻找到合适的备份作为该partition的主备份,则自动将其设置为primary,完成数据恢复,无需人工干预。
+* `-s`:避免交互模式;如果不指定,则在进行诊断的过程中可能会要求用户输入信息,以完成选择、确认、或者信息补充。
+* `-o`:将结果输出到指定文件。
+
+使用示例(如果看不清楚,请在单独的页面中打开图片):
+
+![ddd-diagnose.png](/assets/images/ddd-diagnose.png){:class="img-responsive"}
+
+上图是使用`ddd_diagnose`命令时的输出,我们通过红色的箭头标识依次进行说明:
+* 1:当前正在诊断的partition id。
+* 2:该partition在zookeeper上持久化的`ballot`和`last_committed_decree`信息,但是由于持久化不是实时的,该值可能小于实际值。
+* 3:dropped列表,列举曾经服务过该partition的节点的状态信息,重点关注:
+  * alive:该节点是否可用。
+  * ballot:该partition在该节点上replica的实际`ballot`;如果为-1,表示该partition在该节点上不存在数据。
+  * last_committed:该partition在该节点上replica的实际`last_committed_decree`。
+  * last_prepared:该partition在该节点上replica的实际`last_prepared_decree`。
+  * 最后如果有`<==`,表示该节点是 **最后一个变得不可用**(the latest) 还是 **倒数第二个变得不可用**(the secondary latest)。
+* 4:last_drops列表,记录节点变得不可用的时间顺序。
+* 5:ddd_reason,表示该partition变成DDD状态的原因。
+* 6:recommanded_primary,诊断工具在保证数据一致性的前提下推荐的新primary;如果无法给出,则为`none`。
+* 7:如果第6步给出了推荐节点,则提示用户采取下一步操作(如果指定了`-a`或者`-s`选项,则不会进入这一步,相当于总是自动选择y):
+  * y:采用该推荐节点作为新的primary。
+  * n:不采用该推荐节点,而是让用户选择其他的节点。
+  * s:忽略对该partition的诊断。
+* 8:如果第6步没有给出推荐节点或者第7步选择了n,则提示用户输入新的节点作为primary。
+* 9:生成propose命令,发送给MetaServer,将节点指定为新的primary,对该partition进行恢复。
+* 10:收到propose命令的回复,`ERR_OK`表示执行成功。
+* 11:显示当前已经完成的进度,分子为已经完成诊断的个数,分母为需要进行诊断的总个数。
+
+**推荐用法**:
+* `ddd_diagnose -d -a`,即开启自动诊断,对于无法自动完成诊断的partition,通过与用户交互来获得人工干预。这是最简单省心的用法,在大部分情况下都能自动完成恢复过程,无需人工干预。
+
+在无法完成自动诊断的情况下,会进入上图中的第8步,需要用户输入新的节点作为primary。那么,在dropped列表的众多节点中,如何选择最合适的节点作为primary呢?我们的建议是:
+* **在所有alive为true的节点中,选择`last_prepared`值最大的节点**,因为这样能尽可能多地恢复数据,减少数据丢失的可能性。
diff --git a/_administration/resource-management.md b/_administration/resource-management.md
new file mode 100644
index 0000000..0675260
--- /dev/null
+++ b/_administration/resource-management.md
@@ -0,0 +1,93 @@
+---
+title: 资源管理
+layout: page
+menubar: administration_menu
+---
+
+# 背景介绍
+Pegasus系统主要用到了资源包括SSD存储、内存、网络连接等。对这些资源的使用不要太满,否则系统可能会不稳定甚至崩溃。建议:
+* SSD存储使用不要超过每个节点的80%。
+* 内存使用不要超过每个节点的80%。
+* 网络连接数不要超过系统配置,建议连接数控制在5万以内。
+
+通过调整这些配置参数,可以减少一些SSD存储资源的使用:
+* 设置配置参数`max_replicas_in_group = 3`,参见[Replica备份数管理](#replica备份数管理)。
+* 设置配置参数`gc_disk_error_replica_interval_seconds = 3600`和`gc_disk_garbage_replica_interval_seconds = 3600`,参见[垃圾文件夹管理](#垃圾文件夹管理)。
+* 设置配置参数`checkpoint_reserve_min_count = 2`和`checkpoint_reserve_time_seconds = 1200`,参见[Rocksdb-Checkpoint管理](#rocksdb-checkpoint管理)。
+
+# Replica备份数管理
+
+Pegasus推荐使用3备份(1 primary + 2 secondary),在创建表的时候将`-r`参数设为3。
+
+但是在系统中实际存在的备份数可能不止3个,这是通过以下配置参数决定的:
+```
+[meta_server]
+    max_replicas_in_group = 4
+```
+
+该参数的意义是:允许一个partition中最多存在的备份数(包括活跃和不活跃的),默认为4(表示允许保留1个不活跃的备份)。为什么会有这个配置呢?这是因为,虽然正在提供服务的活跃备份是3个(1 primary + 2 secondary),但是在宕机恢复或者负载均衡过程中,可能发生replica从A节点迁移到B节点的情况,迁移完成后A节点上的数据实际上不需要了,但是在存储充足的情况下,可以将A节点的数据保留在SSD盘上,如果将来replica重新迁移到A节点,这些数据还有可能被重用,避免重新拷贝数据。
+
+如果想要节省SSD存储占用,希望无用的备份数据及时删除,就可以设置`max_replicas_in_group = 3`,并重启MetaServer使配置生效,然后设置[负载均衡](rebalance)状态为`lively`,让MetaServer控制删除无用的备份数据。
+
+# 垃圾文件夹管理
+
+ReplicaServer中的replica文件夹如果**不需要了**或者**出错了**,都会变成垃圾文件夹:不需要的文件夹会加`.gar`后缀;出错的文件夹会加`.err`后缀。这些文件夹不会被立即删除,因为考虑到某些极端情况下可能还有价值(譬如系统崩溃了需要找回数据)。
+
+有两个配置参数决定这些文件夹的真正删除时间:
+```
+[replication]
+    gc_disk_error_replica_interval_seconds = 604800
+    gc_disk_garbage_replica_interval_seconds = 86400
+```
+参数的意义是:对于这两种文件夹,会检查文件夹的最后修改时间(基本上就是文件夹重命名增加后缀的时间),只有最后修改时间与当前时间的差距超过了参数指定的interval时间,才会执行删除。
+
+如果想要节省SSD存储占用,希望这些垃圾文件夹及时删除,可以减小这两个参数的值(譬如只保留1小时或者更短),然后重启ReplicaServer使配置生效:
+```
+[replication]
+    gc_disk_error_replica_interval_seconds = 3600
+    gc_disk_garbage_replica_interval_seconds = 3600
+```
+
+从1.11.3版本开始,支持通过[远程命令](remote-commands)`useless-dir-reserve-seconds`来动态修改这两个参数,可不重启ReplicaServer进程,用于紧急清理垃圾文件夹,譬如将这两个参数修改为0:
+```
+>>> remote_command -t replica-server useless-dir-reserve-seconds 0
+```
+在确认清理完毕后,再还原为配置文件中的值:
+```
+>>> remote_command -t replica-server useless-dir-reserve-seconds DEFAULT
+```
+
+# Rocksdb-Checkpoint管理
+
+ReplicaServer底层使用RocksDB存储数据,会定期生成[checkpoint](https://github.com/facebook/rocksdb/wiki/Checkpoints)(有时也被称为snapshot)。checkpoint文件夹会放在replica的data文件夹下,并以生成时的last_durable_decree作为作为后缀。
+
+如下图,replica的data文件夹下包含当前使用的rdb文件夹和若干个checkpoint文件夹:
+[[https://github.com/XiaoMi/pegasus-common/blob/master/img/checkpoint_dirs.png|alt=octocat]]
+
+生成checkpoint的时候,sstable文件都是通过硬链接方式拷贝,不会真正copy数据。一个sstable文件可能被rdb持有,也可能被一个或者多个checkpoint持有。只要任意一个在持有,该文件的数据就存在于SSD盘上,占据存储空间。只有rdb和所有的checkpoint都不持有该文件,数据才会被删除。因为RocksDB在不断地进行compaction,所以checkpoint中持有的sstable可能已经过期了。如果checkpoint的保留时间太久,这些过期的sstable不能被及时删除,就会占用SSD存储空间。尤其对于写操作频繁的表,compaction进行得很频繁,单个sstable文件的生命周期很短,如果checkpoint保留得比较多的话,占用的存储空间很可能几倍于实际的数据大小。
+
+以下配置参数决定了checkpoint删除的策略:
+```
+[pegasus.server]
+    checkpoint_reserve_min_count = 3
+    checkpoint_reserve_time_seconds = 3600
+```
+其中:
+* checkpoint_reserve_min_count:表示checkpoint最少保留个数,只有个数超过这个限制的时候,最老的checkpoint才允许被删除。
+* checkpoint_reserve_time_seconds:表示checkpoint保留时间,只有checkpoint生成时间距离当前时间超过这个值时,才允许被删除。
+* 这两个参数所提供的限制条件同时满足时,checkpoint才会被删除。
+
+如果想要节省SSD存储占用,希望checkpoint文件夹删除得更及时,可以减小这两个参数,譬如:
+```
+[pegasus.server]
+    checkpoint_reserve_min_count = 2
+    checkpoint_reserve_time_seconds = 1200
+```
+注意:`checkpoint_reserve_time_seconds`不建议设得太小,考虑到对learn的影响,要尽量大于`replica_assign_delay_ms_for_dropouts`的值(该值默认10分钟),所以建议至少在10分钟以上。
+
+从1.11.3版本开始,支持通过[Table环境变量](table-env)动态修改某个表的这两个配置,可不重启ReplicaServer进程,用于紧急清理checkpoint文件夹,譬如:
+```
+>>> use table_name
+>>> set_app_envs rocksdb.checkpoint.reserve_min_count 1
+>>> set_app_envs rocksdb.checkpoint.reserve_time_seconds 600
+```
diff --git a/_administration/rolling-update.md b/_administration/rolling-update.md
new file mode 100644
index 0000000..e5e2078
--- /dev/null
+++ b/_administration/rolling-update.md
@@ -0,0 +1,111 @@
+---
+title: 集群升级
+layout: page
+show_sidebar: false
+menubar: administration_menu
+---
+
+# 功能目标
+
+当需要升级server版本或者修改config配置时,都需要对集群进行升级。对于分布式集群来说,常用的升级方法就是**滚动升级(Rolling-Update)**,即不停止服务,对一台一台server逐个进行升级。
+
+集群升级的重要目标在于**平稳**,即不停服,并且对可用性的影响降至最低。为了达到这个目标,我们先看看在升级过程中哪些地方可能会影响可用性:
+* replica server进程被kill后,该进程服务的replica无法提供服务:
+  * 对于primary replica:因为直接向客户端提供读写服务,所以进程kill后肯定会影响读写,需要等metaserver重新分派新的primary replica后才能恢复。meta server通过心跳感知replica server的存活状态,failure detection的时间延迟取决于配置参数`fd_grace_seconds`,通常配置为10秒,即最多需要经过**10秒**,meta server才能知道replica server挂了,然后重新分派新的primary replica。
+  * 对于secondary replica:由于不服务读,所以理论上对读无影响。但是会影响写,因为一致性协议要求一主两备都写成功,写操作才能提交。进程kill后,primary replica在执行写操作过程中会发现该secondary replica已失联,然后通知meta server将其踢掉,经过`reconfiguration`阶段后变成一主一备,继续提供写服务。在切换过程中尚未完成的写操作,即使有`reconciliation`阶段重新执行,但客户端那边大概率已经超时了,对可用性有一定影响。但是这个影响相对小些,因为`reconfiguration`的速度是比较快的,通常在**1秒**以内就能完成。
+* 升级meta server:升级meta server对可用度的影响几乎可以忽略不计,因为客户端会在本地缓存各partition的服务节点信息,通常情况下并不需要向meta server查询,因此meta server重启过程中的短暂失联对客户端基本没有影响。不过考虑到meta server需要与replica server维持心跳,所以要避免连续kill meta server进程,造成replica server心跳失联的风险。
+* 升级collector:升级collector对可用度没有影响。但是可用度统计是在collector上进行的,所以可能会对统计数据有轻微影响。
+
+因此,在集群升级过程要提高可用性,需要考虑如下几点:
+* 一次只能升级一个进程,且在该进程重启并完全恢复进入服务状态后,才能升级下一个进程。
+  * 因为如果升级一个进程后,集群没有恢复到完全健康状态,有的partition还只有一主一备,这时再kill一个replica server的话,很可能进入只有一主的状态,无法提供写服务。
+  * 另外,等待集群所有partition都恢复三备份后再继续升级下一个进程,也能有效降低数据丢失的风险。
+* 尽量主动迁移replica,而不是被动迁移replica,避免failure detection的时间延迟影响可用度。
+  * 被动迁移需要等待failure detection来感知节点失联,而主动迁移就是在kill掉replica server之前,先将这个进程服务的primary replica都迁移到其他节点上,这个`reconfiguration`过程是很快的,基本1秒以内完成。
+  * 更进一步,还可以在kill掉replica server之前,将这个进程服务的secondary replica手动降级,将`reconfiguration`过程由“写失败被动触发”变为“主动触发”,也能降低对可用度的影响。
+* 尽量减少进程重启时恢复过程的工作量,缩短进程重启时间。
+  * replica server在重启时需要replay log来恢复数据。如果直接kill掉,需要replay的数据量可能很大。但是如果在kill之前,先主动触发memtable的flush操作,让内存数据先落地,在重启时需要replay的数据量就会大大减少,重启时间会缩短很多,而整个集群升级所需的时间也能大大缩短。
+* 尽量减少不必要的节点间数据拷贝,避免因为增加CPU/网络/IO负载影响可用度。
+  * replica server挂掉后,部分partition进入一主一备的状态。如果meta server立即在其他replica server上补充备份,会带来大量的跨节点数据拷贝,增加CPU/网络/IO负载压力,影响集群稳定性。Pegasus解决这个问题的办法是,允许在一段时间内维持一主一备状态,给原来的replica server进行恢复的机会。如果长时间没有恢复,才会在新的replica server上补充备份。这样兼顾了数据的安全性和集群的稳定性。可以通过配置参数`replica_assign_delay_ms_for_dropouts`控制等待时间,默认为10分钟。
+
+# 升级流程
+
+## 高可用升级
+根据以上对高可用度的考虑,我们建议完善的升级流程如下:
+* 准备好新的Server程序包和配置文件
+* 使用shell工具将集群的meta level设置为steady,关闭[负载均衡功能](rebalance),避免不必要的replica迁移
+  ```
+  >>> set_meta_level steady
+  ```
+* 升级replica server进程,采用逐个升级的策略。升级单个replica server:
+  * 通过shell向meta server发送[远程命令](remote-commands#meta-server),禁掉`add_secondary`操作:
+    ```
+    >>> remote_command -t meta-server meta.lb.add_secondary_max_count_for_one_node 0
+    ```
+  * 通过migrate_node命令,将replica server上的primary replica都迁走:
+    ```bash
+    $ ./run.sh migrate_node -c $meta_list -n $node -t run
+    ```
+    通过shell的`nodes -d`命令查看该节点的服务replica情况,等待primary replica的个数变为0;如果长时间不变为0,重新执行上面命令。
+  * 通过downgrade_node命令,将replica server上的secondary replica都降级为INACTIVE:
+    ```bash
+    $ ./run.sh downgrade_node -c $meta_list -n $node -t run
+    ```
+    通过shell的`nodes -d`命令查看该节点的服务replica情况,等待secondary replica的个数变为0;如果长时间不变为0,重新执行上面命令。
+  * 通过shell向replica server发送远程命令,将所有replica都关闭,以触发flush操作,将数据都落地:
+    ```
+    >>> remote_command -l $node replica.kill_partition
+    ```
+    等待大约1分钟,让数据完成落地。
+  * 通过shell向meta server发送[远程命令](remote-commands#meta-server),开启`add_secondary`操作:
+    ```
+    >>> remote_command -t meta-server meta.lb.add_secondary_max_count_for_one_node 100
+    ```
+  * 替换程序包和配置文件
+  * 重启meta server进程
+  * 使用shell的`ls -d`命令查看集群状态,等待所有partition都完全恢复健康
+  * 继续升级下一个replica server
+* 升级meta server进程,采用逐个升级的策略。升级单个meta server:
+  * kill掉meta server进程
+  * 替换程序包和配置文件
+  * 重启meta server进程
+  * 等待30秒以上,保证meta server与replica server心跳的连续性
+  * 继续升级下一个meta server
+* 升级collector进程:
+  * kill掉collector进程
+  * 替换程序包和配置文件
+  * 重启collector进程
+
+## 简化版升级
+如果对可用性要求没那么高,升级流程可简化如下:
+* 准备好新的Server程序包和配置文件
+* 使用shell工具将集群的meta level设置为steady,关闭[负载均衡功能](rebalance),避免不必要的replica迁移
+  ```
+  >>> set_meta_level steady
+  ```
+* 升级replica server进程,采用逐个升级的策略。升级单个replica server:
+  * kill掉replica server进程
+  * 替换程序包和配置文件
+  * 重启replica server进程
+  * 使用shell的`ls -d`命令查看集群状态,等待所有partition都完全恢复健康
+  * 继续升级下一个replica server
+* 升级meta server进程,采用逐个升级的策略。升级单个meta server:
+  * kill掉meta server进程
+  * 替换程序包和配置文件
+  * 重启meta server进程
+  * 等待30秒以上,保证meta server与replica server心跳的连续性
+  * 继续升级下一个meta server
+* 升级collector进程:
+  * kill掉collector进程
+  * 替换程序包和配置文件
+  * 重启collector进程
+
+# 升级脚本
+
+我们提供了集群升级脚本[scripts/pegasus_rolling_update.sh](https://github.com/XiaoMi/pegasus/blob/master/scripts/pegasus_rolling_update.sh)。该脚本采用[高可用升级](#高可用升级)流程,用于小米内部的集群升级。
+
+不过这个脚本并不能直接使用,因为其依赖minos部署工具来完成以下事情:
+* 获取集群的进程列表
+* 自动部署更新程序包和配置文件,并重启进程
+
+你可以修改该脚本,针对你们自己的部署系统,修改以上通过minos完成的部分,使其可以正常工作。如需帮助,请联系我们。
diff --git a/_administration/table-env.md b/_administration/table-env.md
new file mode 100644
index 0000000..636c0e5
--- /dev/null
+++ b/_administration/table-env.md
@@ -0,0 +1,130 @@
+---
+title: Table环境变量
+layout: page
+show_sidebar: false
+menubar: administration_menu
+---
+
+# 功能目标
+
+为了对Table的一些行为进行控制,Pegasus提供了Table环境变量,又称之为`app envs`。
+
+Table环境变量以kv-map的形式存储在Table的元数据`app_info`中,并持久化到Zookeeper上。通过shell的`ls`命令查看表信息,最后一列`envs_count`记录Table环境变量的kv对个数:
+```
+>>> ls
+app_id    status              app_name            app_type            partition_count     replica_count       is_stateful         drop_expire_time    envs_count          
+1         AVAILABLE           temp                pegasus             8                   3                   true                -                   1     
+```
+
+如果要查看具体的Table环境变量,则需要使用[get_app_envs](#get_app_envs)命令。
+
+Table环境变量具有如下特性:
+* 作为Table的元数据持久化到Zookeeper上。
+* 可以通过命令动态修改,修改成功后会立即更新到Zookeeper上。
+* 通过meta server和replica server的定期同步消息`config_sync`同步给各个replica生效。由于是定期同步,所以环境变量更新后不会立即生效,而是有一个同步过程,这个过程的时间依赖于配置文件`config_sync_interval_ms`的值,默认是30秒。
+* 环境变量的key通过都是采用`.`分隔的字段的形式,方便组织。
+
+目前通过Table环境变量支持的功能包括:
+* [Manual-Compact功能](manual-compact)
+* [Usage-Scenario功能](usage-scenario)
+
+# 操作命令
+Pegasus的[Shell工具](/overview/shell#set_app_envs)中提供了操作Table环境变量的命令。这些命令执行前都需要先执行`use xxx`选择表。
+
+## get_app_envs
+获取环境变量列表,用法:`get_app_envs`
+
+示例:
+```
+>>> use temp
+OK
+>>> get_app_envs
+get app envs succeed, count = 1
+=================================
+rocksdb.usage_scenario = normal
+=================================
+```
+## set_app_envs
+设置环境变量,用法:`set_app_envs <key> <value> [key value...]`
+
+示例:
+```
+>>> use temp
+OK
+>>> set_app_envs rocksdb.usage_scenario bulk_load
+set app envs succeed
+>>> get_app_envs
+get app envs succeed, count = 1
+=================================
+rocksdb.usage_scenario = bulk_load
+=================================
+```
+
+## del_app_envs
+删除环境变量,用法:`del_app_envs <key> [key...]`
+
+示例:
+```
+>>> use temp
+OK
+>>> del_app_envs rocksdb.usage_scenario
+del app envs succeed
+=============================
+deleted keys:
+    rocksdb.usage_scenario
+=============================
+>>> get_app_envs
+get app envs succeed, count = 0
+```
+
+## clear_app_envs
+清理环境变量,或者叫批量删除环境变量,用法:`clear_app_envs <-a|--all> <-p|--prefix str>`
+
+两种方式:
+* 全部清理:使用`-a`选项。
+* 通过前缀清理:使用`-p`选项指定前缀,匹配时会先自动在前缀后面加上`.`,然后按照字符串前缀匹配。
+
+譬如:
+```
+>>> use temp
+OK
+>>> set_app_envs k.x v1 k.y v2
+set app envs succeed
+>>> get_app_envs
+get app envs succeed, count = 2
+=================================
+k.x = v1
+k.y = v2
+=================================
+>>> clear_app_envs -p k
+clear app envs succeed
+=============================
+deleted keys:
+    k.x
+    k.y
+=============================
+>>> get_app_envs
+get app envs succeed, count = 0
+```
+
+# 支持列表
+
+key名称 | value类型 | value约束 | value示例 | 功能说明 | 支持版本
+-- | -- | -- | -- | -- | --
+rocksdb.usage_scenario | string | normal \| prefer_write \| bulk_load | bulk_load | [Usage-Scenario](usage-scenario) | 1.8.1
+replica.deny_client_write | bool | true \| false | true | 拒绝写请求 | 1.11.2
+replica.write_throttling | string | 特定格式 | 1000\*delay\*100 | [流量控制#表级流控](throttling#表级流控) | 1.11.2
+replica.write_throttling_by_size | string | 特定格式 | 1000\*delay\*100 | [流量控制#表级流控](throttling#表级流控) | 1.12.0
+default_ttl | int | >=0 | 86400 | [表级TTL](/api/ttl#表级TTL) | 1.11.2
+manual_compact.disabled | bool | true \| false | true | [Manual-Compact](manual-compact) | 1.9.0
+manual_compact.max_concurrent_running_count | int | >=0 | 10 | [Manual-Compact](manual-compact) | 1.11.3
+manual_compact.once.trigger_time | int | Unix Timestamp in Seconds | 1547091115 | [Manual-Compact](manual-compact) | 1.8.1
+manual_compact.once.target_level | int | -1 \| >=1 | 2 | [Manual-Compact](manual-compact) | 1.8.1
+manual_compact.once.bottommost_level_compaction | string | force \| skip | force | [Manual-Compact](manual-compact) | 1.8.1
+manual_compact.periodic.trigger_time | string | 特定格式 | 3:00,5:00 | [Manual-Compact](manual-compact) | 1.8.1
+manual_compact.periodic.target_level | int | -1 \| >=1 | 2 | [Manual-Compact](manual-compact) | 1.8.1
+manual_compact.periodic.bottommost_level_compaction | string | force \| skip | force | [Manual-Compact](manual-compact) | 1.8.1
+rocksdb.checkpoint.reserve_min_count | int | >=1 | 2 | [Rocksdb-Checkpoint管理](resource-management#rocksdb-checkpoint管理) | 1.11.3
+rocksdb.checkpoint.reserve_time_seconds | int | >=0 | 600 | [Rocksdb-Checkpoint管理](resource-management#rocksdb-checkpoint管理) | 1.11.3
+business.info | string | 特定格式(使用utf-8编码) | depart=云平台部-存储平台,user=qinzuoyan&wutao1 | 记录表的业务归属信息,可用于生成账单 | -
+replica.slow_query_threshold | int | >=20 | 30 | 慢查询阈值 | 1.12.0
diff --git a/_administration/table-migration.md b/_administration/table-migration.md
new file mode 100644
index 0000000..f31290e
--- /dev/null
+++ b/_administration/table-migration.md
@@ -0,0 +1,45 @@
+---
+title: Table迁移
+layout: page
+show_sidebar: false
+menubar: administration_menu
+---
+
+这里说的Table迁移是指将某个Pegasus集群的一个表的数据迁移到另一个Pegasus集群中。
+
+目前提供了两种办法:使用Shell工具的copy_data命令;通过冷备份恢复。
+
+# copy_data迁移
+
+Shell工具的[copy_data命令](/overview/shell#copy_data)的原理:通过scan接口从源集群的表中逐条读入数据,然后通过set接口将数据逐条写入到目标集群的表中。如果set的数据在目标集群的表中已经存在,会直接覆盖。
+
+copy_data命令:
+```
+    copy_data              <-c|--target_cluster_name str> <-a|--target_app_name str>
+                           [-s|--max_split_count num] [-b|--max_batch_count num]
+                           [-t|--timeout_ms num]
+```
+
+假设源集群为ClusterA,目标集群为ClusterB,需要迁移的表为TableA。迁移步骤如下:
+* 在目标集群上建表。由于copy_data命令并不会自动在目标集群上创建表,所以需要自己先建表。相对源表,新表的表名可以不同,partition count也可以不同。假设在目标集群上新建的表名为TableB。
+* 在Shell工具的配置文件中添加目标集群的配置。因为copy_data命令需要通过```-c```参数指定目标集群,所以需要配置目标集群的MetaServer地址列表。在执行Shell所在文件夹,修改配置文件[src/shell/config.ini](https://github.com/XiaoMi/pegasus/blob/master/src/shell/config.ini),在文件最后添加如下几行(将ClusterB替换为你自己的集群名):
+```
+[pegasus.clusters]
+    ClusterB = {ClusterB的MetaServer地址}
+```
+* 在Shell中执行命令:
+```
+>>> use TableA
+>>> copy_data -c ClusterB -a TableB -t 10000
+```
+* 如果以上步骤都没有问题,copy操作应当就开始执行了,每隔1秒会打印进度。通常来说,copy速度应当在10万/秒以上。copy过程中如果出现问题终止了,就重新执行命令。
+
+# 冷备份迁移
+
+所谓冷备份迁移,就是利用Pegasus的[冷备份功能](/administration/cold-backup),先将数据备份到HDFS或者其他介质上,然后恢复到新的表中。
+
+冷备份迁移的好处:
+* 速度更快:因为冷备份是拷贝文件,相对copy_data的逐条拷贝,速度要快很多。
+* 错误容忍度高:冷备份功能有很多容错逻辑,避免因为网络抖动等问题带来的影响。如果用copy_data,中途出错就需要从头再来。
+* 多次迁移更友好:如果要从一个表拷贝到多个地方,只需要备份一次,然后执行多次恢复。
+
diff --git a/_administration/table-soft-delete.md b/_administration/table-soft-delete.md
new file mode 100644
index 0000000..32eb4ea
--- /dev/null
+++ b/_administration/table-soft-delete.md
@@ -0,0 +1,118 @@
+---
+title: Table软删除
+layout: page
+show_sidebar: false
+menubar: administration_menu
+---
+
+# 功能目标
+软删除主要用于防范数据被错误的永久删除。具体而言,软删除应该具有如下几个功能:
+* 当用户删除某个指定的表后,该表变得不可访问。
+* 被删除的表其数据不会立即物理清除,而是会保留一段时间。
+* 被删除的表过期(超过保留时间)后,数据会从物理上做彻底的删除,但是数据真正删除的时间还取决于其他因素,参见[垃圾文件夹管理](resource-management#垃圾文件夹管理)。
+* 未过期的表可以通过一定的方式恢复,恢复成功后又可以像正常的表一样进行读写访问。
+
+# 操作命令
+shell端提供了`drop`和`recall`命令支持软删除。
+
+## 使用drop命令删除表
+使用方式:
+```
+drop                   <app_name> [-r|--reserve_seconds num]
+```
+
+drop命令用于删除一个表,通过`-r`选项指定数据的保留时间,从删除时间开始计算,单位为秒。如果不指定,则使用配置文件`hold_seconds_for_dropped_app`指定的值,默认为7天。
+
+表删除成功后:
+* 再次访问该表,将会返回`ERR_OBJECT_NOT_FOUND`,提示表不存在。
+* 通过shell的`ls`命令看不到被删除的表。
+* 通过shell的`ls -a`命令可以看到被删除的表。
+* 被删除的表的ID不会被重用,以防止恢复时出现ID冲突。
+* 表删除后,可以新建同名表,当客户端访问这个表名时,访问的是新表。
+
+## 过期表数据的物理删除
+过期表的数据在各个replica server上也未必能立即被物理删除,因为:
+* 表的保留时间过期后,只有通过shell设置了`set_meta_level lively`,使meta server进入[负载均衡模式](rebalance#控制集群的负载均衡),meta server才会通过`config_sync`RPC通知replica server删除相关的replica。而replica server在收到meta server的通知后,就会将需删除的replica文件夹通过添加`.gar`后缀进行重命名,表示这是可以被删除的垃圾数据。但此时数据仍未被真正物理删除。
+* replica server会定期(配置文件`disk_stat_interval_seconds`)扫描各个数据文件夹(配置文件`data_dirs`),统计文件夹的使用情况。对于标记为`.gar`后缀的replica文件夹,获取其最后修改时间,并和当前时间进行比较,只有当两者时间差超过了阈值(配置文件`gc_disk_garbage_replica_interval_seconds`)后,在会将文件夹删除掉。此时数据才算被真正物理删除。
+
+所以综上所述,能够影响表被删除后进行物理删除的时间点的配置项包括:
+* `[meta_server] hold_seconds_for_dropped_app`:当drop表没有指定`-r`选项时,决定该表的保留时间。
+* `[replication] disk_stat_interval_seconds`:replica server定期扫描各个数据文件夹的时间间隔。
+* `[replication] gc_disk_garbage_replica_interval_seconds`:垃圾replica文件夹的最后修改时间距离当前时间超过这个阈值,文件夹才会被删除。
+
+如果遇到需要紧急删除数据以释放磁盘空间,但是又不方便重启replica server更新配置的情况,可以根据表ID进行手工暴力删除,但是千万注意:
+* 不到万不得已,不要进行手工暴力删表,避免误操作。
+* 坚决只能删除**过期表**的数据。
+* 不要误删其他表的数据。
+
+## 使用recall命令恢复表
+使用方式
+```
+recall                 <app_id> [new_app_name]
+```
+
+只要表的保留时间还没有过期,就能执行恢复:
+* 恢复时需指定表ID。
+* 可以指定新表名,如果不指定新表名,则会使用原表名。
+* 如果原表名已存在(删表后新建了同名表),则必须指定另外一个不同的新表名,否则会失败。
+* 恢复过程可能需要花费一段时间。
+
+## 示例
+以下是使用示例:删除mytable表,然后恢复成新表名mytable2
+```
+>>> ls
+app_id    status              app_name            app_type            partition_count     replica_count       is_stateful         drop_expire_time    envs_count          
+1         AVAILABLE           temp                pegasus             8                   3                   true                -                   0                   
+2         AVAILABLE           mytable             pegasus             8                   3                   true                -                   0                   
+
+list apps succeed
+
+>>> drop mytable
+reserve_seconds = 0
+drop app mytable succeed
+
+>>> ls
+app_id    status              app_name            app_type            partition_count     replica_count       is_stateful         drop_expire_time    envs_count          
+1         AVAILABLE           temp                pegasus             8                   3                   true                -                   0                   
+
+list apps succeed
+
+>>> ls -a
+app_id    status              app_name            app_type            partition_count     replica_count       is_stateful         drop_expire_time    envs_count          
+1         AVAILABLE           temp                pegasus             8                   3                   true                -                   0                   
+2         DROPPED             mytable             pegasus             8                   3                   true                2018-07-28 19:07:21 0                   
+
+list apps succeed
+
+>>> recall 2 mytable2
+recall app ok, id(2), name(mytable2), partition_count(8), wait it ready
+mytable2 not ready yet, still waiting... (0/8)
+mytable2 not ready yet, still waiting... (0/8)
+mytable2 not ready yet, still waiting... (0/8)
+mytable2 not ready yet, still waiting... (0/8)
+mytable2 not ready yet, still waiting... (0/8)
+mytable2 not ready yet, still waiting... (0/8)
+mytable2 not ready yet, still waiting... (0/8)
+mytable2 not ready yet, still waiting... (0/8)
+mytable2 not ready yet, still waiting... (7/8)
+mytable2 is ready now: (8/8)
+recall app 2 succeed
+
+>>> ls
+app_id    status              app_name            app_type            partition_count     replica_count       is_stateful         drop_expire_time    envs_count          
+1         AVAILABLE           temp                pegasus             8                   3                   true                -                   0                   
+2         AVAILABLE           mytable2            pegasus             8                   3                   true                -                   0                   
+
+list apps succeed
+```
+
+# 设计与实现
+关键点:
+* 表的生命周期定义要清晰:对于正在删除/召回的表,其他的create/recall/drop的操作要禁止。
+* 表的过期时间要在各个meta server之间达成一致,这需要各个meta server做时钟同步。
+* 如果表格被多次的删除和召回,当这些消息以乱序的方式送达到replica server时,一定要保证replica server能处理这些情况。最好能把drop操作映射成replica configuration元数据的变更。
+
+实现要点简述:
+* meta server对删除动作的响应:当收到客户端响应时,meta server需要把信息更新到zookeeper: (1) app的状态改为dropped, 记录过期时间 (2) 升级configuration的状态,并记录到zookeeper。
+* replica server的replica生命周期变化:replica server通过定期和meta server交换心跳获取自己所服务的replica,如果发现本地的replica已经在远端不存在,则把本地的replica清除掉。
+* 过期数据的删除:replica server和meta交换心跳时,汇报自己所存储的replica。当meta判定一个replica已经无效时,命令replica将其删掉。
diff --git a/_administration/throttling.md b/_administration/throttling.md
new file mode 100755
index 0000000..a98bbdb
--- /dev/null
+++ b/_administration/throttling.md
@@ -0,0 +1,71 @@
+---
+title: 流量控制
+layout: page
+show_sidebar: false
+menubar: administration_menu
+---
+
+# 关于流控
+流量控制是指通过一些手段来控制读写请求的速度。
+
+为什么要做流量控制?主要是减小集群压力,提升稳定性。如果集群的写流量太大,就会消耗大量的系统资源(CPU、IO等),从而影响读请求的延迟。有些业务对读性能要求比较高,如果对写流量不加控制,就无法保证服务质量。
+
+从流控的作用位置来看,可分为:
+* 客户端流控:从源头掐住流量。优点是避免不必要的网络传输;缺点是需要在客户端增加逻辑,且因为无法掌控用户的使用方式造成流控难以准确。
+* 服务端流控:在ReplicaServer节点上进行流控。优点是对客户端透明,流控集中容易掌控;缺点是只能通过增大延迟或者拒绝请求的方式来流控,不够直接,另外可能无法避免不必要的网络传输。
+
+从流控的粒度来看,可分为:
+* 表级流控:只控制单个表的流控,粒度较细。
+* 节点级流控:针对ReplicaServer节点进行的流控,不区分具体的表。
+
+# 客户端流控
+
+目前Java客户端提供了流控工具,参见[Java客户端文档#流量控制](/clients/java-client#流量控制)。
+
+# 服务端流控
+
+# 表级流控
+
+从[v1.11.2版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.11.2)开始,Pegasus支持Server端表级流控,目前只针对写操作。另外,从[v1.12.0版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.12.0)开始增加了基于吞吐量的限流。
+
+实现原理:
+* 用户可以在[Table环境变量](table-env)中设置`replica.write_throttling`和`replica.write_throttling_by_size`环境变量。其中`replica.write_throttling`是基于qps的限流,`replica.write_throttling_by_size`是基于吞吐量的限流。
+* MetaServer将环境变量异步地通知到各个ReplicaServer,使该表的每个replica都获取到该环境变量,这个过程大约有几秒到几十秒不等的延迟,但是不会超过一分钟。
+* replica获得环境变量后,解析获得write_throttling流控配置,并立即开始生效。
+
+write_throttling流控目前支持两种操作类型:
+* delay:server端收到请求后不立即处理,而是推迟一段时间后再处理,这样使client端的写延迟增大,间接达到流控的目的。
+* reject:server端收到请求后不进行处理,而是返回ERR_BUSY的错误码。可以推迟一段时间再返回错误码,以增大客户端收到错误的延迟,避免客户端立即重试,造成频繁的不必要重试。
+
+环境变量`replica.write_throttling`/`replica.write_throttling_by_size`的value格式:
+```
+{delay_qps_threshold}*delay*{delay_ms},{reject_qps_threshold}*reject*{delay_ms_before_reject}
+```
+注:
+* delay_qps_threshold:触发delay操作的QPS阈值。如果1秒内处理的写请求数超过这个值,则这1秒内后面的请求都执行delay操作。
+* delay_ms:delay操作的推迟时间,单位毫秒,需满足>=0。
+* reject_qps_threshold:触发reject操作的QPS阈值。如果1秒内处理的写请求数超过这个值,则这1秒内后面的请求都执行reject操作。
+* delay_ms_before_reject:reject操作返回错误码之前的推迟时间,单位毫秒,需满足>=0。
+* delay和reject配置可以同时提供两个,也可以只提供其中一个。
+* 如果delay和reject配置同时提供,且QPS同时达到了delay和reject的阈值,那么会执行reject操作。
+
+示例:
+```bash
+$ ./run.sh shell
+>>> use temp
+OK
+>>> set_app_envs replica.write_throttling 1000*delay*100,2000*reject*200
+set app envs succeed
+>>> get_app_envs
+get app envs succeed, count = 1
+=================================
+replica.write_throttling = 1000*delay*100,2000*reject*200
+=================================
+>>> 
+```
+
+上面我们设置了`temp`表的write_throttling配置为`1000*delay*100,2000*reject*200`,这个配置的意思是:当QPS超过1000时,就开始执行delay操作;当QPS超过2000时,就开始执行reject操作。
+
+## 节点级流控
+
+待补充。
diff --git a/_administration/usage-scenario.md b/_administration/usage-scenario.md
new file mode 100644
index 0000000..e880c7e
--- /dev/null
+++ b/_administration/usage-scenario.md
@@ -0,0 +1,88 @@
+---
+title: Usage Scenario功能
+layout: page
+menubar: administration_menu
+---
+
+注:Usage Scenario功能从[v1.8.1版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.8.1)开始支持。
+
+# 原理
+Usage Scenario功能,是指对于Pegasus的表,可以指定其使用场景。针对不同的场景,通过优化底层RocksDB的配置,以获得更好的读写性能。
+
+我们知道,RocksDB的LSM-Tree设计具有明显的写放大效应。数据先是写入到memtable中,当memtable满了,就会flush到sstable中,并放在level-0层。当level-0层的文件个数达到预设的限制时,就会触发compaction操作,将level-0层的所有文件merge到level-1层。同理,当level-1层的数据量达到预设的限制时,也会触发level-1层的compaction,将挑选一些文件merge到level-2层。这样,数据会从低层往高层逐层上移。
+
+RocksDB是一个可配置性很强的引擎,上面的各种行为都可以通过配置参数来调节,并且有很多参数都是可以动态修改的。这里给出几个比较关键的配置参数:
+* write_buffer_size:memtable的大小限制,配置得越小,写入同样数据量产生的sstable文件数就越多。
+* level0_file_num_compaction_trigger:level-0层的文件个数限制,当达到这个限制时,就会触发compaction。
+* level0_slowdown_writes_trigger:当level-0层的文件个数超过这个值的时候,就会触发slowdown-writes行为,通过主动提升写操作延迟,来降低写入速度。
+* level0_stop_writes_trigger:当level-0层的文件个数超过这个值的时候,就会触发stop-writes行为,拒绝写入新的数据。
+* max_bytes_for_level_base:level-1层的数据量限制,当达到这个限制时,就会挑选一些文件merge到level-2层。
+* max_bytes_for_level_multiplier:数据量随层数的增长因子,譬如如果设为10,表示level-2的数据量限制就是level-1的10倍,level-3的数据量限制也是level-2的10倍。
+
+Pegasus在提供读写服务的时候,需要考虑这些因素:
+* 当写数据的速度较大时,memtable很快就会写满,就会不断flush memtable产生新的sstable文件;
+* 新sstable文件的产生就会触发compaction,并从低层向高层逐层蔓延;
+* compaction需要耗费大量的CPU和IO,造成**机器的CPU和IO负载居高不下**,影响读写操作的性能;
+* 如果compaction速度赶不上数据写入的速度,level-0的文件数就会越堆越多,最终达到`level0_slowdown_writes_trigger`的限制,**使写操作的延迟陡增**;甚至进一步达到`level0_stop_writes_trigger`的限制,**使写操作失败**,影响系统的稳定性和服务的可用性。
+* **读写需求很难同时得到满足**,二者需要权衡。compaction进行得越快,level-0的文件数维持得越少,读的时候需要读取的文件个数就越少,读性能就越高;但是compaction越快,带来的写放大效应就越大,CPU和IO的负载就越重,也会影响读写性能。
+
+所幸的是,RocksDB针对这个问题也给出了一些解决方案,譬如在[RocksDB-FAQ](https://github.com/facebook/rocksdb/wiki/RocksDB-FAQ)中给出的方案:
+```
+Q: What's the fastest way to load data into RocksDB?
+
+A: A fast way to direct insert data to the DB:
+   1. using single writer thread and insert in sorted order
+   2. batch hundreds of keys into one write batch
+   3. use vector memtable
+   4. make sure options.max_background_flushes is at least 4
+   5. before inserting the data, disable automatic compaction, set options.level0_file_num_compaction_trigger, 
+      options.level0_slowdown_writes_trigger and options.level0_stop_writes_trigger to very large. After inserting all the 
+      data, issue a manual compaction.
+ 
+3-5 will be automatically done if you call Options::PrepareForBulkLoad() to your option
+```
+
+而我们的思路正是:通过针对不同业务场景,设置不同的RocksDB参数,调节RocksDB的行为,以提供更好的读写性能。具体来说:
+* 通过[Table环境变量](table-env)设置`rocksdb.usage_scenario`来指定当前的业务场景。
+* Replica在检测到该环境变量发生变化时,就会根据业务场景,动态修改RocksDB的配置参数。具体设置了哪些参数,请参见[src/server/pegasus_server_impl.cpp](https://github.com/XiaoMi/pegasus/blob/master/src/server/pegasus_server_impl.cpp)中的`set_usage_scenario()`方法。
+
+# 支持场景
+
+目前支持三种场景:
+* normal:正常场景,读写兼顾。这也是表的默认场景,该场景不会对写进行特别的优化,适合大部分读多写少或者读写均衡的应用。
+* prefer_write:写较多的场景。主要是增大`write_buffer_size`以降低sstable的产生速度。
+* bulk_load:灌数据场景。应用上面RocksDB-FAQ中提到的优化,避免compaction过程。因为Bulk load模式停止compaction,所以写入的数据都会堆放在level-0层,对读不友好。因此,Bulk load模式通常与[Manual Compact功能](manual-compact)配合使用,在数据加载完成后进行一次Manual Compact,以去除垃圾数据、提升读性能(参见后面的[应用示例](#应用示例))。另外,当不需要加载数据时,应当恢复为Normal模式。典型的灌数据流程:
+  * 设置表的Usage Scenario模式为bulk_load
+  * 灌数据:在bulk load模式下灌数据的QPS会更高,流量更稳定
+  * 执行Manual Compact:该过程消耗大量的CPU和IO,可能对集群读写性能有影响
+  * 恢复表的Usage Scenario模式为normal
+
+# 如何设置
+## 通过shell设置
+通过shell的[set_app_envs命令](/overview/shell#set_app_envs)来设置,譬如设置temp表为bulk_load模式:
+```
+>>> use temp
+>>> set_app_envs rocksdb.usage_scenario bulk_load
+```
+
+Table环境变量不会立即生效,大约需要等几十秒后才能在所有replica上生效。
+
+## 通过脚本设置
+我们提供了一个脚本工具[scripts/pegasus_set_usage_scenario.sh](https://github.com/XiaoMi/pegasus/blob/master/scripts/pegasus_set_usage_scenario.sh)来方便地设置,用法:
+```
+$ ./scripts/pegasus_set_usage_scenario.sh   
+This tool is for set usage scenario of specified table(app).
+USAGE: ./scripts/pegasus_set_usage_scenario.sh <cluster-meta-list> <app-name> <normal|prefer_write|bulk_load>
+```
+
+该工具会调用shell命令设置Table环境变量,然后还会检测是否在所有replica上都已经生效,只有所有都生效了才算执行完成。
+
+## 应用示例
+
+bulk_load模式通常用于灌数据,但是在灌数据过程中因为消耗大量的CPU和IO,对读性能会产生较大影响,造成读延迟陡增、超时率升高等。如果业务对读性能要求比较苛刻,可以考虑**读写分离的双集群方案**。
+
+假设两个集群分别为A和B,最初线上流量访问A集群,灌数据流程:
+* 第一步:​设置B模式为bulk_load -> 灌数据至B -> Manual Compact B -> 设置B模式为normal​​ -> 切线上流量至B
+* 第二步:设置A模式为bulk_load -> 灌数据至A -> Manual Compact A -> 设置A模式为normal -> 切线上流量至A
+
+关于如何Manual Compact,请参考[Manual-Compact功能](manual-compact)。
diff --git a/_administration/whitelist.md b/_administration/whitelist.md
new file mode 100644
index 0000000..c23a93d
--- /dev/null
+++ b/_administration/whitelist.md
@@ -0,0 +1,53 @@
+---
+title: 白名单
+layout: page
+menubar: administration_menu
+---
+
+## 介绍
+
+Pegasus的白名单功能用来防止非预期的replica server加入集群。例如:
+1. 已stop的replica server因外因重启后,加入集群;
+2. replica server配置的meta server地址有误,加入别的集群。
+
+白名单功能禁用时,任何replica server只需要配置meta server地址,就可以被加入该集群。
+
+白名单功能开启时,meta server只允许白名单中的replica server(ip:port)加入集群。
+
+## 怎样开启白名单
+
+### 配置
+
+配置 [meta_server].enable_white_list 与 [meta_server].replica_white_list,多个ip用','分隔:
+```
+[meta_server]
+  enable_white_list = true
+  replica_white_list = 127.0.0.1:34801,127.0.0.2:34801
+```
+
+### 查询
+
+白名单在meta server运行过程中不允许更改,只能通过修改配置并重启来修改。但是可以通过shell的remote_command来查询。
+
+示例:
+```
+>>> remote_command -t meta-server meta.fd.allow_list
+```
+
+## 开启白名单的扩缩容
+
+在白名单开启后,扩缩容操作需要考虑白名单的影响。
+
+### 扩容
+
+由于扩容需replica server先与meta server通信,如果此时白名单未更新,会导致meta server拒绝这个新replica server加入集群。
+
+所以,开启白名单的集群扩容步骤,需要在普通[扩容流程](membership-change#扩容流程)前,进行一下步骤:
+1. 修改meta server白名单配置,加入新replica servers
+2. 重启meta server
+
+### 缩容
+
+[缩容流程](membership-change#缩容流程)中,白名单不会造成任何影响。白名单的更新也可以在缩容完成之后任意时刻进行。
+
+但为了安全,建议及时更新白名单。只需在缩容流程的最后一步“重启meta server”前,修改meta server的白名单配置。
\ No newline at end of file
diff --git a/_administration/zk-migration.md b/_administration/zk-migration.md
new file mode 100644
index 0000000..47b2b02
--- /dev/null
+++ b/_administration/zk-migration.md
@@ -0,0 +1,112 @@
+---
+title: Zookeeper迁移
+layout: page
+menubar: administration_menu
+---
+
+由于Pegasus的meta server依赖Zookeeper存储元数据和抢主,所以Zookeeper服务的不稳定会造成Pegasus服务不稳定,有时就需要迁移到其他更稳定或者空闲的Zookeeper上。
+
+Zookeeper迁移提供了两种办法:通过元数据恢复迁移;通过zkcopy工具迁移。
+
+# 通过元数据恢复迁移
+
+Pegasus提供了[元数据恢复](meta-recovery)功能,这个功能也可用于Zookeeper迁移。基本思路就是配置新的Zookeeper后,通过recover命令发起元数据恢复,这样元数据就写入新的Zookeeper上。
+
+1. 备份app列表
+
+   使用shell的`ls`命令:
+   ```
+   >>> ls -o apps.list
+   ```
+
+2. 备份node列表
+
+   使用shell的`nodes`命令:
+   ```
+   >>> nodes -d -o nodes.list
+   ```
+
+   生成元数据恢复所需的`recover_node_list`文件:
+   ```bash
+   grep ALIVE nodes.list | awk '{print $1}' >recover_node_list
+   ```
+
+3. 停掉所有meta
+
+   停掉所有meta server,并等待30秒以上,以保证所有replica server因为心跳超时进入INACTIVE状态。
+
+4. 修改meta配置文件
+
+   修改meta server的配置文件,如下:
+   ```
+   [meta_server]
+     recover_from_replica_server = true
+   [zookeeper]
+     hosts_list = {new zookeeper host list}
+   ```
+   * 将`recover_from_replica_server`设置为true
+   * 将zookeeper的`hosts_lists`改为新的服务地址
+
+5. 启动一个meta
+
+   启动其中一个meta server。
+
+6. 通过shell发送recover命令
+
+   ```
+   >>> recover -f recover_node_list
+   ```
+   检查恢复结果,如果出错请参考[常见问题整理](meta-recovery#常见问题整理)排查问题。
+
+7. 修改配置文件并重启meta
+
+   恢复成功后,需要修改配置文件,重新改回非recovery模式:
+   ```
+   [meta_server]
+     recover_from_replica_server = false
+   ```
+
+   重新启动所有的meta server,集群进入正常状态。
+
+注:[scripts/pegasus_migrate_zookeeper.sh](https://github.com/XiaoMi/pegasus/blob/master/scripts/pegasus_migrate_zookeeper.sh)是我们在内部使用的迁移Zookeeper的脚本,虽然因为服务启停功能的兼容性不能直接使用,但是可以参考其中的流程,或者进行改造。
+
+# 通过zkcopy工具迁移
+
+基本思路就是使用zkcopy工具将原始Zookeeper数据拷贝到目标Zookeeper上,修改meta server配置文件并重启。
+
+1. 停掉所有的备meta server
+
+   为了防止重启主meta server时有其他的备meta server抢到锁,造成状态混乱,在整个迁移过程中只保留一个主meta server,其他的备meta server全部停掉。
+
+2. 修改主meta server状态为blind
+
+   将主meta server的level设置为blind(关于meta server的level介绍请参见[负载均衡](rebalance#控制集群的负载均衡)),以禁止任何对Zookeeper数据的更新操作,防止在copy过程中出现不一致:
+   ```
+   >>> set_meta_level blind
+   ```
+
+3. 使用zkcopy工具拷贝Zookeeper数据
+
+   通过shell的`cluster_info`命令获取Zookeeper元数据节点路径`zookeeper_root`,然后使用zkcopy工具将该节点的数据完全拷贝到新集群的节点上,注意需要递归拷贝。
+
+4. 修改配置文件
+
+   修改meta server的配置文件,将zookeeper的`hosts_lists`改为新的服务地址:
+   ```
+   [meta_server]
+     hosts_list = {new zookeeper host list}
+   ```
+
+5. 重启主meta server
+
+   重新启动主meta server,通过shell工具检查集群进入正常状态。
+
+6. 启动所有备meta server
+
+   启动所有备meta server,集群进入正常状态。
+
+7. 清理旧Zookeeper上的数据
+
+   使用zookeepercli工具的rmr命令清理旧zookeeper上的数据。
+
+注:上面使用到的`zkcopy`和`zookeepercli`工具以后会提供出来。
diff --git a/_api/geo.md b/_api/geo.md
new file mode 100755
index 0000000..8ec2822
--- /dev/null
+++ b/_api/geo.md
@@ -0,0 +1,320 @@
+---
+title: GEO支持
+layout: page
+menubar: api_menu
+---
+
+# Pegasus GEO支持
+
+## 背景
+
+业务数据跟Pegasus的普通数据类似,由hashkey、sortkey、value组成。但业务数据隐含有地理信息,比如value中包含有经纬度(latitude,longitude),需要提供API进行GEO特性的支持,比如给定一个中心点坐标和一个半径,查找这个范围内的所有数据;给定两条数据的hashkey和sortkey,求这两条数据地理上的距离。
+
+pegasus的GEO(Geographic)支持使用了[S2](https://github.com/google/s2geometry)库, 主要利用其中将二维地理坐标(经纬度)与一维编码的相互转换、基于圆形的范围查询、Hilbert曲线规则等特性。在Pegasus中如何充分利用S2的这些特性,并结合Pegasus的数据分布、数据存储特性,是本文的阐述重点。
+
+关于S2的实现原理细节请参考[S2官网](http://s2geometry.io/)
+
+## 坐标转换
+
+在S2中,可以把二维经纬度编码成一维编码,一维编码由两部分组成:立方体面、平面坐标编码,比如:
+
+经纬度(116.334441,40.030202)的编码是:`1/223320022232200331010110113301`(32位),在S2中称为cellid。
+
+其中,首位的`1`代表地球立方体投影的面索引,索引范围是0~5,如下图所示:
+
+![geo_faces.png](/assets/images/geo_faces.png){:class="img-responsive"}
+
+`/`是分隔符
+
+`223320022232200331010110113301`(30位)是经纬度坐标经过一系列转换得到的编码,具体转换过程这里不详细描述。需要指出的是,这是一个名为Hilbert曲线编码,它最大的特点是具有稳定性、连续性。
+
+![hilbert.png](/assets/images/hilbert.png){:class="img-responsive"}
+
+编码由前往后按层进行,完整编码是前缀编码的子区域,每个父区域由4个子区域组成,比如`00`,`01`,`02`,`03`是`0`的子区域,且前者的区域范围的并集就是后者的区域范围。最多有30层,每层都有相应的cellid集合,高层cell是底层cell的父区域,高层cellid是底层cellid的前缀。
+
+编码可以看作是一个4进制的数值编码,同时**在数值上连续的值,在地理位置上也是连续的**。
+
+## 编码精度
+
+S2中的Hilbert曲线编码由30位组成,每一位代表一层划分。下表是各层单个cell的面积和cell个数。
+
+| **level** | **min area** | **max area** | **average area** | **units** | **Number of cells** |
+| --------- | ------------ | ------------ | ---------------- | --------- | ------------------- |
+| 00        | 85011012.19  | 85011012.19  | 85011012.19      | km^2    | 6                   |
+| 01        | 21252753.05  | 21252753.05  | 21252753.05      | km^2    | 24                  |
+| 02        | 4919708.23   | 6026521.16   | 5313188.26       | km^2    | 96                  |
+| 03        | 1055377.48   | 1646455.50   | 1328297.07       | km^2    | 384                 |
+| 04        | 231564.06    | 413918.15    | 332074.27        | km^2    | 1536                |
+| 05        | 53798.67     | 104297.91    | 83018.57         | km^2    | 6K                  |
+| 06        | 12948.81     | 26113.30     | 20754.64         | km^2    | 24K                 |
+| 07        | 3175.44      | 6529.09      | 5188.66          | km^2    | 98K                 |
+| 08        | 786.20       | 1632.45      | 1297.17          | km^2    | 393K                |
+| 09        | 195.59       | 408.12       | 324.29           | km^2    | 1573K               |
+| 10        | 48.78        | 102.03       | 81.07            | km^2    | 6M                  |
+| 11        | 12.18        | 25.51        | 20.27            | km^2    | 25M                 |
+| 12        | 3.04         | 6.38         | 5.07             | km^2    | 100M                |
+| 13        | 0.76         | 1.59         | 1.27             | km^2    | 402M                |
+| 14        | 0.19         | 0.40         | 0.32             | km^2    | 1610M               |
+| 15        | 47520.30     | 99638.93     | 79172.67         | m^2     | 6B                  |
+| 16        | 11880.08     | 24909.73     | 19793.17         | m^2     | 25B                 |
+| 17        | 2970.02      | 6227.43      | 4948.29          | m^2     | 103B                |
+| 18        | 742.50       | 1556.86      | 1237.07          | m^2     | 412B                |
+| 19        | 185.63       | 389.21       | 309.27           | m^2     | 1649B               |
+| 20        | 46.41        | 97.30        | 77.32            | m^2     | 7T                  |
+| 21        | 11.60        | 24.33        | 19.33            | m^2     | 26T                 |
+| 22        | 2.90         | 6.08         | 4.83             | m^2     | 105T                |
+| 23        | 0.73         | 1.52         | 1.21             | m^2     | 422T                |
+| 24        | 0.18         | 0.38         | 0.30             | m^2     | 1689T               |
+| 25        | 453.19       | 950.23       | 755.05           | cm^2    | 7e15                |
+| 26        | 113.30       | 237.56       | 188.76           | cm^2    | 27e15               |
+| 27        | 28.32        | 59.39        | 47.19            | cm^2    | 108e15              |
+| 28        | 7.08         | 14.85        | 11.80            | cm^2    | 432e15              |
+| 29        | 1.77         | 3.71         | 2.95             | cm^2    | 1729e15             |
+| 30        | 0.44         | 0.93         | 0.74             | cm^2    | 7e18                |
+
+
+
+## 数据存储
+
+在Pegasus中,数据存储的key是hashkey+sortkey: hashkey用于数据partition,同一hashkey的数据存储在同一replica server下的一块或多块(由rocksdb实际存储的状态决定:数据随机写入后,同一hashkey下连续的sortkey空间可能分布在多个不连续的sst文件中,进行full compact后,会分布在连续sst的内)连续区域; sortkey用于在这块(或多块)区域中做数据排序的依据。
+
+经纬度经过坐标转换得到一维编码(字符串)后,就可以把这个一维编码作为key存储起来做**GEO索引数据**了,这里需要将这个一维编码拆分成hashkey和sortkey两部分,可以根据实际的业务场景采取不同的划分策略。
+
+GEO索引数据独立于原始数据,两类数据存储在不同的table内,通过[geo_client](https://github.com/XiaoMi/pegasus/blob/master/src/geo/lib/geo_client.h)做数据同步,同时支持原生Pegasus API和GEO API访问。
+
+下面讨论GEO索引数据的构造方式。
+
+### hashkey
+
+hashkey直接由一维编码的前缀构成。比如在我们的LBS业务场景中,范围查询都是集中在10km半径内的圆形范围,实际测试结果是将hashkey长度定为`14`(1位face,1位分隔符`/`,12位Hilbert编码)能取得更好的性能。
+
+> `最小搜索层`为12
+
+```
+               cellid
+|--------------32 bytes-------------|
+|---14 bytes----|
+    hashkey
+```
+
+### sortkey
+
+为了满足不同半径范围、不同精度的查询,我们把cellid剩下的18位全部放到sortkey中(这并不会给底层存储带来多少压力),这可以在应用层保持比较高的灵活性,而不用修改底层的数据。在进行较大范围的临近查询时,取更少的sortkey位数(对应的cellid更短)进行数据查询;进行较小范围的临近查询或点查询时,取更多的sortkey位数(对应的cellid更长)进行数据查询。
+
+尽管在30层时,cell的面积已经足够小(<1cm^2),但仍有可能两条数据落在同一个cell里,所以需要区分不同的数据。这里,将原始数据的hashkey和sortkey联合起来,并追加在上述sortkey之后。
+
+                   cellid
+    |--------------32 bytes-------------|
+    |---14 bytes---||-----18 bytes------||--原始hashkey--||--原始sortkey--|
+    |--GEO hashkey-||----------------------GEO sortkey-------------------|
+
+
+> 在相同地理范围内进行数据查询时,使用短cellid查询数据查询的范围大,查询的次数更少,但得到的在区域外的无用数据更多,反之亦然---这需要在查询次数与查询到的有用数据之间做权衡。
+
+### value
+
+GEO API的value必须能够解析出经纬度,具体的解析方式参考[自定义extrator](https://pegasus-kv.github.io/api/geo#%E8%87%AA%E5%AE%9A%E4%B9%89extrator)。
+
+GEO索引数据的value跟原始数据的value完全相同。这里会存在一份冗余,但通常在相对廉价的磁盘存储介质上,这是可以接受的。
+
+> 我们建议业务层在使用GEO API时value只存储小数据,大数据建议采用二次索引的方式。
+
+## 数据更新
+
+### set
+
+`set`操作会同时更新两个table的数据: Pegasus原始数据和GEO索引数据(数据构造方式如上所述)。
+
+> `set`操作的hashkey, sortkey是业务自己的格式,使用GEO API时并不做约束, 只是在geo client转存GEO索引数据时,会自动做如上所述的编码转换。
+
+> 使用Redis API时, 参考 [GEO API](https://pegasus-kv.github.io/api/redis#geo-api)。
+
+> 实现上,`set`会首先尝试`get`出已有的数据,并将已有数据的GEO索引数据清理掉后,再写入新数据。因为新老数据的索引数据hashkey+sortkey可能不一样(即新老value根据extractor解析得到的经纬度不一样),若不清理,在进行地理搜索时将会搜索到脏数据。
+
+### del
+
+`del`操作也会同时删除两个table的数据,原理同上。
+
+## 数据查询
+
+### 思路
+
+直观地,集合中总的cell数量尽可能少,但同时单个cell面积尽可能小。比如:
+
+![s2_cap_1.png](/assets/images/s2_cap_1.png){:class="img-responsive"}
+
+虽然这样的结果更精确,但在实际测试中发现当参与计算的cell层级越大时,cellid的数量就越多,带来的client-server RPC次数更多,整个API消耗更大、延迟就越高。同时,在真实的应用场景中,太小的cell意义不大(没有数据)。
+
+所以,在当前的Pegasus实现中,只联合使用两层cell,`最大搜索层`和`最小搜索层`, 以12层和16层为例:
+
+![s2_cap_2.png](/assets/images/s2_cap_2.png){:class="img-responsive"}
+
+### 查询流程
+
+以search_radial为例,此API的意义是给定点和半径,求出该圆形区域内的所有数据。
+
+> 这里我们只讨论圆形区域的数据查询,其他的比如多边形区域的思想是类似的。
+
+需利用S2提供的查询覆盖指定区域的cellid集合的API:
+
+```
+// Returns an S2CellUnion that covers the given region and satisfies the current options.
+S2CellUnion GetCovering(const S2Region& region);
+```
+
+> `search_radial`API有两个重载函数,一个是输入经纬度,一个是输入hashky+sortkey,后者是通过key取到经纬度再转调前者。
+
+查询流程如下:
+
+1. 根据经纬度、半径,求出S2Cap圆形区域`C`
+2. 根据圆形区域、指定的`最小搜索层`,通过`GetCovering`,求出在`最小搜索层`上的cellid集合
+3. 遍历这些cellid,判断cellid区域跟圆形区域`C`的关系
+   1. 全覆盖:取该cellid的所有数据
+   2. 半覆盖:将该cellid按`最大搜索层`继续拆份, 判断拆分后的sub_cellid区域与圆形区域`C`的关系
+      1. 相交:取该sub_cellid的所有数据
+      2. 不相交:排除
+
+> `最小搜索层`,`最大搜索层`的配置参考后文。
+
+取一个cellid的所有数据时,会根据上文的key构造规则,构造一对包含这个cellid所有数据的`start_sortkey`,`stop_sortkey`,再使用Pegasus的`scan`接口进行数据搜索。
+
+- 对于`3.1`步取到的cellid,它的长度即是hashkey的长度,它也就是hashkey,调用`scan(cellid, '"', "")`搜索数据
+  - 比如,一个12层cell `1/223320022232`被区域完全覆盖,则我们`scan("1/223320022232", "", "")`。
+- 对于`3.2.1`步取到的sub_cellid,hashkey是它的前缀,调用`scan(sub_cellid[0:hashkey_len], sub_cellid[hashkey_len:], sub_cellid[hashkey_len:])`搜索数据
+  - 比如,一个12层cell `1/223320022232`的子区域`0001`,`0002`,`0003`,`0100`才跟目标区域相交时,则我们`scan("1/223320022232", "0001", "0003")`、`scan("1/223320022232", "0100", "0100")`。
+
+> 此处还有一个根据Hilbert曲线实现的一个优化,具体参见[代码](https://github.com/XiaoMi/pegasus/blob/master/src/geo/lib/geo_client.cpp#L510)
+
+得到`scan`的结果后,还需处理:
+
+- 计算距离:因为cellid可能只与输入区域部分重合,该点若在区域外, 需去除
+- 排序:当有升序/降序要求时
+
+### 灵活性
+
+由于我们存储了完整的30层cellid,所以在实际使用中,可以按照自己的地理数据密度、延迟要求等情况调整API的的`最大搜索层`。
+
+> 默认为`16`。
+
+#### API方式
+
+```
+dsn::error_s set_max_level(int level);
+```
+
+#### 配置文件方式
+
+```
+[geo_client.lib]
+max_level = 16
+```
+
+### 不变性
+
+修改hashkey长度需要修改配置文件,但需注意:hashkey一旦确定,数据写入后改配置便不可修改,因为数据已按这个hashkey长度规则固化下来。
+
+> 默认为`12`。
+
+```
+[geo_client.lib]
+;NOTE: 'min_level' is immutable after some data has been inserted into DB by geo_client.
+min_level = 12
+```
+
+## 自定义extrator
+
+目前Pegasus支持从固定格式的value中解析经纬度。经纬度以字符串形式嵌入在value中,以`|`分割, 比如:`.*|115.886447|41.269031|.*`,他们的索引由配置文件中的`latitude_index`和`longitude_index`确定。
+
+## API & redis proxy
+
+Pegasus GEO特性的使用有两种方式,一是直接使用C++ geo client;二是使用redis proxy。
+
+[C++ geo client代码](https://github.com/XiaoMi/pegasus/blob/master/src/geo/lib/geo_client.h)中有详细的API说明,这里不再赘述。
+
+## 配置文件
+
+redis proxy的使用请参考[Redis适配](redis)。
+
+GEO API添加的配置文件如下:
+
+```
+[geo_client.lib]
+;NOTE: 'min_level' is immutable after some data has been inserted into DB by geo_client.
+min_level = 12
+max_level = 16
+
+; 用于经纬度的extrator
+latitude_index = 5
+longitude_index = 4
+```
+
+## 数据导入
+
+有的使用场景是业务已经有普通的KV数据,需要根据这份已有的KV数据转换成如上述的数据格式,我们可以使用shell工具里的`copy_data`功能来实现。比如:
+
+```
+copy_data -c target_cluster -a temp -g
+```
+
+此时目标集群是`target_cluster`,目标表是`temp`,他存储上述的普通数据,目标GEO索引数据表是`temp_geo`,他存储上述的GEO索引数据。
+
+在进行`copy_data`操作之前,目标集群以及两个目标表都需要提前创建好。
+
+数据导入完成后就可以搭建`redis_proxy`了,具体的说明参考[redis适配](redis),需要注意的是配置项:
+
+```
+[apps.proxy]
+; if using GEO APIs, an extra table name which store geo index data should be appened, i.e.
+arguments = redis_cluster temp temp_geo
+```
+
+## benchmark
+
+### 测试环境
+
+机器配置:
+
+- CPU:E5-2620v3 *2
+- 内存:128GB
+- 存储:480G SSD *8
+- 网卡:1Gb
+
+集群配置:
+
+- 节点数:5个replica server节点(使用[v1.9.2版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.9.2))
+- 测试表的Partition数:128个
+- 单条数据大小:120字节
+
+针对接口:
+
+```
+void async_search_radial(double lat_degrees,
+                         double lng_degrees,
+                         double radius_m,
+                         int count,
+                         SortType sort_type,
+                         int timeout_ms,
+                         geo_search_callback_t &&callback);
+```
+
+传递参数:
+
+lat_degrees、lng_degrees:每次都选取北京五环内的随机点
+
+radius_m:如下表第一列,单位米
+
+count:-1,表示不限定结果数量
+
+sort_type:不排序
+
+### 测试结果
+
+| 半径(m) | P50(ms)     | P75(ms)     | P99(ms)     | P99.9(ms)   | 平均结果条数 | 单节点QPS |
+| ------- | ----------- | ----------- | ----------- | ----------- | ------------ | --------- |
+| 50      | 1.63071622  | 1.84607433  | 4.04545455  | 6.28        | 9.4608       | 740.287   |
+| 100     | 1.76        | 2.33614794  | 5.4         | 6.45319149  | 38.0296      | 656.66    |
+| 200     | 2.41017042  | 3.31062092  | 6.41781609  | 9.60588235  | 154.3682     | 536.624   |
+| 300     | 3.30833333  | 4.21979167  | 9.4310559   | 18          | 350.9676     | 434.491   |
+| 500     | 5.07763975  | 6.84964682  | 16.84931507 | 21.78082192 | 986.0826     | 347.231   |
+| 1000    | 12.28164727 | 18.70972532 | 43.18181818 | 57.049698   | 3947.5294    | 204.23    |
+| 2000    | 35.78666667 | 54.7300885  | 108.7331378 | 148.616578  | 15674.1198   | 98.7633   |
diff --git a/_api/http.md b/_api/http.md
new file mode 100644
index 0000000..410e8e3
--- /dev/null
+++ b/_api/http.md
@@ -0,0 +1,396 @@
+---
+title: HTTP接口
+layout: page
+show_sidebar: false
+menubar: api_menu
+---
+
+## 功能介绍
+
+Pegasus为MetaServer,ReplicaServer和Collector都提供了HTTP接口,用于查看集群相关信息,查询服务状态等。
+
+**注意**
+
+- 由于最新数据由主MetaServer维护,当访问备用MetaServer的元数据查询接口时,会自动重定向至主MetaServer对应的接口。
+举个例子,假设 `127.0.0.1:34601` 和 `127.0.0.1:34602` 分别是主备MetaServer,访问 `127.0.0.1:34602/meta/cluster`
+会自动跳转到 `127.0.0.1:34601/meta/cluster`。
+
+## 接口介绍
+
+所有接口均返回JSON格式。使用浏览器查看JSON时,建议使用[Chrome插件json-formatter](https://chrome.google.com/webstore/detail/json-formatter/bcjindcccaagfpapjjmafapmmgkkhgoa)以优化阅读体验。
+
+### `/`
+
+**功能:** 主页面,获取所有接口的使用帮助
+
+**示例:**`127.0.0.1:34801`
+
+**返回:**
+
+```json
+{
+    "/": "ip:port/",
+    "/meta/app": "ip:port/meta/app?app_name=temp",
+    "/meta/app/duplication": "ip:port/meta/app/duplication?name=<app_name>",
+    "/meta/app_envs": "ip:port/meta/app_envs?name=temp",
+    "/meta/apps": "ip:port/meta/apps",
+    "/meta/backup_policy": "ip:port/meta/backup_policy",
+    "/meta/cluster": "ip:port/meta/cluster",
+    "/meta/nodes": "ip:port/meta/nodes",
+    "/perfCounter": "ip:port/perfCounter?name={perf_counter_name}",
+    "/pprof/cmdline": "ip:port/pprof/cmdline",
+    "/pprof/growth": "ip:port/pprof/growth",
+    "/pprof/heap": "ip:port/pprof/heap",
+    "/pprof/profile": "ip:port/pprof/profile",
+    "/pprof/symbol": "ip:port/pprof/symbol",
+    "/recentStartTime": "ip:port/recentStartTime",
+    "/version": "ip:port/version"
+}
+```
+
+### `/version`
+
+**功能:** 获取应用的版本
+
+**示例:**`127.0.0.1:34801/version`
+
+**返回:**
+
+```json
+{
+    "GitCommit": "cf428dc0ef995b961809df860e783417de7757fc",
+    "Version": "1.12.SNAPSHOT"
+}
+```
+
+### `/recentStartTime`
+
+**功能:** 获取进程启动时间
+
+**示例:**`127.0.0.1:34801/recentStartTime`
+
+**返回:**
+
+```json
+{
+    "RecentStartTime": "2019-08-14 00:22:00"
+}
+```
+
+### `/pprof/profile`
+
+**功能:** 获取server的cpu采样
+
+**示例1:**`127.0.0.1:34801/pprof/profile?seconds=10`
+
+**返回:** profile文件
+
+**示例2:** 推荐使用google/pprof Web UI
+
+`pprof --http=:8080 http://127.0.0.1:34801/pprof/heap`
+
+### `/pprof/symbol`
+
+**功能:** 获取symbol信息
+
+**示例1:**`127.0.0.1:34801/pprof/symbol`
+
+**返回:** 非post请求,返回symbol个数
+```
+num_symbols: 46225
+```
+
+**示例2:**`curl 127.0.0.1:34801/pprof/symbol -X POST -d "123455"`
+
+**返回:** post请求,返回输入地址对应的symbol
+```
+0x00123455      boost::asio::detail::keyword_tss_ptr
+```
+
+### `/pprof/heap`
+
+**功能:** 获取server的内存采样
+
+**示例1:**`127.0.0.1:34801/pprof/heap`
+
+**返回:** heap profile文本
+
+**示例2:** 推荐使用google/pprof Web UI
+
+`pprof --http=:8080 http://127.0.0.1:34801/pprof/heap`
+
+**注意:** 需在被采样server所在机器设置环境变量`TCMALLOC_SAMPLE_PARAMETER`
+
+### `/pprof/cmdline`
+
+**功能:** 获取/proc/self/cmdline内容
+
+**示例:**`127.0.0.1:34801/pprof/cmdline`
+
+**返回:** 
+```json
+/somepath/pegasus/onebox/replica1/pegasus_server
+config.ini
+-app_list
+replica
+```
+### `/pprof/growth`
+
+**功能:** 获取growth profile
+
+**示例:**`127.0.0.1:34801/pprof/growth`
+
+**返回:** growth profile文本
+
+### `/meta/app?name=<app_name>`
+
+**功能:** 获取某个表的基本信息。
+
+**参数:**
+
+- name: 待查的表名
+- detail:是否打印详细信息
+
+**示例:**`127.0.0.1:34601/meta/app?name=stat&detail`
+
+**返回:**
+
+```json
+{
+    "general": {
+        "app_id": "1",
+        "app_name": "stat",
+        "max_replica_count": "3",
+        "partition_count": "4"
+    },
+    "healthy": {
+        "fully_healthy_partition_count": "0",
+        "read_unhealthy_partition_count": "0",
+        "unhealthy_partition_count": "4",
+        "write_unhealthy_partition_count": "0"
+    },
+    "nodes": {
+        "127.0.0.1:34801": {
+            "node": "127.0.0.1:34801",
+            "primary": "2",
+            "secondary": "2",
+            "total": "4"
+        },
+        "127.0.0.1:34802": {
+            "node": "127.0.0.1:34802",
+            "primary": "2",
+            "secondary": "2",
+            "total": "4"
+        },
+        "total": {
+            "node": "total",
+            "primary": "4",
+            "secondary": "4",
+            "total": "8"
+        }
+    },
+    "replicas": {
+        "0": {
+            "ballot": "2",
+            "pidx": "0",
+            "primary": "127.0.0.1:34801",
+            "replica_count": "2/3",
+            "secondaries": "[127.0.0.1:34802]"
+        },
+        "1": {
+            "ballot": "2",
+            "pidx": "1",
+            "primary": "127.0.0.1:34802",
+            "replica_count": "2/3",
+            "secondaries": "[127.0.0.1:34801]"
+        },
+        "2": {
+            "ballot": "2",
+            "pidx": "2",
+            "primary": "127.0.0.1:34801",
+            "replica_count": "2/3",
+            "secondaries": "[127.0.0.1:34802]"
+        },
+        "3": {
+            "ballot": "2",
+            "pidx": "3",
+            "primary": "127.0.0.1:34802",
+            "replica_count": "2/3",
+            "secondaries": "[127.0.0.1:34801]"
+        }
+    }
+}
+```
+
+### `/meta/apps`
+
+**功能:** 获取所有表的列表。
+
+**参数:**
+
+- detail:是否打印详细信息
+  
+**示例:**`127.0.0.1:34601/meta/apps?detail`
+
+**返回:**
+  
+```json
+{
+    "general_info": {
+        "1": {
+            "app_id": "1",
+            "app_name": "stat",
+            "app_type": "pegasus",
+            "create_time": "2019-08-15 12:34:15.000",
+            "drop_expire": "-",
+            "drop_time": "-",
+            "envs_count": "0",
+            "is_stateful": "true",
+            "partition_count": "4",
+            "replica_count": "3",
+            "status": "AVAILABLE"
+        },
+        "2": {
+            "app_id": "2",
+            "app_name": "temp",
+            "app_type": "pegasus",
+            "create_time": "2019-08-15 12:34:15.000",
+            "drop_expire": "-",
+            "drop_time": "-",
+            "envs_count": "0",
+            "is_stateful": "true",
+            "partition_count": "8",
+            "replica_count": "3",
+            "status": "AVAILABLE"
+        }
+    },
+    "healthy_info": {
+        "1": {
+            "app_id": "1",
+            "app_name": "stat",
+            "fully_healthy": "0",
+            "partition_count": "4",
+            "read_unhealthy": "0",
+            "unhealthy": "4",
+            "write_unhealthy": "4"
+        },
+        "2": {
+            "app_id": "2",
+            "app_name": "temp",
+            "fully_healthy": "0",
+            "partition_count": "8",
+            "read_unhealthy": "0",
+            "unhealthy": "8",
+            "write_unhealthy": "8"
+        }
+    },
+    "summary": {
+        "fully_healthy_app_count": "0",
+        "read_unhealthy_app_count": "0",
+        "total_app_count": "2",
+        "unhealthy_app_count": "2",
+        "write_unhealthy_app_count": "2"
+    }
+}
+```
+
+### `/meta/cluster`
+
+**功能:** 获取集群基本信息。
+
+**示例:**`127.0.0.1:34601/meta/cluster`
+
+**返回:**
+
+```json
+{
+    "balance_operation_count": "move_pri=0,copy_pri=0,copy_sec=0,total=0",
+    "meta_function_level": "steady",
+    "meta_servers": "127.0.0.1:34601,127.0.0.1:34602,127.0.0.1:34603",
+    "primary_meta_server": "127.0.0.1:34601",
+    "primary_replica_count_stddev": "0.00",
+    "total_replica_count_stddev": "0.00",
+    "zookeeper_hosts": "127.0.0.1:22181",
+    "zookeeper_root": "/pegasus/onebox/127.0.0.1"
+}
+```
+
+### `/meta/nodes`
+
+**功能:** 获取replica节点列表,以IP地址表示各个节点,并输出基本信息。
+
+**参数:**
+
+- detail:是否打印详细信息
+
+**示例:**`127.0.0.1:34601/meta/nodes?detail`
+
+**返回:**
+
+```json
+{
+    "details": {
+        "127.0.0.1:34801": {
+            "address": "127.0.0.1:34801",
+            "primary_count": "6",
+            "replica_count": "12",
+            "secondary_count": "6",
+            "status": "ALIVE"
+        },
+        "127.0.0.1:34802": {
+            "address": "127.0.0.1:34802",
+            "primary_count": "6",
+            "replica_count": "12",
+            "secondary_count": "6",
+            "status": "ALIVE"
+        }
+    },
+    "summary": {
+        "alive_node_count": "2",
+        "total_node_count": "2",
+        "unalive_node_count": "0"
+    }
+}
+```
+
+### `/meta/app_envs?name=<app_name>`
+
+**功能:** 获取某个表的所有环境变量。
+
+**参数:**
+
+- name: 待查的表名
+  
+**示例:**`127.0.0.1:34601/meta/app_envs?name=temp`
+
+**返回:**
+  
+```json
+{
+    "replica.enable_slow_query_log": "true",
+    "replica.slow_query_threshold": "20"
+}
+```
+
+### `/perfCounter?name=<perf_counter_name>`
+
+**功能:** 获取某个perf counter的详细信息。如果perf counter名字中含有特殊字符时,需要先对其进行uri编码。
+
+**参数:**
+
+- name: 待查的perf counter名字
+  
+**示例:**`127.0.0.1:34101/perfCounter?name=collector*app.pegasus*app.stat.read_qps%23_all_`
+
+**返回:**
+  
+```json
+{
+    "name": "collector*app.pegasus*app.stat.read_qps#_all_",
+    "value": "0.00",
+    "type": "NUMBER",
+    "description": "statistic the read_qps of app _all_"
+}
+```
+
+**NOTE:** http中的字符`#`代表锚,是一种特殊字符,所以对于perf counter名字中包含'#'字符的需要转换成`%23`
diff --git a/_api/index.md b/_api/index.md
new file mode 100644
index 0000000..7aecccd
--- /dev/null
+++ b/_api/index.md
@@ -0,0 +1,30 @@
+---
+title: 用户接口
+layout: page
+menubar: api_menu
+---
+
+这里介绍Pegasus服务所提供的用户接口。客户端可以在我们的服务接口上做二次封装。
+通常你只需要通过 [客户端文档](/clients) 学习如何与Pegasus交互。
+
+当前Pegasus支持几种接口协议:
+
+## Pegasus 协议
+
+这也是我们的客户端与Pegasus服务端交互所使用的协议。我们通过该协议暴露了
+
+- 单行非幂等原子操作,如 set/get/del/ttl...
+
+- 单行幂等原子操作,如 check_and_mutate/incr...
+
+- 多行幂等原子操作,如 multiset/multiget/multidel...
+
+- 支持过滤的扫描操作,如 hash_scan/unordered_scan...
+
+## Redis 协议
+
+我们通过Pegasus Redis Proxy这一组件在Pegasus协议之上支持了Redis协议。
+你可以阅读 [Redis适配](/redis) 了解更多。
+
+因为Redis接口提供了GEO支持,我们也借此支持了地理信息查询的功能。
+你可以阅读 [GEO支持](/geo) 了解更多。 
diff --git a/_api/redis.md b/_api/redis.md
new file mode 100644
index 0000000..40de7fe
--- /dev/null
+++ b/_api/redis.md
@@ -0,0 +1,204 @@
+---
+title: Redis适配
+layout: page
+menubar: api_menu
+---
+
+# Redis适配
+
+## 架构
+
+在pegasus上添加了redis proxy后,用户可以通过redis协议直接访问proxy,从而间接访问pegasus服务。整体架构如下:
+
+![redis_proxy_arch.png](/assets/images/redis_proxy_arch.png){:class="img-responsive"}
+
+redis客户端与redis proxy之间使用[redis协议](https://redis.io/topics/protocol),目前proxy已支持所有redis 数据类型(Simple Strings、Errors、Integers、Bulk Strings、Arrays)。
+
+redis proxy与pegasus集群之间使用pegasus的thrift协议,proxy在这里就类似一个普通的pegasus client,从meta server查询meta信息、与replica server进行用户数据的读写。
+
+## 提供服务的形式
+
+跟redis服务一样,以``host:port``形式提供,如果服务压力大,可以提供多个``host:port``来避免单点proxy压力过大造成瓶颈。当提供多个redis proxy地址时,由于后端访问的都是同一个集群的同一张表,数据是完全相同的。用户可以采用round robin, hash等方式进行负载均衡。
+
+>proxy的可执行文件为``pegasus_rproxy``, 由``./run.sh pack_tools``打包生成。
+
+## 配置
+
+redis proxy的配置文件规则遵循[配置说明](/administration/config),参考[示例](https://github.com/XiaoMi/pegasus/blob/master/src/geo/bench/config.ini)。
+
+在redis proxy中有几项特有的配置项需要注意:
+
+```
+[apps.proxy]
+name = proxy
+type = proxy
+; which pegasus cluster and table dose this proxy redirect to
+; 'onebox' is the cluster name which will be used in the next section
+; 'temp' is the table name in the cluster 
+arguments = onebox temp
+; if using GEO APIs, an extra table name which will store geo index data
+; should be appended, i.e.
+; arguments = onebox temp temp_geo
+; port serve for redis clients
+ports = 6379
+pools = THREAD_POOL_DEFAULT
+run = true
+
+[pegasus.clusters]
+; meta serer list the proxy redirect to
+onebox = 127.0.0.1:34601,127.0.0.1:34602,127.0.0.1:34603
+```
+
+## APIs
+
+redis的原生命令请见[这里](https://redis.io/commands) 。
+
+以下接口说明都兼容redis原生命令,但支持的参数可能少于redis,以下接口说明中都给出了目前Pegasus代理所支持的所有参数,未给出的目前不支持。
+
+### KEY规则
+
+#### KV API
+
+对于redis的普通key-value操作,key对应到Pegasus中的hashkey,而Pegasus中的sortkey被设置为空串``""``。如`SET`,  `GET`, `TTL`, `INCR`等。
+
+#### GEO API
+
+在原生redis中,`GEO*`接口操作的数据是通过`GEOADD key longitude latitude member`添加到数据库中的,这时`key`是一个namespace的概念,而不是`SET`操作时的key。
+
+而pegasus proxy的`GEO*`接口操作的数据是通过`SET`接口添加到数据库中的,`SET`的key对应于`GEO*`接口的member,而`GEO*`接口的key则只能是``""``。也就是说,在pegasus 的redis GEO数据中,不再有namespace的概念,全部数据在同一空间`""`下,若要区分key空间,则可以在pegasus层创建新的table实现。具体参考后面的示例。
+
+### SET
+
+```
+SET key value [EX seconds]
+```
+
+### GET
+
+```
+GET key
+```
+
+### DEL
+
+```
+DEL key
+```
+
+注意:  
+
+1. 这里的接口返回值和redis的定义略有不同:当key不存在时,redis接口返回0,这里则返回1。
+
+### SETEX
+
+```
+SETEX key seconds value
+```
+
+### TTL
+
+```
+TTL key
+```
+
+### PTTL
+
+```
+PTTL key
+```
+
+### INCR
+
+```
+INCR key
+```
+
+### INCRBY
+
+```
+INCRBY key increment
+```
+
+### DECR
+
+```
+DECR key
+```
+
+### DECRBY
+
+```
+DECRBY key decrement
+```
+
+### GEODIST
+
+[GEO接口的实现原理](geo)
+
+```
+GEODIST key member1 member2 [unit]
+```
+
+注意:  
+
+1. key规则遵循GEO API的key规则:这里的key只能是``""``,而这里member对应于`SET`操作时的key。
+
+### GEORADIUS
+
+```
+GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD][WITHDIST] [WITHHASH][COUNT count] [ASC|DESC]
+```
+
+注意:  
+1. key规则遵循GEO API的key规则:这里的key只能是``""``,而这里member对应于`SET`操作时的key。
+2. 我们对redis的``WITHHASH``参数进行了修改,使用它将会返回该member的value。
+
+### GEORADIUSBYMEMBER
+
+```
+GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD][WITHDIST] [WITHHASH][COUNT count] [ASC|DESC]
+```
+
+注意:  
+1. key规则同上,这里的key只能是``""``,而这里member对应于`SET`操作时的key。  
+2. 我们对redis的``WITHHASH``参数进行了修改,使用它将会返回该member的value。
+
+## 示例
+
+```
+127.0.0.1:6379> SET abc 1 EX 60
+OK
+
+127.0.0.1:6379> GET abc
+"1"
+
+127.0.0.1:6379> TTL abc
+(integer) 52
+
+127.0.0.1:6379> INCR abc
+(integer) 2
+
+// 以下是GEO API, 注意需要提前创建好geo表
+127.0.0.1:6379> SET 1cc0001000010290050356f "1cc0001000010290050356f|2018-06-10 23:59:59|2018-06-11 13:00:00|wx5j5ff05|116.886447|40.269031|4.863045|20.563248|0|-1"
+OK
+
+127.0.0.1:6379> SET 2cc0001000010290050356f "2cc0001000010290050356f|2018-06-10 23:59:59|2018-06-11 13:00:00|wx5j5ff05|115.886447|41.269031|4.863045|20.563248|0|-1"
+OK
+
+127.0.0.1:6379> GEORADIUS "" 116.889137 40.261774 1000 m COUNT 100 ASC WITHDIST WITHCOORD
+1) 1) "1cc0001000010290050356f"
+   2) "838.600772"
+   3) 1) "116.886447"
+      2) "40.269031"
+   4) "1cc0001000010290050356f|2018-06-10 23:59:59|2018-06-11 13:00:00|wx5j5ff05|116.886447|40.269031|4.863045|20.563248|0|-1"
+
+127.0.0.1:6379> GEORADIUSBYMEMBER "" 1cc0001000010290050356f 1000 m WITHCOORD WITHDIST WITHHASH
+1) 1) "1cc0001000010290050356f"
+   2) "0.000000"
+   3) 1) "116.886447"
+      2) "40.269031"
+   4) "1cc0001000010290050356f|2018-06-10 23:59:59|2018-06-11 13:00:00|wx5j5ff05|116.886447|40.269031|4.863045|20.563248|0|-1"
+
+127.0.0.1:6379> GEODIST "" 1cc0001000010290050356f 2cc0001000010290050356f m
+"139483.293598"
+```
diff --git a/_api/single-atomic.md b/_api/single-atomic.md
new file mode 100755
index 0000000..87c407d
--- /dev/null
+++ b/_api/single-atomic.md
@@ -0,0 +1,55 @@
+---
+title: 单行原子操作
+layout: page
+show_sidebar: false
+menubar: api_menu
+---
+
+从[v1.10.0版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.10.0)开始,Pegasus支持单行原子操作。这里的**单行**是指同一HashKey下的所有数据。
+
+# 原理
+Pegasus采用Hash分片,同一个HashKey的数据总是存储在同一个Partition中,即相同的Replica中。同时,Pegasus实现时,同一个Replica的写操作在server端总是串行执行的。因此对于同一HashKey下的数据操作,可以很方便地实现原子的语义。
+
+对于纯粹的写操作,譬如[multiSet](/clients/java-client#multiset)和[multiDel](/clients/java-client#multidel),单个操作中对多个SortKey同时set或者del,原子语义很容易理解,要么同时成功,要么同时失败,所以这两个操作属于单行原子操作。
+
+不过我们这里重点关注的是另一类操作:**先读后写,并且写操作依赖读的结果**。这类操作的特点就是:它们是**非幂等**的,即同一个操作如何多次重复执行,造成的结果(包括数据实际的更新情况、返回给用户的结果)可能是不同的。原子增减和CAS操作都属于这类。Pegasus能保证这类操作的原子性和一致性,因为:
+* 同一个HashKey的数据总是存储在同一个Replica中;
+* 同一个Replica的写操作在server端总是串行执行的;
+* 同一个操作保证执行且只会执行一次,即使发生数据迁移、宕机恢复等情况。
+
+由于**非幂等**特性,这类操作会和Pegasus的另外一些特性冲突,譬如跨机房热备。所以我们在1.10.0版本中还提供了一个配置项,用于配置集群是否允许**非幂等**操作,如果配置为false,则所有非幂等操作都会返回`ERR_OPERATION_DISABLED`:
+```ini
+[replication]
+    allow_non_idempotent_write = false
+```
+
+# 原子增减
+虽然Pegasus的value不支持schema,但是我们依然提供了原子增减操作,类似Redis的[incr命令](http://www.redis.cn/commands/incr.html)。接口参见[incr](/clients/java-client#incr)。
+
+语义解释:
+* 首先server端存储的仍然是字节串,但是在incr的时候会先将字节串先转换为int64类型,转换方式就是简单的String-to-Int,譬如字节串`12345`就会转换为数字12345。完成incr操作后,得到的结果会重新转换为字节串,然后存储为新值。
+* 字节串转换为int64时可能出错,譬如不是合法的数字、超过int64的范围等,都会报错。
+* 如果原value不存在,则认为原始值为0,正常执行incr操作。
+* 操作数`increment`可以为正数也可以为负数,所以一个incr接口就可以实现原子增或者原子减。
+* TTL语义:如果原value存在,则新值和原值的TTL保持一致;如果原value不存在,则新值在存储时不设TTL。
+
+# CAS操作
+另一类很有用的原子操作就是CAS(Compare-And-Swap),直译就是对比交换,最初是表示一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值。基于CAS操作,可以实现很多高级的并发特性,譬如锁。因此很多编程语言也原生地提供CAS操作。
+
+Pegasus提供了check_and_set的CAS操作,其语义就是:根据HashKey的某一个SortKey的值是否满足某种条件,来决定是否修改另一个SortKey的值。我们将用于判断条件的SortKey称之为`CheckSortKey`,将用于设置值的SortKey称之为`SetSortKey`。对应地,CheckSortKey的value称之为`CheckValue`,SetSortKey要设置的value称之为`SetValue`。接口参见[checkAndSet](/clients/java-client#checkandset),以及其扩展版本[checkAndMutate](/clients/java-client#checkandmutate)和[compareExchange](/clients/java-client#compareexchange)。
+
+语义解释:
+* 只有当CheckValue满足指定的条件时,才会设置SetSortKey的值。
+* 需要满足的条件类型通过CheckType指定,有的CheckType还需要指定操作数CheckOperand。目前支持的类型包括:
+  * 判断CheckValue的appearance属性:是否存在、是否为空字节串等。
+  * 字节串比较:将CheckValue与CheckOperand按照字节序比较,看是否满足`<`、`<=`、`==`、`>=`、`>=`关系。
+  * 数字比较:类似于[incr操作](#原子增减),先将CheckValue转换为int64,再与CheckOperand比较,看是否满足`<`、`<=`、`==`、`>=`、`>=`关系。
+* CheckSortKey和SetSortKey可以相同,如果相同,就是先判断旧值是否满足条件,满足的话就设置为新值。
+* 可以通过选项`CheckAndSetOptions.returnCheckValue`指定返回CheckValue的值。
+* 可以通过选项`CheckAndSetOptions.setValueTTLSeconds`指定SetValue的TTL。
+
+为了方便使用,Pegasus Java Client还提供了compare_exchange接口:当HashKey的某个SortKey的value按照字节串比较**等于**用户指定的ExpectedValue时,就将其value更新为用户指定的DesiredValue。从语义上来看,compare_exchange更像是Compare-And-Swap的另外一种说法。接口参见[compareExchange](/clients/java-client#compareexchange)。
+
+compare_exchange其实是check_and_set的特化版本:
+* CheckSortKey和SetSortKey相同。
+* CheckType为CT_VALUE_BYTES_EQUAL。
diff --git a/_api/ttl.md b/_api/ttl.md
new file mode 100755
index 0000000..f8b766a
--- /dev/null
+++ b/_api/ttl.md
@@ -0,0 +1,71 @@
+---
+title: TTL
+layout: page
+menubar: api_menu
+---
+
+# 原理
+Pegasus支持TTL(Time-To-Live)功能,即在写入数据的时候,可以指定数据的过期时间。一旦过期,数据对用户就是不可见的,通过get/multiGet等查询接口获取不到数据,就跟数据没有写入一样。
+
+设置的时候,用户通常都是提供`ttl_seconds`参数,表示从当前时间开始计算,多少秒之后为过期时间。如果为0,表示不设置TTL,即数据用不过期。
+
+用户通常有疑问,数据过期后对用户不可见是怎么做到的呢?数据会被立即删除吗?实际上不是这样的,这要从TTL的实现原理说起。
+
+Pegasus的TTL是通过在RocksDB中存储数据时记录数据的过期时间,然后在查询时对过期时间进行检查和过滤来实现的。如下图:
+
+![pegasus-ttl.png](/assets/images/pegasus-ttl.png){:class="img-responsive"}
+
+**写入过程:**
+* 在写入数据时,用户在客户端通过`ttl_seconds`参数设置TTL时间,客户端先计算数据的过期时间`ExpireTime = CurrentTime + ttl_seconds`,然后通过RPC将数据和ExpireTime一起传给ReplicaServer端执行。
+* ReplicaServer收到写请求后,经过各种处理(包括写日志、replication复制等),最后将数据存储到RocksDB中。在存储value的时候,会将ExpireTime放在value头部固定的4个字节中。
+
+**读取过程:**
+* 用户通过客户端查询指定key对应的value数据。
+* ReplicaServer收到读请求后,先从RocksDB获取到key对应的value,然后从value头部提取出ExpireTime:
+  * 如果ExpireTime == 0,表示数据没有设置TTL,是有效的。
+  * 如果ExpireTime > 0,表示数据设置了TTL,则将ExpireTime与当前时间进行对比:如果没有过期,则数据是有效的;如果已经过期,则数据是无效的,返回NotFound。
+
+**数据删除:**
+* 数据过期后,并不能立即从RocksDB中消失,而是通过compaction来进行删除的。我们定制了RocksDB的CompactionFilter,使其在compaction过程中检查数据value头部的ExpireTime,如果已经过期,则数据会被扔掉,不会出现在新生成的文件中。
+* 因为删除过程是异步的,与compaction的执行时机和频率有关,所以数据过期与数据删除通常不是同时发生的,唯一能保证的是数据删除肯定发生在数据过期之后。未删除的过期数据会占据磁盘空间,这点是需要考虑到的。
+
+# 接口
+我们在客户端和Shell工具都提供了设置和查询TTL的接口。
+
+Pegasus Java Client中以下接口可以查询和设置TTL:
+* [ttl](/clients/java-client#ttl):获取指定数据的TTL信息。
+* [set](/clients/java-client#set)和[batchSet](/clients/java-client#batchset):都提供了设置TTL的参数,其中batchSet是在SetItem中设置的。
+* [multiSet](/clients/java-client#multiset)和[batchMultiSet](/clients/java-client#batchmultiset):都提供了设置TTL的参数。
+* [incr](/clients/java-client#batchmultiset):从[v1.11.1版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.11.1)开始,incr接口也提供了修改TTL的功能。
+* [checkAndSet](/clients/java-client#checkandset):在CheckAndSetOptions中提供了设置TTL的参数。
+
+Shell工具中以下命令可以查询和设置TTL:
+* [ttl](/overview/shell#ttl)命令:获取指定数据的TTL信息。
+* [set](/overview/shell#set)和[multi_set](/overview/shell#multi_set)命令:都提供了设置TTL的参数。
+
+# 表级TTL
+从[v1.11.2版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.11.2)开始,Pegasus支持表级TTL功能。
+
+实现原理:
+* 用户在[Table环境变量](/administration/table-env)中设置`default_ttl`环境变量。
+* MetaServer将环境变量异步地通知到各个ReplicaServer,使该表的每个replica都获取到该环境变量,这个过程大约有几秒到几十秒不等的延迟,但是不会超过一分钟。
+* replica获得环境变量后,解析获得default_ttl配置,并立即开始生效。生效之后:
+  * 用户新写入的数据,如果TTL=0(使用默认TTL=0或者显式设置TTL=0),则将数据的实际TTL设置为default_ttl。
+  * RocksDB在进行compaction的时候,如果compact输入文件的原数据没有TTL,则将compact输出文件的新数据的TTL设置为default_ttl。这个过程依赖于compaction的触发时机,所以时间点是不确定的。
+  * 如果执行[Manual Compact](/administration/manual-compact),那么所有文件都会经过compaction处理,原来没有TTL的数据都会设置TTL为default_ttl。
+
+考虑这样的场景:业务方在初期写入数据时没有设置TTL,后来改变需求,希望所有数据都加TTL,并且以前没有设置TTL的数据从现在开始计算TTL,那么就可以通过`表级TTL`加上`Manual Compact`的功能实现这个目的。
+
+# 通过TTL计算数据写入时间
+如果数据写入时设置了TTL,就可以通过TTL计算出数据写入时间。依据的公式是:
+```
+TTLExpireTime = InsertTime + TTLSeconds = now + TTLRemainingSeconds
+
+  ==>
+
+InsertTime = now + TTLRemainingSeconds - TTLSeconds
+```
+其中:
+* TTLRemainingSeconds:通过[Shell的ttl命令](/overview/shell#ttl)获取。
+* now:执行Shell ttl命令的时间。
+* TTLSeconds:用户知道数据写入时设置的TTL。
diff --git a/_clients/cpp-client.md b/_clients/cpp-client.md
new file mode 100755
index 0000000..3d6e90d
--- /dev/null
+++ b/_clients/cpp-client.md
@@ -0,0 +1,730 @@
+---
+title: C++客户端文档
+layout: page
+show_sidebar: false
+menubar: clients_menu
+---
+
+# 获取Cpp客户端
+首先需要[编译Pegasus](/overview/compilation),编译完成后运行以下命令可以打包生产Cpp客户端库:
+```
+./run.sh pack_client
+```
+运行成功后,会在本地文件夹下生产pegasus-client-{version}-{platform}-{buildType}的文件夹以及tar.gz文件。在文件夹里面有个sample/文件夹,进去后可以运行make编译示例程序。
+
+# 配置文件
+Cpp客户端由于使用了rdsn框架,所以配置文件较为复杂,如下:
+```
+[apps..default]
+run = true
+count = 1
+;network.client.RPC_CHANNEL_TCP = dsn::tools::sim_network_provider, 65536
+;network.client.RPC_CHANNEL_UDP = dsn::tools::sim_network_provider, 65536
+;network.server.0.RPC_CHANNEL_TCP = dsn::tools::sim_network_provider, 65536
+
+[apps.mimic]
+type = dsn.app.mimic
+arguments =
+pools = THREAD_POOL_DEFAULT
+run = true
+count = 1
+
+[core]
+;tool = simulator
+;tool = fastrun
+tool = nativerun
+;toollets = tracer
+;toollets = tracer, profiler, fault_injector
+pause_on_start = false
+cli_local = false
+cli_remote = false
+
+start_nfs = false
+
+logging_start_level = LOG_LEVEL_DEBUG
+logging_factory_name = dsn::tools::simple_logger
+logging_flush_on_exit = true
+
+enable_default_app_mimic = true
+
+data_dir = ./data
+
+[tools.simple_logger]
+short_header = true
+fast_flush = true
+max_number_of_log_files_on_disk = 10
+stderr_start_level = LOG_LEVEL_ERROR
+
+[tools.hpc_logger]
+per_thread_buffer_bytes = 8192
+max_number_of_log_files_on_disk = 10
+
+[tools.simulator]
+random_seed = 0
+
+[network]
+; how many network threads for network library(used by asio)
+io_service_worker_count = 4
+
+; specification for each thread pool
+[threadpool..default]
+worker_count = 4
+
+[threadpool.THREAD_POOL_DEFAULT]
+name = default
+partitioned = false
+max_input_queue_length = 1024
+worker_priority = THREAD_xPRIORITY_NORMAL
+worker_count = 4
+
+[task..default]
+is_trace = false
+is_profile = false
+allow_inline = false
+fast_execution_in_network_thread = false
+rpc_call_header_format = NET_HDR_DSN
+rpc_call_channel = RPC_CHANNEL_TCP
+rpc_timeout_milliseconds = 5000
+
+[pegasus.clusters]
+onebox = @LOCAL_IP@:34601,@LOCAL_IP@:34602,@LOCAL_IP@:34603
+another_cluster = @SOME_IP@:34601,@SOME_IP@:34601,@SOME_IP@:34601
+```
+配置文件的具体解释,请移步[配置说明](/administration/config)。
+
+# 接口定义
+## 创建Client实例
+客户端工厂类
+```
+namespace pegasus {
+class pegasus_client_factory
+{
+public:
+    ///
+    /// \brief initialize
+    /// initialize pegasus client lib. must call this function before anything else.
+    /// \param config_file
+    /// the configuration file of client lib
+    /// \return
+    /// true indicate the initailize is success.
+    ///
+    static bool initialize(const char *config_file);
+
+    ///
+    /// \brief get_client
+    /// get an instance for a given cluster and a given app name.
+    /// \param cluster_name
+    /// the pegasus cluster name.
+    /// a cluster can have multiple apps.
+    /// \param app_name
+    /// an app is a logical isolated k-v store.
+    /// a cluster can have multiple apps.
+    /// \return
+    /// the client instance. DO NOT delete this client even after usage.
+    static pegasus_client *get_client(const char *cluster_name, const char *app_name);
+};
+} //end namespace
+```
+客户端的初始化,实际上是对rdsn框架初始化
+```
+if (!pegasus::pegasus_client_factory::initialize("config.ini")) {
+    fprintf(stderr, "ERROR: init pegasus failed\n");
+    return -1;
+}
+/**succeed, continue**/
+```
+注:
+ * 参数:config_file 见 配置文件 的介绍
+ * 返回值:bool值,true代表初始化成功,false初始化失败
+ * 该函数只需在一个进程生命周期内调用一次即可,并且非线程安全的
+
+获取客户端
+```
+pegasus::pegasus_client* pg_client = pegasus::pegasus_client_factory::get_client("cluster_name", "table_name");
+if (pg_client == nullptr) {
+    fprintf(stderr, "ERROR: get pegasus client failed\n");
+    return -1;
+}
+/***  do what you want with pg_client ****/
+```
+注意: get_client返回值不能明确的调用delete,也不能用智能指针封装,框架在停止的时候自动释放(底层使用单例来保存)
+
+## pegasus_client接口
+Cpp客户端提供两种接口,异步API 和 同步API,同步API是基于异步API实现的,具体如下:
+
+### set
+写单行数据
+```
+    ///
+    /// \brief set
+    ///     store the k-v to the cluster.
+    ///     key is composed of hashkey and sortkey.
+    /// \param hashkey
+    /// used to decide which partition to put this k-v
+    /// \param sortkey
+    /// all the k-v under hashkey will be sorted by sortkey.
+    /// \param value
+    /// the value we want to store.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \param ttl_seconds
+    /// time to live of this value, if expired, will return not found; 0 means no ttl
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string()
+    ///
+    virtual int set(const std::string &hashkey,
+                    const std::string &sortkey,
+                    const std::string &value,
+                    int timeout_milliseconds = 5000,
+                    int ttl_seconds = 0,
+                    internal_info *info = NULL) = 0;
+```
+注:
+ * internal_info 结构如下,主要是记录在写入成功之后,该条数据的一些信息,在使用之前需要判断info是否为空
+```
+    struct internal_info
+    {
+        int32_t app_id;
+        int32_t partition_index;
+        int64_t decree;
+        std::string server;
+        internal_info() : app_id(-1), partition_index(-1), decree(-1) {}
+     }
+```
+ * 返回值:int值来表示是否成功,通过get_error_string() 函数来判断返回值的意义(下面的所有同步接口的返回值,都可以通过该函数判断返回值意义)
+```
+    ///
+    /// \brief get_error_string
+    /// get error string
+    /// all the function above return an int value that indicates an error can be converted into a
+    /// string for human reading.
+    /// \param error_code
+    /// all the error code are defined in "error_def.h"
+    /// \return
+    ///
+    virtual const char *get_error_string(int error_code) const = 0;
+```
+### async_set
+异步写单行数据
+```
+    ///
+    /// \brief asynchronous set
+    ///     store the k-v to the cluster.
+    ///     will not be blocked, return immediately.
+    ///     key is composed of hashkey and sortkey.
+    /// \param hashkey
+    /// used to decide which partition to put this k-v
+    /// \param sortkey
+    /// all the k-v under hashkey will be stored by sortkey.
+    /// \param value
+    /// the value we want to store.
+    /// \param callback
+    /// the callback function will be invoked after operation finished or error occurred.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error.
+    /// \param ttl_seconds
+    /// time to live of this value, if expired, will return not found; 0 means no ttl.
+    /// \return
+    /// void.
+    ///
+    virtual void async_set(const std::string &hashkey,
+                           const std::string &sortkey,
+                           const std::string &value,
+                           async_set_callback_t &&callback = nullptr,
+                           int timeout_milliseconds = 5000,
+                           int ttl_seconds = 0) = 0;
+```
+
+### multi_set
+写多条数据(同一hashkey下面)
+```
+    ///
+    /// \brief multi_set (guarantee atomicity)
+    ///     store multiple k-v of the same hashkey to the cluster.
+    /// \param hashkey
+    /// used to decide which partition to put this k-v
+    /// \param kvs
+    /// all <sortkey,value> pairs to be set. should not be empty
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \param ttl_seconds
+    /// time to live of this value, if expired, will return not found; 0 means no ttl
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string().
+    /// return PERR_INVALID_ARGUMENT if param kvs is empty.
+    ///
+    virtual int multi_set(const std::string &hashkey,
+                          const std::map<std::string, std::string> &kvs,
+                          int timeout_milliseconds = 5000,
+                          int ttl_seconds = 0,
+                          internal_info *info = NULL) = 0;
+```
+
+### async_multi_set
+异步写多条数据
+```
+    ///
+    /// \brief asynchronous multi_set (guarantee atomicity)
+    ///     store multiple k-v of the same hashkey to the cluster.
+    ///     will not be blocked, return immediately.
+    /// \param hashkey
+    /// used to decide which partition to put this k-v
+    /// \param kvs
+    /// all <sortkey,value> pairs to be set. should not be empty
+    /// \param callback
+    /// the callback function will be invoked after operation finished or error occurred.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \param ttl_seconds
+    /// time to live of this value, if expired, will return not found; 0 means no ttl
+    /// \return
+    /// void.
+    ///
+    virtual void async_multi_set(const std::string &hashkey,
+                                 const std::map<std::string, std::string> &kvs,
+                                 async_multi_set_callback_t &&callback = nullptr,
+                                 int timeout_milliseconds = 5000,
+                                 int ttl_seconds = 0) = 0;
+```
+
+### get
+读取一条数据
+```
+    ///
+    /// \brief get
+    ///     get value by key from the cluster.
+    /// \param hashkey
+    /// used to decide which partition to get this k-v
+    /// \param sortkey
+    /// all the k-v under hashkey will be sorted by sortkey.
+    /// \param value
+    /// the returned value will be put into it.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string().
+    /// returns PERR_NOT_FOUND if no value is found under the <hashkey,sortkey>.
+    ///
+    virtual int get(const std::string &hashkey,
+                    const std::string &sortkey,
+                    std::string &value,
+                    int timeout_milliseconds = 5000,
+                    internal_info *info = NULL) = 0;
+```
+
+### async_get
+异步读取一条数据
+```
+    ///
+    /// \brief asynchronous get
+    ///     get value by key from the cluster.
+    ///     will not be blocked, return immediately.
+    /// \param hashkey
+    /// used to decide which partition to get this k-v
+    /// \param sortkey
+    /// all the k-v under hashkey will be sorted by sortkey.
+    /// \param callback
+    /// the callback function will be invoked after operation finished or error occurred.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// void.
+    ///
+    virtual void async_get(const std::string &hashkey,
+                           const std::string &sortkey,
+                           async_get_callback_t &&callback = nullptr,
+                           int timeout_milliseconds = 5000) = 0;
+```
+
+### multi_get
+读取多条数据
+```
+    ///
+    /// \brief multi_get
+    ///     get multiple value by key from the cluster.
+    /// \param hashkey
+    /// used to decide which partition to get this k-v
+    /// \param sortkeys
+    /// all the k-v under hashkey will be sorted by sortkey.
+    /// if empty, means fetch all sortkeys under the hashkey.
+    /// \param values
+    /// the returned <sortkey,value> pairs will be put into it.
+    /// if data is not found for some <hashkey,sortkey>, then it will not appear in the map.
+    /// \param max_fetch_count
+    /// max count of k-v pairs to be fetched. max_fetch_count <= 0 means no limit.
+    /// \param max_fetch_size
+    /// max size of k-v pairs to be fetched. max_fetch_size <= 0 means no limit.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string().
+    /// returns PERR_OK if fetch done, even no data is returned.
+    /// returns PERR_INCOMPLETE is only partial data is fetched.
+    ///
+    virtual int multi_get(const std::string &hashkey,
+                          const std::set<std::string> &sortkeys,
+                          std::map<std::string, std::string> &values,
+                          int max_fetch_count = 100,
+                          int max_fetch_size = 1000000,
+                          int timeout_milliseconds = 5000,
+                          internal_info *info = NULL) = 0;
+```
+注:max_fetch_count 和 max_fetch_size 分别从kv-pair的条数 和 总的大小来限制multi_get的返回值
+
+### async_multi_get
+异步读取多条数据
+```
+    ///
+    /// \brief asynchronous multi_get
+    ///     get multiple value by key from the cluster.
+    ///     will not be blocked, return immediately.
+    /// \param hashkey
+    /// used to decide which partition to get this k-v
+    /// \param sortkeys
+    /// all the k-v under hashkey will be sorted by sortkey.
+    /// if empty, means fetch all sortkeys under the hashkey.
+    /// \param callback
+    /// the callback function will be invoked after operation finished or error occurred.
+    /// \param max_fetch_count
+    /// max count of k-v pairs to be fetched. max_fetch_count <= 0 means no limit.
+    /// \param max_fetch_size
+    /// max size of k-v pairs to be fetched. max_fetch_size <= 0 means no limit.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// void.
+    ///
+    virtual void async_multi_get(const std::string &hashkey,
+                                 const std::set<std::string> &sortkeys,
+                                 async_multi_get_callback_t &&callback = nullptr,
+                                 int max_fetch_count = 100,
+                                 int max_fetch_size = 1000000,
+                                 int timeout_milliseconds = 5000) = 0;
+```
+
+### multi_get_sortkeys
+获取hashkey下面的多个sortkey,不返回value
+```
+    ///
+    /// \brief multi_get_sortkeys
+    ///     get multiple sort keys by hash key from the cluster.
+    ///     only fetch sort keys, but not fetch values.
+    /// \param hashkey
+    /// used to decide which partition to get this k-v
+    /// \param sortkeys
+    /// the returned sort keys will be put into it.
+    /// \param max_fetch_count
+    /// max count of sort keys to be fetched. max_fetch_count <= 0 means no limit.
+    /// \param max_fetch_size
+    /// max size of sort keys to be fetched. max_fetch_size <= 0 means no limit.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string().
+    /// returns PERR_OK if fetch done, even no data is returned.
+    /// returns PERR_INCOMPLETE is only partial data is fetched.
+    ///
+    virtual int multi_get_sortkeys(const std::string &hashkey,
+                                   std::set<std::string> &sortkeys,
+                                   int max_fetch_count = 100,
+                                   int max_fetch_size = 1000000,
+                                   int timeout_milliseconds = 5000,
+                                   internal_info *info = NULL) = 0;
+```
+注:max_fetch_count 和 max_fetch_size 分别限制返回的sortkey的个数与总大小(计算大小的时候,为每条sortkey都计算一次hashkey的大小)
+
+### async_multi_get_sortkeys
+异步获取hashkey下面的多个sortkey(不包含value)
+```
+    ///
+    /// \brief asynchronous multi_get_sortkeys
+    ///     get multiple sort keys by hash key from the cluster.
+    ///     only fetch sort keys, but not fetch values.
+    ///     will not be blocked, return immediately.
+    /// \param hashkey
+    /// used to decide which partition to get this k-v
+    /// \param callback
+    /// the callback function will be invoked after operation finished or error occurred.
+    /// \param max_fetch_count
+    /// max count of sort keys to be fetched. max_fetch_count <= 0 means no limit.
+    /// \param max_fetch_size
+    /// max size of sort keys to be fetched. max_fetch_size <= 0 means no limit.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// void.
+    ///
+    virtual void async_multi_get_sortkeys(const std::string &hashkey,
+                                          async_multi_get_sortkeys_callback_t &&callback = nullptr,
+                                          int max_fetch_count = 100,
+                                          int max_fetch_size = 1000000,
+                                          int timeout_milliseconds = 5000) = 0;
+```
+
+### exist
+判断单条数据是否存在
+```
+    ///
+    /// \brief exist
+    ///     check value exist by key from the cluster.
+    /// \param hashkey
+    /// used to decide which partition to get this k-v
+    /// \param sortkey
+    /// all the k-v under hashkey will be sorted by sortkey.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string().
+    /// returns PERR_OK if exist.
+    /// returns PERR_NOT_FOUND if not exist.
+    ///
+    virtual int exist(const std::string &hashkey,
+                      const std::string &sortkey,
+                      int timeout_milliseconds = 5000,
+                      internal_info *info = NULL) = 0;
+```
+
+### sortkey_count
+统计hashkey下面的sortkey个数
+```
+    ///
+    /// \brief sortkey_count
+    ///     get sortkey count by hashkey from the cluster.
+    /// \param hashkey
+    /// used to decide which partition to get this k-v
+    /// \param count
+    /// the returned sortkey count
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string().
+    ///
+    virtual int sortkey_count(const std::string &hashkey,
+                              int64_t &count,
+                              int timeout_milliseconds = 5000,
+                              internal_info *info = NULL) = 0;
+```
+
+### del
+删除单条数据
+```
+    ///
+    /// \brief del
+    ///     del stored k-v by key from cluster
+    ///     key is composed of hashkey and sortkey. must provide both to get the value.
+    /// \param hashkey
+    /// used to decide from which partition to del this k-v
+    /// \param sortkey
+    /// all the k-v under hashkey will be sorted by sortkey.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string()
+    ///
+    virtual int del(const std::string &hashkey,
+                    const std::string &sortkey,
+                    int timeout_milliseconds = 5000,
+                    internal_info *info = NULL) = 0;
+```
+
+### async_del
+异步删除单条数据
+```
+    ///
+    /// \brief asynchronous del
+    ///     del stored k-v by key from cluster
+    ///     key is composed of hashkey and sortkey. must provide both to get the value.
+    ///     will not be blocked, return immediately.
+    /// \param hashkey
+    /// used to decide from which partition to del this k-v
+    /// \param sortkey
+    /// all the k-v under hashkey will be sorted by sortkey.
+    /// \param callback
+    /// the callback function will be invoked after operation finished or error occurred.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// void.
+    ///
+    virtual void async_del(const std::string &hashkey,
+                           const std::string &sortkey,
+                           async_del_callback_t &&callback = nullptr,
+                           int timeout_milliseconds = 5000) = 0;
+```
+
+### multi_del
+删除多条数据
+```
+    ///
+    /// \brief multi_del
+    ///     delete multiple value by key from the cluster.
+    /// \param hashkey
+    /// used to decide which partition to get this k-v
+    /// \param sortkeys
+    /// all the k-v under hashkey will be sorted by sortkey. should not be empty.
+    /// \param deleted_count
+    /// return count of deleted k-v pairs.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string().
+    ///
+    virtual int multi_del(const std::string &hashkey,
+                          const std::set<std::string> &sortkeys,
+                          int64_t &deleted_count,
+                          int timeout_milliseconds = 5000,
+                          internal_info *info = NULL) = 0;
+```
+
+### async_multi_del
+异步的删除多条数据
+```
+    ///
+    /// \brief asynchronous multi_del
+    ///     delete multiple value by key from the cluster.
+    ///     will not be blocked, return immediately.
+    /// \param hashkey
+    /// used to decide which partition to get this k-v
+    /// \param sortkeys
+    /// all the k-v under hashkey will be sorted by sortkey. should not be empty.
+    /// \param callback
+    /// the callback function will be invoked after operation finished or error occurred.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// void.
+    ///
+    virtual void async_multi_del(const std::string &hashkey,
+                                 const std::set<std::string> &sortkeys,
+                                 async_multi_del_callback_t &&callback = nullptr,
+                                 int timeout_milliseconds = 5000) = 0;
+```
+
+### ttl
+获取单行数据的TTL时间。TTL表示Time To Live,表示该数据还能存活多久。如果超过存活时间,数据就读不到了
+```
+    ///
+    /// \brief ttl (time to live)
+    ///     get ttl in seconds of this k-v.
+    ///     key is composed of hashkey and sortkey. must provide both to get the value.
+    /// \param hashkey
+    /// used to decide which partition to get this k-v
+    /// \param sortkey
+    /// all the k-v under hashkey will be sorted by sortkey.
+    /// \param ttl_seconds
+    /// the returned ttl value in seconds.
+    /// \param timeout_milliseconds
+    /// if wait longer than this value, will return time out error
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string()
+    ///
+    virtual int ttl(const std::string &hashkey,
+                    const std::string &sortkey,
+                    int &ttl_seconds,
+                    int timeout_milliseconds = 5000,
+                    internal_info *info = NULL) = 0;
+```
+
+### get_scanner
+获取针对某个hashkey下的sortkey区间 [sortkeyA ~ sortkeyB)的一个scanner
+```
+    ///
+    /// \brief get hash scanner
+    ///     get scanner for [start_sortkey, stop_sortkey) of hashkey
+    /// \param hashkey
+    /// cannot be empty
+    /// \param start_sortkey
+    /// sortkey to start with
+    /// \param stop_sortkey
+    /// sortkey to stop. ""(empty string) represents the max key
+    /// \param options
+    /// which used to indicate scan options, like which bound is inclusive
+    /// \param scanner
+    /// out param, used to get k-v
+    /// this pointer should be deleted when scan complete
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string()
+    ///
+    virtual int get_scanner(const std::string &hashkey,
+                            const std::string &start_sortkey, // start from beginning if this set ""
+                            const std::string &stop_sortkey,  // to the last item if this set ""
+                            const scan_options &options,
+                            pegasus_scanner *&scanner) = 0;
+```
+
+### async_get_scanner
+异步获取针对某个hashkey下的sortkey区间 [sortkeyA ~ sortkeyB)的一个scanner
+```
+    ///
+    /// \brief async get hash scanner
+    ///     get scanner for [start_sortkey, stop_sortkey) of hashkey
+    ///     will not be blocked, return immediately.
+    /// \param hashkey
+    /// cannot be empty
+    /// \param start_sortkey
+    /// sortkey to start with
+    /// \param stop_sortkey
+    /// sortkey to stop. ""(empty string) represents the max key
+    /// \param options
+    /// which used to indicate scan options, like which bound is inclusive
+    /// \param callback
+    /// return status and scanner in callback, and the latter should be deleted when scan complete
+    ///
+    virtual void
+    async_get_scanner(const std::string &hashkey,
+                      const std::string &start_sortkey, // start from beginning if this set ""
+                      const std::string &stop_sortkey,  // to the last item if this set ""
+                      const scan_options &options,
+                      async_get_scanner_callback_t &&callback) = 0;
+```
+
+### get_unordered_scanners
+获取一个遍历所有数据的scanner
+```
+    ///
+    /// \brief get a bundle of scanners to iterate all k-v in table
+    ///        scanners should be deleted when scan complete
+    /// \param max_split_count
+    /// the number of scanners returned will always <= max_split_count
+    /// \param options
+    /// which used to indicate scan options, like timeout_milliseconds
+    /// \param scanners
+    /// out param, used to get k-v
+    /// these pointers should be deleted
+    /// \return
+    /// int, the error indicates whether or not the operation is succeeded.
+    /// this error can be converted to a string using get_error_string()
+    ///
+    virtual int get_unordered_scanners(int max_split_count,
+                                       const scan_options &options,
+                                       std::vector<pegasus_scanner *> &scanners) = 0;
+```
+
+### async_get_unordered_scanners
+异步获取一个遍历所有数据的scanner
+```
+    ///
+    /// \brief async get a bundle of scanners to iterate all k-v in table
+    ///        scannners return by callback should be deleted when all scan complete
+    /// \param max_split_count
+    /// the number of scanners returned will always <= max_split_count
+    /// \param options
+    /// which used to indicate scan options, like timeout_milliseconds
+    /// \param callback; return status and scanner in this callback
+    ///
+    virtual void
+    async_get_unordered_scanners(int max_split_count,
+                                 const scan_options &options,
+                                 async_get_unordered_scanners_callback_t &&callback) = 0;
+```
diff --git a/_clients/go-client.md b/_clients/go-client.md
new file mode 100755
index 0000000..37f7b02
--- /dev/null
+++ b/_clients/go-client.md
@@ -0,0 +1,51 @@
+---
+title: Go客户端文档
+layout: page
+show_sidebar: false
+menubar: clients_menu
+---
+
+## 项目地址
+
+<https://github.com/XiaoMi/pegasus-go-client>
+
+## 版本要求
+
+```
+>= go 1.8
+```
+
+## 安装
+
+```
+go get github.com/XiaoMi/pegasus-go-client
+```
+
+## 使用
+
+### 示例
+
+完整的示例请参考[example](https://github.com/XiaoMi/pegasus-go-client/tree/master/example)。
+
+### log配置文件
+
+go-client 提供了简单的日志接口 pegalog,用户可以使用现有的日志库来实现该接口,go-client 会经由该接口来打印内部日志。
+
+```go
+type Logger interface {
+    Fatal(args ...interface{})
+    Fatalf(format string, args ...interface{})
+    Fatalln(args ...interface{})
+    Print(args ...interface{})
+    Printf(format string, args ...interface{})
+    Println(args ...interface{})
+}
+```
+
+### 注意事项
+
+go-client 提供了两种客户端实现以应付不同的使用场景,它们使用相同的接口,但实现分别放在不同的两个包下:
+
+- **pegasus**: RPC 采用 pipeline 实现,即不等待服务端(单指某一 server,server 之间的 RPC 互不影响)发回消息响应就发送下一条消息。这种实现的好处在于吞吐大,延时小,缺点在于实现较为复杂。在我们的性能测试(使用[pingcap/go-ycsb](https://github.com/pingcap/go-ycsb))中,使用该实现的 go-client 仅用一个实例即可把拥有5个 replica-server 的服务打满([issue-4](https://github.com/XiaoMi/pegasus-go-client/issues/4))。我们**推荐使用这一实现**。
+
+- **pegasus2**: RPC 采用非 pipeline 实现,即 RPC 依次进行,只有等待上一条消息成功收到服务端的响应,才可发下一条消息。这种做法的性能较差,但有些业务青睐于它的实现简单。使用 pegasus2 的用户通常需要为一张表维护多个 `TableConnector` 才能够满足性能要求,其中每个 `TableConnector` 对每个 replica server 会维护一个 tcp 连接。我们推荐使用连接池的方式,根据请求数动态调节池中 `TableConnector` 的数量。
diff --git a/_clients/index.md b/_clients/index.md
new file mode 100644
index 0000000..8268d67
--- /dev/null
+++ b/_clients/index.md
@@ -0,0 +1,15 @@
+---
+title: 客户端库
+layout: page
+show_sidebar: false
+menubar: clients_menu
+---
+
+Pegasus目前提供以下多种客户端支持:
+
+| 编程语言 | 项目链接 |
+| Java | <https://github.com/XiaoMi/pegasus-java-client> |
+| Scala | <https://github.com/XiaoMi/pegasus-scala-client> |
+| Go | <https://github.com/XiaoMi/pegasus-go-client> |
+| Python | <https://github.com/XiaoMi/pegasus-python-client> |
+| NodeJs | <https://github.com/XiaoMi/pegasus-nodejs-client.git> |
diff --git a/_clients/java-client.md b/_clients/java-client.md
new file mode 100755
index 0000000..abc3627
--- /dev/null
+++ b/_clients/java-client.md
@@ -0,0 +1,2313 @@
+---
+title: Java客户端文档
+layout: page
+show_sidebar: false
+menubar: clients_menu
+---
+
+# 获取Java客户端
+
+项目地址:[Pegasus Java Client](https://github.com/XiaoMi/pegasus-java-client)
+
+下载:
+
+```bash
+git clone https://github.com/XiaoMi/pegasus-java-client.git
+cd pegasus-java-client
+```
+
+选择所使用的版本并构建,建议使用[最新的release版本](https://github.com/xiaomi/pegasus-java-client/releases):
+
+```bash
+git checkout 1.11.10-thrift-0.11.0-inlined-release
+mvn clean package -DskipTests
+```
+
+安装到本地的maven repository,方便在项目中使用:
+
+```bash
+mvn clean install -DskipTests
+```
+
+安装完成后,通过maven配置在项目中使用:
+
+```xml
+<dependency>
+  <groupId>com.xiaomi.infra</groupId>
+  <artifactId>pegasus-client</artifactId>
+  <version>1.11.10-thrift-0.11.0-inlined</version>
+</dependency>
+```
+
+# 客户端配置
+创建Java client实例需要配置相关参数,用户可以选择两种方式进行配置:文件配置方式和参数传递方式
+
+## 文件配置
+Java客户端需要准备配置文件,用以确定Pegasus集群的位置,以及配置默认超时时间等。
+
+配置文件一般命名为```pegasus.properties```,样例:
+
+```ini
+meta_servers = 127.0.0.1:34601,127.0.0.1:34602,127.0.0.1:34603
+operation_timeout = 1000
+```
+
+其中:  
+
+* meta_servers是必选项,表示Pegasus集群的MetaServer地址列表,用于定位集群的位置。  
+* operation_timeout是可选项,表示各操作的默认超时时间,单位毫秒,默认值为1000。其实接口中每个操作一般都可以指定单独的超时时间,当指定为0时,就会使用该默认超时时间。
+
+配置文件在创建Client实例的时候使用,需传入configPath参数:  
+
+```java
+PegasusClientInterface client = PegasusClientFactory.getSingletonClient(configPath);
+```
+
+其中configPath的格式为```type : // path```,目前type支持三种类型:  
+
+* 本地文件系统
+  * 格式:file:///path/to/config
+  * 样例1:file://./pegasus.properties (表示本地 ./pegasus.properties 文件)  
+  * 样例2:file:///home/work/pegasus.properties (表示本地 /home/work/pegasus.properties 文件)  
+* Java Resource  
+  * 格式:resource:///path/to/config
+  * 样例1:resource:///pegasus.properties  
+  * 样例2:resource:///com/xiaomi/xxx/pegasus.properties
+* Zookeeper
+  * 格式:zk://host1:port1,host2:port2,host3:port3/path/to/config
+  * 样例1:zk://127.0.0.1:2181/databases/pegasus/pegasus.properties
+  * 样例2:zk://127.0.0.1:2181,127.0.0.1:2182/databases/pegasus/pegasus.properties
+
+## 参数传递
+用户可以选择构造ClientOptions实例作为创建客户端实例的参数,ClientOptions包含下列参数:  
+* metaServers:meta_servers地址,默认:127.0.0.1:34601,127.0.0.1:34602,127.0.0.1:34603
+* operationTimeout:客户端请求的超时阈值,默认:1000ms
+* asyncWorkers:后台工作线程数,内部实际是Netty NIO处理客户端和replica_server之间RPC的线程,默认:4
+* enablePerfCounter:是否开启性能指标监控数据,如果开启则则客户端会周期性的上报监控数据,目前仅支持[Falcon](http://open-falcon.org/),默认:false
+* falconPerfCounterTags:falcon监控数据标签,默认:空
+* falconPushInterval:falcon监控数据上报间隔,默认:10s
+
+其中ClientOptions实例提供两种创建方式,你可以使用:
+```java
+ClientOptions clientOptions = ClientOptions.create()
+```
+创建默认的ClientOptions实例。否则,可以参照下列样例创建自定义的实例:
+```java
+ClientOptions clientOptions =
+      ClientOptions.builder()
+          .metaServers("127.0.0.1:34601,127.0.0.1:34602,127.0.0.1:34603")
+          .operationTimeout(Duration.ofMillis(1000))
+          .asyncWorkers(4)
+          .enablePerfCounter(false)
+          .falconPerfCounterTags("")
+          .falconPushInterval(Duration.ofSeconds(10))
+          .build();
+```
+# 接口定义
+
+Java客户端的类都在```com.xiaomi.infra.pegasus.client```包下面,主要提供了三个类:
+
+| 类名                   | 功能                                                       |
+| ---------------------- | ---------------------------------------------------------- |
+| PegasusClientFactory   | Client工厂类,用于创建Client实例                           |
+| PegasusClientInterface | Client接口类,封装了各种**同步API**,也可用于创建Table实例 |
+| PegasusTableInterface  | Table接口类,封装了存取单个Table数据的**同步和异步API**    |
+
+用户可以选择使用Client接口(PegasusClientInterface)或者是Table接(PegasusTableInterface)存取数据,区别如下:
+
+* Client接口直接在参数中指定表名,省去了打开表的动作,使用更便捷。
+* Table接口同时支持**同步和异步API**,而Client接口只支持**同步API**。
+* Table接口可以为每个操作设置单独的超时,而Client接口无法单独指定超时,只能使用配置文件中的默认超时。
+* Table接口的超时更准确,而Client接口在首次读写请求时可能需要在内部初始化Table对象,所以首次读写的超时可能不太准确。
+* 推荐用户首选Table接口。
+
+## 创建Client实例
+
+创建Client实例有两种方式:单例和非单例。
+
+### 单例
+
+如果程序中只需要访问**单个集群**,那么用单例是比较合适的,这样可以共享各种资源,譬如线程池、连接等。
+
+**注意**:如果在多个地方调用```getSingletonClient()```获取单例对象,需要保证传入的configPath或者ClientOptions对象是一致的,不然就会抛出异常,这样是为了保证多次调用获取到的是同一个实例。
+
+调用```PegasusClientFactory::getSingletonClient()```方法获取PegasusClientInterface的单例对象:
+
+```java
+/**
+  * Get the singleton client instance with default config path of "resource:///pegasus.properties".
+  * After used, should call PegasusClientFactory.closeSingletonClient() to release resource.
+  *
+  * @return PegasusClientInterface PegasusClientInterface.
+  * @throws PException throws exception if any error occurs.
+  */
+public static PegasusClientInterface getSingletonClient() throws PException;
+
+/**
+  * Get the singleton client instance with customized config path. After used, should call
+  * PegasusClientFactory.closeSingletonClient() to release resource.
+  *
+  * @param configPath configPath could be:
+  * - zookeeper path  : zk://host1:port1,host2:port2,host3:port3/path/to/config
+  * - local file path : file:///path/to/config
+  * - java resource   : resource:///path/to/config
+  *
+  * @return PegasusClientInterface PegasusClientInterface.
+  * @throws PException throws exception if any error occurs.
+  */
+public static PegasusClientInterface getSingletonClient(String configPath) throws PException;
+
+/**
+  * Get the singleton client instance instance with ClientOptions. After used, should call
+  * PegasusClientFactory.closeSingletonClient() to release resource.
+  *
+  * @param options The client option
+  * @return PegasusClientInterface PegasusClientInterface.
+  * @throws PException throws exception if any error occurs.
+  */
+public static PegasusClientInterface getSingletonClient(ClientOptions options) throws PException;
+```
+
+使用完毕后,记得close单例以释放资源,譬如:
+
+```java
+PegasusClientInterface client = PegasusClientFactory.getSingletonClient(configPath);
+
+... ...
+
+PegasusClientFactory.closeSingletonClient();
+```
+
+### 非单例
+
+如果在程序中需要访问多个集群,就不能用单例了。因此我们提供了创建普通实例的接口,创建时传入一个configPath或者ClientOptions对象,不同集群使用不同的configPath或者ClientOptions对象。
+
+**注意**:每个实例都拥有自己独立的资源,互不影响,因此要尽量避免重复创建实例,造成资源浪费,并且使用完后要记得调用close()释放资源。
+
+调用```PegasusClientFactory::createClient()```方法,获取非单例的client实例:
+
+```java
+/**
+  * Create a client instance. After used, should call client.close() to release resource.
+  *
+  * @param configPath client config path,could be:
+  * - zookeeper path  : zk://host1:port1,host2:port2,host3:port3/path/to/config
+  * - local file path : file:///path/to/config
+  * - java resource   : resource:///path/to/config
+  *
+  * @return PegasusClientInterface.
+  * @throws PException throws exception if any error occurs.
+  */
+public static PegasusClientInterface createClient(String configPath) throws PException;
+
+/**
+  * Create a client instance instance with ClientOptions. After used, should call
+  * client.close() to release resource.
+  *
+  * @param options The client option
+  * @return PegasusClientInterface.
+  * @throws PException throws exception if any error occurs.
+  */
+public static PegasusClientInterface createClient(ClientOptions options) throws PException;
+```
+
+譬如:
+```java
+PegasusClientInterface client = PegasusClientFactory.createClient(configPath);
+
+... ...
+
+client.close();
+```
+
+## PegasusClientInterface接口
+
+### get  
+读单行数据。
+```java
+/**
+ * Get value.
+ * @param tableName TableHandler name
+ * @param hashKey used to decide which partition to get this k-v,
+ *                if null or length == 0, means hash key is "".
+ * @param sortKey all the k-v under hashKey will be sorted by sortKey,
+ *                if null or length == 0, means sort key is "".
+ * @return value; null if not found
+ * @throws PException
+ */
+public byte[] get(String tableName, byte[] hashKey, byte[] sortKey) throws PException;
+```
+注:
+  * 参数:需传入TableName、HashKey、SortKey。
+  * 返回值:如果返回null,表示key对应的数据不存在
+  * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+
+### batchGet
+读取一批数据,对get函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常。如果抛出了异常,则values中的结果是未定义的。
+```java
+/**
+ * Batch get values of different keys.
+ * Will terminate immediately if any error occurs.
+ * @param tableName table name
+ * @param keys hashKey and sortKey pair list.
+ * @param values output values; should be created by caller; if succeed, the size of values will
+ *               be same with keys; the value of keys[i] is stored in values[i]; if the value of
+ *               keys[i] is not found, then values[i] will be set to null.
+ * @throws PException throws exception if any error occurs.
+ *
+ * Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+ */
+public void batchGet(String tableName, List<Pair<byte[], byte[]>> keys, List<byte[]> values) throws PException;
+```
+注:
+ * 参数:
+   * 传入参数:TableName、Keys。
+   * 传出参数:Values。该变量需由调用者创建;如果读取成功,Values[i]中存放Keys[i]对应的结果,如果value不存在则为null。
+ * 返回值:无。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,只要任意一个失败都会抛出异常。
+
+### batchGet2
+读取一批数据,对get函数的批量封装。该函数并发地向server发送异步请求,但与上面batchGet不同的是,无论请求成功还是失败,它都会等待所有请求结束。
+
+用户可以根据results中的PException是否设置来判断请求成功还是失败,并可以选择只使用成功的结果。
+```java
+/**
+* Batch get values of different keys.
+* Will wait for all requests done even if some error occurs.
+* @param tableName table name
+* @param keys hashKey and sortKey pair list.
+* @param results output results; should be created by caller; after call done, the size of results will
+*                be same with keys; the results[i] is a Pair:
+*                - if Pair.left != null : means query keys[i] failed, Pair.left is the exception.
+*                - if Pair.left == null : means query keys[i] succeed, Pair.right is the result value.
+* @return succeed count.
+* @throws PException
+*
+* Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+*/
+public int batchGet2(String tableName, List<Pair<byte[], byte[]>> keys, List<Pair<PException, byte[]>> results) throws PException;
+```
+注:
+ * 参数:
+   * 传入参数:TableName、Keys。
+   * 传出参数:Results。该变量需由调用者创建;Results[i]中存放Keys[i]对应的结果;如果Results[i].left不为null(PException已设置),表示对Keys[i]的请求失败。
+ * 返回值:请求成功的个数。
+ * 异常:如果出现异常,譬如参数错误、表名不存在等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,用户可以选择只使用成功的结果。
+
+### multiGet
+读同一HashKey下的多行数据。
+```java
+/**
+ * Get multiple value under the same hash key.
+ * @param tableName table name
+ * @param hashKey used to decide which partition to put this k-v,
+ *                should not be null or empty.
+ * @param sortKeys all the k-v under hashKey will be sorted by sortKey,
+ *                if null or empty, means fetch all sortKeys under the hashKey.
+ * @param maxFetchCount max count of k-v pairs to be fetched.
+ *                      max_fetch_count <= 0 means no limit. default value is 100.
+ * @param maxFetchSize max size of k-v pairs to be fetched.
+ *                     max_fetch_size <= 0 means no limit. default value is 1000000.
+ * @param values output values; if sortKey is not found, then it will not appear in values.
+ *               the returned sortKey is just the same one in incoming sortKeys.
+ * @return true if all data is fetched; false if only partial data is fetched.
+ * @throws PException
+ */
+public boolean multiGet(String tableName, byte[] hashKey, List<byte[]> sortKeys, int maxFetchCount, int maxFetchSize, List<Pair<byte[], byte[]>> values) throws PException;
+public boolean multiGet(String tableName, byte[] hashKey, List<byte[]> sortKeys, List<Pair<byte[], byte[]>> values) throws PException;
+```
+注:
+ * 提供了两个版本的接口,其中第一个接口可以指定maxFetchCount和maxFetchSize。
+ * 参数:
+   * 传入参数:需传入TableName、HashKey、SortKeys;选择性传入maxFetchCount、maxFetchSize。
+   * 传出参数:数据通过values传出,values由用户在调用前new出来。
+   * SortKeys如果非空,则只读取指定的数据;SortKeys如果为空,则表示读取该HashKey下的所有数据。
+   * maxFetchCount和maxFetchSize用于限制读取的数据量,maxFetchCount表示最多读取的数据条数,maxFetchSize表示最多读取的数据字节数,两者任一达到限制就停止读取。
+ * 返回值:如果用户指定了maxFetchCount或者maxFetchSize,单次查询可能只获取到部分结果。如果所有满足条件的数据都已经获取到,则返回true;否则返回false。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+
+multiGet还有另外一个版本的接口,可以支持SortKey的**范围查询**和**条件过滤**,只读取满足特定条件的数据。并且从1.8.0开始在MultiGetOptions中增加了reverse参数,支持**逆向扫描**数据。
+```java
+public enum FilterType {
+    FT_NO_FILTER(0),
+    FT_MATCH_ANYWHERE(1), // match filter string at any position
+    FT_MATCH_PREFIX(2),   // match filter string at prefix
+    FT_MATCH_POSTFIX(3);  // match filter string at postfix
+}
+ 
+public class MultiGetOptions {
+    public boolean startInclusive = true; // if the startSortKey is included
+    public boolean stopInclusive = false; // if the stopSortKey is included
+    public FilterType sortKeyFilterType = FilterType.FT_NO_FILTER; // filter type for sort key
+    public byte[] sortKeyFilterPattern = null; // filter pattern for sort key
+    public boolean noValue = false; // only fetch hash_key and sort_key, but not fetch value
+    public boolean reverse = false; // if search in reverse direction
+}
+ 
+/**
+* Get multiple key-values under the same hashKey with sortKey range limited.
+* @param tableName table name
+* @param hashKey used to decide which partition the key may exist
+*                should not be null or empty.
+* @param startSortKey the start sort key.
+*                     null means "".
+* @param stopSortKey the stop sort key.
+*                    null or "" means fetch to the last sort key.
+* @param options multi-get options.
+* @param maxFetchCount max count of kv pairs to be fetched
+*                      maxFetchCount <= 0 means no limit. default value is 100
+* @param maxFetchSize max size of kv pairs to be fetched.
+*                     maxFetchSize <= 0 means no limit. default value is 1000000.
+* @param values output values; if sortKey is not found, then it will not appear in values.
+*               the returned sortKey is just the same one in incoming sortKeys.
+* @return true if all data is fetched; false if only partial data is fetched.
+* @throws PException
+*/
+public boolean multiGet(String tableName, byte[] hashKey,
+                    byte[] startSortKey, byte[] stopSortKey, MultiGetOptions options,
+                    int maxFetchCount, int maxFetchSize,
+                    List<Pair<byte[], byte[]>> values) throws PException;
+public boolean multiGet(String tableName, byte[] hashKey,
+                    byte[] startSortKey, byte[] stopSortKey, MultiGetOptions options,
+                    List<Pair<byte[], byte[]>> values) throws PException;
+```
+注:
+  * 提供了两个版本的接口,其中第一个接口可以指定maxFetchCount和maxFetchSize。
+  * 参数:
+    * 传入参数:需传入TableName、HashKey、StartSortKey、StopSortKey、MultiGetOptions;选择性传入maxFetchCount、maxFetchSize。
+    * 传出参数:数据通过values传出,values由用户在调用前new出来。
+    * StopSortKeys如果为空,不论stopInclusive为何值,都会读到该HashKey的SortKey结束。
+    * maxFetchCount和maxFetchSize用于限制读取的数据量,maxFetchCount表示最多读取的数据条数,maxFetchSize表示最多读取的数据字节数,两者任一达到限制就停止读取。
+    * MultiGetOptions说明:
+      * startInclusive:是否包含StartSortKey,默认为true。
+      * stopInclusive:是否包含StopSortKey,默认为false。
+      * sortKeyFilterType:SortKey的过滤类型,包括无过滤、任意位置匹配、前缀匹配和后缀匹配,默认无过滤。
+      * sortKeyFilterPattern:SortKey的过滤模式串,空串相当于无过滤。
+      * noValue:只返回HashKey和SortKey,不返回Value数据,默认为false。
+      * reverse:是否逆向扫描数据库,从后往前查找数据。但是查找得到的结果在list中还是按照SortKey从小到大顺序存放。从Pegasus Server 1.8.0时开始支持。
+    * 返回值:如果读取了所有满足条件的数据,返回true;如果只读取了部分满足条件的数据,返回false。
+    * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+    * 示例:获取某个HashKey下的所有数据(注意如果数据条数太多容易超时)
+      * multiGet(hashKey, null, null, new MultiGetOptions(), -1, -1, values);
+
+### batchMultiGet
+对multiGet函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常。如果抛出了异常,则values中的结果是未定义的。
+```java
+/**
+* Batch get multiple values under the same hash key.
+* Will terminate immediately if any error occurs.
+* @param tableName table name
+* @param keys List{hashKey,List{sortKey}}; if List{sortKey} is null or empty, means fetch all
+*             sortKeys under the hashKey.
+* @param values output values; should be created by caller; if succeed, the size of values will
+*               be same with keys; the data for keys[i] is stored in values[i].
+* @throws PException throws exception if any error occurs.
+*
+* Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+*/
+public void batchMultiGet(String tableName, List<Pair<byte[], List<byte[]>>> keys, List<HashKeyData> values) throws PException;
+```
+注:
+ * 参数:
+   * 传入参数:TableName、Keys。Keys是一个Pair列表,Pair的左值是hashKey,右值是sortKey列表;如果Pair的右值为null或者空列表,则获取该hashKey下的所有数据。
+   * 传出参数:Values。该List需由调用者创建;如果读取成功,Values[i]中存放Keys[i]对应的结果。
+ * 返回值:无。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,只要任意一个失败都会抛出异常。
+
+### batchMultiGet2
+对multiGet函数的批量封装。该函数并发地向server发送异步请求,并等待结果。但与上面batchMultiGet不同的是,无论请求成功还是失败,它都会等待所有请求结束。
+
+用户可以根据results中的PException是否设置来判断请求成功还是失败,并可以选择只使用成功的结果。
+```java
+/**
+* Batch get multiple values under the same hash key.
+* Will wait for all requests done even if some error occurs.
+* @param tableName table name
+* @param keys List{hashKey,List{sortKey}}; if List{sortKey} is null or empty, means fetch all
+*             sortKeys under the hashKey.
+* @param results output results; should be created by caller; after call done, the size of results will
+*                be same with keys; the results[i] is a Pair:
+*                - if Pair.left != null : means query keys[i] failed, Pair.left is the exception.
+*                - if Pair.left == null : means query keys[i] succeed, Pair.right is the result value.
+* @return succeed count.
+* @throws PException
+*
+* Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+*/
+public int batchMultiGet2(String tableName, List<Pair<byte[], List<byte[]>>> keys, List<Pair<PException, HashKeyData>> results) throws PException;
+```
+注:
+ * 参数:
+   * 传入参数:TableName、Keys。Keys是一个Pair列表,Pair的左值是hashKey,右值是sortKey列表;如果Pair的右值为null或者空列表,则获取该hashKey下的所有数据。
+   * 传出参数:Results。该变量需由调用者创建;Results[i]中存放Keys[i]对应的结果;如果Results[i].left不为null(PException已设置),表示对Keys[i]的请求失败。
+ * 返回值:请求成功的个数。
+ * 异常:如果出现异常,譬如参数错误、表名不存在等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,用户可以选择只使用成功的结果。
+
+### set
+写单行数据。
+```java
+/**
+ * Set value.
+ * @param tableName TableHandler name
+ * @param hashKey used to decide which partition to put this k-v,
+ *                if null or length == 0, means hash key is "".
+ * @param sortKey all the k-v under hashKey will be sorted by sortKey,
+ *                if null or length == 0, means sort key is "".
+ * @param value should not be null
+ * @param ttl_seconds time to live in seconds,
+ *                    0 means no ttl. default value is 0.
+ * @throws PException
+ */
+public void set(String tableName, byte[] hashKey, byte[] sortKey, byte[] value, int ttl_seconds) throws PException;
+public void set(String tableName, byte[] hashKey, byte[] sortKey, byte[] value) throws PException;
+```
+注:
+ * 提供了两个版本的接口,其中第一个接口可以指定TTL时间。
+ * 参数:需传入TableName、HashKey、SortKey、value;选择性传入TTL,TTL必须>=0, 当<0时会抛出PException异常。
+ * 返回值:无。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误、TTL<0等,会抛出 PException。
+
+### batchSet
+写一批数据,对set函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常。
+```java
+/**
+ * Batch set lots of values.
+ * @param tableName TableHandler name
+ * @param items list of items.
+ * @throws PException throws exception if any error occurs.
+ *
+ * Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+ */
+public void batchSet(String tableName, List<SetItem> items) throws PException;
+```
+注:
+ * 参数:需传入TableName、Items。
+ * 返回值:无。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,只要任意一个失败都会抛出异常。
+
+### batchSet2
+对set函数的批量封装。该函数并发地向server发送异步请求,并等待结果。但与上面batchSet不同的是,无论请求成功还是失败,它都会等待所有请求结束。
+
+用户可以根据results中的PException是否设置来判断请求成功还是失败,并可以选择只使用成功的结果。
+```java
+/**
+* Batch set lots of values.
+* Will wait for all requests done even if some error occurs.
+* @param tableName table name
+* @param items list of items.
+* @param results output results; should be created by caller; after call done, the size of results will
+*                be same with items; the results[i] is a PException:
+*                - if results[i] != null : means set items[i] failed, results[i] is the exception.
+*                - if results[i] == null : means set items[i] succeed.
+* @return succeed count.
+* @throws PException
+*
+* Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+*/
+public int batchSet2(String tableName, List<SetItem> items, List<PException> results) throws PException;
+```
+注:
+ * 参数:
+   * 传入参数:TableName、Items。
+   * 传出参数:Results。该变量需由调用者创建;Results[i]中存放Items[i]对应的结果;如果Results[i]不为null(PException已设置),表示对Items[i]的请求失败。
+ * 返回值:请求成功的个数。
+ * 异常:如果出现异常,譬如参数错误、表名不存在等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,用户可以选择只使用成功的结果。
+
+### multiSet
+写同一HashKey下的多行数据。
+```java
+/**
+ * Set multiple value under the same hash key.
+ * @param tableName table name
+ * @param hashKey used to decide which partition to put this k-v,
+ *                should not be null or empty.
+ * @param values all <sortkey,value> pairs to be set,
+ *               should not be null or empty.
+ * @param ttl_seconds time to live in seconds,
+ *                    0 means no ttl. default value is 0.
+ * @throws PException
+ */
+public void multiSet(String tableName, byte[] hashKey, List<Pair<byte[], byte[]>> values, int ttl_seconds) throws PException;
+public void multiSet(String tableName, byte[] hashKey, List<Pair<byte[], byte[]>> values) throws PException;
+```
+注:
+ * 提供了两个版本的接口,其中第一个接口可以指定TTL时间。
+ * 参数:需传入TableName、HashKey、Values;选择性传入TTL,TTL必须>=0, 当<0时会抛出PException异常。
+   * Values是Pair列表,Pair的第一个元素是SortKey,第二个元素为value。
+ * 返回值:无。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误、TTL<0等,会抛出 PException。
+
+### batchMultiSet
+对multiSet函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常。
+```java
+/**
+* Batch set multiple value under the same hash key.
+* Will terminate immediately if any error occurs.
+* @param tableName TableHandler name
+* @param items list of items.
+* @param ttl_seconds time to live in seconds,
+*                    0 means no ttl. default value is 0.
+* @throws PException throws exception if any error occurs.
+*
+* Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+*/
+public void batchMultiSet(String tableName, List<HashKeyData> items, int ttl_seconds) throws PException;
+public void batchMultiSet(String tableName, List<HashKeyData> items) throws PException;
+```
+注:
+ * 提供了两个版本的接口,其中第一个接口可以指定TTL时间。
+ * 参数:需传入TableName、Items;选择性传入TTL,TTL必须>=0, 当<0时会抛出PException异常。
+ * 返回值:无。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误、TTL<0等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,只要任意一个失败都会抛出异常。
+
+### batchMultiSet2
+对multiSet函数的批量封装。该函数并发地向server发送异步请求,并等待结果。但与上面batchMultiSet不同的是,无论请求成功还是失败,它都会等待所有请求结束。
+```java
+/**
+* Batch set multiple value under the same hash key.
+* Will wait for all requests done even if some error occurs.
+* @param tableName table name
+* @param items list of items.
+* @param ttl_seconds time to live in seconds,
+*                    0 means no ttl. default value is 0.
+* @param results output results; should be created by caller; after call done, the size of results will
+*                be same with items; the results[i] is a PException:
+*                - if results[i] != null : means set items[i] failed, results[i] is the exception.
+*                - if results[i] == null : means set items[i] succeed.
+* @return succeed count.
+* @throws PException
+*
+* Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+*/
+public int batchMultiSet2(String tableName, List<HashKeyData> items, int ttl_seconds, List<PException> results) throws PException;
+public int batchMultiSet2(String tableName, List<HashKeyData> items, List<PException> results) throws PException;
+```
+注:
+ * 提供了两个版本的接口,其中第一个接口可以指定TTL时间。
+ * 参数:
+   * 传入参数:TableName、Items;选择性传入TTL,TTL必须>=0, 当<0时会抛出PException异常。
+   * 传出参数:Results。该变量需由调用者创建;Results[i]中存放Items[i]对应的结果;如果Results[i]不为null(PException已设置),表示对Items[i]的请求失败。
+ * 返回值:请求成功的个数。
+ * 异常:如果出现异常,譬如参数错误、表名不存在、TTL<0等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,用户可以选择只使用成功的结果。
+
+### del
+删单行数据。
+```java
+/**
+ * Delete value.
+ * @param tableName TableHandler name
+ * @param hashKey used to decide which partition to put this k-v,
+ *                if null or length == 0, means hash key is "".
+ * @param sortKey all the k-v under hashKey will be sorted by sortKey,
+ *                if null or length == 0, means sort key is "".
+ * @throws PException
+ */
+public void del(String tableName, byte[] hashKey, byte[] sortKey) throws PException;
+```
+注:
+ * 参数:需传入TableName、HashKey、SortKey。
+ * 返回值:无。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+
+### batchDel
+删除一批数据,对del函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常。
+```java
+/**
+* Batch delete values of different keys.
+* Will terminate immediately if any error occurs.
+* @param tableName table name
+* @param keys hashKey and sortKey pair list.
+* @throws PException throws exception if any error occurs.
+*
+* Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+*/
+public void batchDel(String tableName, List<Pair<byte[], byte[]>> keys) throws PException;
+```
+注:
+ * 参数:需传入TableName、Keys。
+ * 返回值:无。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,只要任意一个失败都会抛出异常。
+
+### batchDel2
+对del函数的批量封装。该函数并发地向server发送异步请求,并等待结果。但与上面batchDel不同的是,无论请求成功还是失败,它都会等待所有请求结束。
+
+用户可以根据results中的PException是否设置来判断请求成功还是失败,并可以选择只使用成功的结果。
+```java
+/**
+* Batch delete values of different keys.
+* Will wait for all requests done even if some error occurs.
+* @param tableName table name
+* @param keys hashKey and sortKey pair list.
+* @param results output results; should be created by caller; after call done, the size of results will
+*                be same with keys; the results[i] is a PException:
+*                - if results[i] != null : means del keys[i] failed, results[i] is the exception.
+*                - if results[i] == null : means del keys[i] succeed.
+* @return succeed count.
+* @throws PException
+*
+* Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+*/
+public int batchDel2(String tableName, List<Pair<byte[], byte[]>> keys, List<PException> results) throws PException;
+```
+注:
+ * 参数:
+   * 传入参数:TableName、Keys。
+   * 传出参数:Results。该变量需由调用者创建;Results[i]中存放Keys[i]对应的结果;如果Results[i]不为null(PException已设置),表示对Keys[i]的请求失败。
+ * 返回值:请求成功的个数。
+ * 异常:如果出现异常,譬如参数错误、表名不存在等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,用户可以选择只使用成功的结果。
+
+### multiDel
+删同一HashKey下的多行数据。
+```java
+/**
+ * Delete specified sort keys under the same hash key.
+ * @param tableName table name
+ * @param hashKey used to decide which partition to put this k-v,
+ *                should not be null or empty.
+ * @param sortKeys specify sort keys to be deleted.
+ *                 should not be empty.
+ * @throws PException
+ */
+public void multiDel(String tableName, byte[] hashKey, List<byte[]> sortKeys) throws PException;
+```
+注:
+ * 参数:需传入TableName、HashKey、SortKeys。
+   * SortKeys不允许为空,如果不知道该HashKey下面有哪些SortKey,可以通过下面的multiGetSortKeys方法获取。
+ * 返回值:无。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+
+### batchMultiDel
+对multiDel函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常。
+```java
+/**
+* Batch delete specified sort keys under the same hash key.
+* Will terminate immediately if any error occurs.
+* @param tableName table name
+* @param keys List{hashKey,List{sortKey}}
+* @throws PException throws exception if any error occurs.
+*
+* Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+*/
+public void batchMultiDel(String tableName, List<Pair<byte[], List<byte[]>>> keys) throws PException;
+```
+注:
+ * 参数:需传入TableName、Keys。Keys是一个Pair列表,Pair的左值是hashKey,右值是非空的sortKey列表。
+ * 返回值:无。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,只要任意一个失败都会抛出异常。
+
+### batchMultiDel2
+对del函数的批量封装。该函数并发地向server发送异步请求,并等待结果。但与上面batchMultiDel不同的是,无论请求成功还是失败,它都会等待所有请求结束。
+
+用户可以根据results中的PException是否设置来判断请求成功还是失败,并可以选择只使用成功的结果。
+```java
+/**
+* Batch delete specified sort keys under the same hash key.
+* Will wait for all requests done even if some error occurs.
+* @param tableName table name
+* @param keys List{hashKey,List{sortKey}}
+* @param results output results; should be created by caller; after call done, the size of results will
+*                be same with keys; the results[i] is a PException:
+*                - if results[i] != null : means del keys[i] failed, results[i] is the exception.
+*                - if results[i] == null : means del keys[i] succeed.
+* @return succeed count.
+* @throws PException
+*
+* Notice: the method is not atomic, that means, maybe some keys succeed but some keys failed.
+*/
+public int batchMultiDel2(String tableName, List<Pair<byte[], List<byte[]>>> keys, List<PException> results) throws PException;
+```
+注:
+ * 参数:
+   * 传入参数:TableName、Keys。Keys是一个Pair列表,Pair的左值是hashKey,右值是非空的sortKey列表。
+   * 传出参数:Results。该变量需由调用者创建;Results[i]中存放Keys[i]对应的结果;如果Results[i]不为null(PException已设置),表示对Keys[i]的请求失败。
+ * 返回值:请求成功的个数。
+ * 异常:如果出现异常,譬如参数错误、表名不存在等,会抛出 PException。
+ * 注意:该方法不是原子的,有可能出现部分成功部分失败的情况,用户可以选择只使用成功的结果。
+
+### delRange
+删除同一HashKey下,SortKey值在startSortKey和stopSortKey范围内的数据。该函数首先从server获取当前在该范围内存在SortKey集合, 再分批次删除(100个SortKey作为一次, 最后不足100的作为一次)。删除过程中若发生错误,不影响已经删除的数据,同时会标记该范围内未删除的第一个SortKey。
+```java
+/**
+   * Delete key-values within range of startSortKey and stopSortKey under hashKey. Will terminate
+   * immediately if any error occurs.
+   *
+   * @param tableName table name
+   * @param hashKey used to decide which partition the key may exist, should not be null or empty.
+   * @param startSortKey the start sort key. null or "" means fetch to the first sort key.
+   * @param stopSortKey the stop sort key. null or "" means fetch to the last sort key.
+   * @param options del range options.
+   * @throws PException throws exception if any error occurs.
+   */
+  public void delRange(String tableName, byte[] hashKey, byte[] startSortKey, byte[] stopSortKey,DelRangeOptions options) throws PException;
+```
+注:
+* 参数:
+  * 传入参数:startSortKey和stopSortKey是sortkey的起止key值,你可以通过`options`配置更多的选项,如是否包含起止的sortkey值。
+  * 传出参数:无。
+* 返回值:无。
+* 异常:如果出现异常,譬如参数错误、表名不存在、超时等,会抛出 PException。
+* 注意:该方法不是原子的,有可能出现部分成功部分失败的情况。其中`DelRangeOptions`中包含参数`nextSortkey`,用于标记该范围内未删除的第一个SortKey值,当删除过程出现错误时,用户可以使用该参数继续接下来的操作。
+
+### incr
+单行原子增(减)操作。详细说明参见[单行原子操作](/api/single-atomic#原子增减)。
+
+该操作先将key所指向的value的字节串转换为int64类型(实现上类似于Java的[Long.parseLong()](https://docs.oracle.com/javase/7/docs/api/java/lang/Long.html#parseLong(java.lang.String))函数),然后加上increment,将结果转换为字节串设置为新值。
+
+当参数increment为正数时,即原子加;当参数increment为负数时,即原子减。
+
+```java
+/**
+ * Atomically increment value.
+ *
+ * @param tableName the table name.
+ * @param hashKey   the hash key to increment.
+ * @param sortKey   the sort key to increment.
+ * @param increment the increment to be added to the old value.
+ * @return the new value.
+ * @throws PException throws exception if any error occurs.
+ */
+public long incr(String tableName, byte[] hashKey, byte[] sortKey, long increment) throws PException;
+```
+注:
+ * 参数:需传入TableName、HashKey、SortKey、Increment。
+ * 返回值:操作成功后的新值。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。另外以下情况也会抛出异常:
+   * 旧值转换为int64时出错,譬如不是合法的数字或者超出int64范围。
+   * 旧值加上increment后的结果超出int64范围。
+ * 其他说明:
+   * 如果旧值不存在,则把旧值当做0处理,即新值等于increment。
+   * TTL语义:如果旧值存在,新值的TTL和旧值保持一致;如果旧值不存在,新值将不设TTL。
+
+从[Pegasus Server v1.11.1版本](https://github.com/XiaoMi/pegasus/releases/tag/v1.11.1)开始支持在incr操作时修改TTL,需使用[Pegasus Java Client 1.11.2-thrift-0.11.0-inlined-release](https://github.com/XiaoMi/pegasus-java-client/releases/tag/1.11.2-thrift-0.11.0-inlined-release)及以上版本来使用这个功能。
+```
+/**
+ * Atomically increment value.
+ *
+ * @param tableName the table name.
+ * @param hashKey   the hash key to increment.
+ * @param sortKey   the sort key to increment.
+ * @param increment the increment to be added to the old value.
+ * @param ttlSeconds time to live in seconds for the new value.
+ *                   should be no less than -1. for the second method, the ttlSeconds is 0.
+ *                   - if ttlSeconds == 0, the semantic is the same as redis:
+ *                     - normally, increment will preserve the original ttl.
+ *                     - if old data is expired by ttl, then set initial value to 0 and set no ttl.
+ *                   - if ttlSeconds > 0, then update with the new ttl if increment succeed.
+ *                   - if ttlSeconds == -1, then update to no ttl if increment succeed.
+ * @return the new value.
+ * @throws PException throws exception if any error occurs.
+ */
+public long incr(String tableName, byte[] hashKey, byte[] sortKey, long increment, int ttlSeconds) throws PException;
+public long incr(String tableName, byte[] hashKey, byte[] sortKey, long increment) throws PException;
+```
+注:
+ * 除了TTL之外,其他语义都与前面相同。
+ * TTL操作说明:
+   * 如果参数ttlSeconds == 0,则和redis语义保持一致:如果旧值存在,新值的TTL和旧值保持一致;如果旧值不存在,新值将不设TTL。
+   * 如果参数ttlSeconds > 0,则将TTL设置为新值。
+   * 如果参数ttlSeconds == -1,则清理掉TTL,即新值不再设置TTL。
+   * 如果参数ttlSeconds < -1,则抛出异常。
+
+### checkAndSet
+单HashKey数据的原子CAS操作(可以理解为**单行原子操作**)。详细说明参见[单行原子操作](/api/single-atomic#cas操作)。
+
+该操作先对某个SortKey(称之为CheckSortKey)的value做条件检查:
+  * 如果检查的条件满足,则将另一个SortKey(称之为SetSortKey)的value设置为新值。
+  * 如果检查的条件不满足,则不执行set操作。
+
+CheckSortKey和SetSortKey可以相同也可以不同。
+
+用户还可以设置`CheckAndSetOptions.returnCheckValue`来获取CheckSortKey对应的value。如果CheckSortKey和SetSortKey相同并且set成功,则获取set之前的旧值。
+
+```java
+public enum CheckType {
+    CT_NO_CHECK(0),
+
+    // appearance
+    CT_VALUE_NOT_EXIST(1),          // value is not exist
+    CT_VALUE_NOT_EXIST_OR_EMPTY(2), // value is not exist or value is empty
+    CT_VALUE_EXIST(3),              // value is exist
+    CT_VALUE_NOT_EMPTY(4),          // value is exist and not empty
+
+    // match
+    CT_VALUE_MATCH_ANYWHERE(5), // operand matches anywhere in value
+    CT_VALUE_MATCH_PREFIX(6),   // operand matches prefix in value
+    CT_VALUE_MATCH_POSTFIX(7),  // operand matches postfix in value
+
+    // bytes compare
+    CT_VALUE_BYTES_LESS(8),              // bytes compare: value < operand
+    CT_VALUE_BYTES_LESS_OR_EQUAL(9),     // bytes compare: value <= operand
+    CT_VALUE_BYTES_EQUAL(10),            // bytes compare: value == operand
+    CT_VALUE_BYTES_GREATER_OR_EQUAL(11), // bytes compare: value >= operand
+    CT_VALUE_BYTES_GREATER(12),          // bytes compare: value > operand
+
+    // int compare: first transfer bytes to int64; then compare by int value
+    CT_VALUE_INT_LESS(13),             // int compare: value < operand
+    CT_VALUE_INT_LESS_OR_EQUAL(14),    // int compare: value <= operand
+    CT_VALUE_INT_EQUAL(15),            // int compare: value == operand
+    CT_VALUE_INT_GREATER_OR_EQUAL(16), // int compare: value >= operand
+    CT_VALUE_INT_GREATER(17);          // int compare: value > operand
+}
+
+public class CheckAndSetOptions {
+    public int setValueTTLSeconds = 0; // time to live in seconds of the set value, 0 means no ttl.
+    public boolean returnCheckValue = false; // if return the check value in results.
+}
+
+public class CheckAndSetResult {
+    /**
+     * return value for checkAndSet
+     *
+     * @param setSucceed true if set value succeed.
+     * @param checkValueReturned true if the check value is returned.
+     * @param checkValueExist true if the check value is exist; can be used only when checkValueReturned is true.
+     * @param checkValue return the check value if exist; can be used only when checkValueExist is true.
+     */
+    boolean setSucceed;
+    boolean checkValueReturned;
+    boolean checkValueExist;
+    byte[] checkValue;
+}
+
+/**
+ * Atomically check and set value by key.
+ * If the check condition is satisfied, then apply to set value.
+ *
+ * @param tableName    the table name.
+ * @param hashKey      the hash key to check and set.
+ * @param checkSortKey the sort key to check.
+ * @param checkType    the check type.
+ * @param checkOperand the check operand.
+ * @param setSortKey   the sort key to set value if check condition is satisfied.
+ * @param setValue     the value to set if check condition is satisfied.
+ * @param options      the check-and-set options.
+ * @return CheckAndSetResult
+ * @throws PException throws exception if any error occurs.
+ */
+public PegasusTableInterface.CheckAndSetResult checkAndSet(String tableName, byte[] hashKey, byte[] checkSortKey,
+                                                           CheckType checkType, byte[] checkOperand,
+                                                           byte[] setSortKey, byte[] setValue,
+                                                           CheckAndSetOptions options) throws PException;
+```
+注:
+ * 参数:需传入TableName、HashKey、CheckSortKey、CheckType、CheckOperand、SetSortKey、SetValue、Options。
+   * checkSortKey、checkType、checkOperand:用于指定检查的条件。
+   * setSortKey、setValue:用于指定条件检查成功后要set的新值。
+   * options:其他选项,包括:
+     * setValueTTLSeconds:新值的TTL时间;TTL必须>=0,0表示不设置TTL限制,当<0时将抛出PException异常。
+     * returnCheckValue:是否需要返回CheckSortKey对应的value。
+ * 返回值:CheckAndSetResult,包括:
+   * setSucceed:是否set成功。
+   * checkValueReturned:是否返回了CheckSortKey对应的value。
+   * checkValueExist:CheckSortKey对应的value是否存在;该域只有在`checkValueReturned=true`时有意义。
+   * checkValue:CheckSortKey对应的value值;该域只有在`checkValueExist=true`时有意义。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误、TTL<0等,会抛出 PException。另外以下情况也会抛出异常:
+   * 如果CheckType为`int compare`类型的操作,且CheckOperand或者CheckValue转换为int64时出错,譬如不是合法的数字或者超出int64范围。
+
+### checkAndMutate
+checkAndMutate是[checkAndSet](#checkandset)的扩展版本:checkAndSet只允许set一个值,而checkAndMutate允许在单个原子操作中set或者del多个值。该接口从[Pegasus Java Client 1.11.0-thrift-0.11.0-inlined-release](https://github.com/XiaoMi/pegasus-java-client/releases/tag/1.11.0-thrift-0.11.0-inlined-release)版本开始提供。
+
+为此,我们提供了一个包装类[Mutations](https://github.com/XiaoMi/pegasus-java-client/blob/thrift-0.11.0-inlined/src/main/java/com/xiaomi/infra/pegasus/client/Mutations.java),用户可以预先设置需要实施的set或者del操作。
+
+```java
+class CheckAndMutateResult {
+/**
+ * return value for checkAndMutate
+ *
+ * @param mutateSucceed true if mutate succeed.
+ * @param checkValueReturned true if the check value is returned.
+ * @param checkValueExist true if the check value is exist; can be used only when
+ *     checkValueReturned is true.
+ * @param checkValue return the check value if exist; can be used only when checkValueExist is
+ *     true.
+ */
+public boolean mutateSucceed;
+
+public boolean checkValueReturned;
+public boolean checkValueExist;
+public byte[] checkValue;
+}
+
+/**
+* atomically check and mutate by key, async version. if the check condition is satisfied, then
+* apply to mutate.
+*
+* @param hashKey the hash key to check and mutate.
+* @param checkSortKey the sort key to check.
+* @param checkType the check type.
+* @param checkOperand the check operand.
+* @param mutations the list of mutations to perform if check condition is satisfied.
+* @param options the check-and-mutate options.
+* @param timeout how long will the operation timeout in milliseconds. if timeout > 0, it is a
+*     timeout value for current op, else the timeout value in the configuration file will be
+*     used.
+* @return the future for current op
+*     <p>Future return: On success: return CheckAndMutateResult. On failure: a throwable, which
+*     is an instance of PException
+*     <p>Thread safety: All the listeners for the same table are guaranteed to be dispatched in
+*     the same thread, so all the listeners for the same future are guaranteed to be executed as
+*     the same order as the listeners added. But listeners for different tables are not
+*     guaranteed to be dispatched in the same thread.
+*/
+Future<CheckAndMutateResult> asyncCheckAndMutate(
+  byte[] hashKey,
+  byte[] checkSortKey,
+  CheckType checkType,
+  byte[] checkOperand,
+  Mutations mutations,
+  CheckAndMutateOptions options,
+  int timeout /*ms*/);
+```
+注:
+ * 参数:需传入TableName、HashKey、CheckSortKey、CheckType、CheckOperand、Mutations、Options。
+   * checkSortKey、checkType、checkOperand:用于指定检查的条件。
+   * mutations:用于指定条件检查成功后要实施的set或者del操作。
+   * options:其他选项,包括:
+     * returnCheckValue:是否需要返回CheckSortKey对应的value。
+ * 返回值:CheckAndMutateResult,包括:
+   * mutateSucceed:是否实施成功。
+   * checkValueReturned:是否返回了CheckSortKey对应的value。
+   * checkValueExist:CheckSortKey对应的value是否存在;该域只有在`checkValueReturned=true`时有意义。
+   * checkValue:CheckSortKey对应的value值;该域只有在`checkValueExist=true`时有意义。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。另外以下情况也会抛出异常:
+   * 如果CheckType为`int compare`类型的操作,且CheckOperand或者CheckValue转换为int64时出错,譬如不是合法的数字或者超出int64范围。
+
+### compareExchange
+compareExchange是[checkAndSet](#checkandset)的特化版本:
+  * CheckSortKey和SetSortKey相同。
+  * CheckType为CT_VALUE_BYTES_EQUAL。
+
+该方法语义就是:如果SortKey对应的value存在且等于期望的值,则将其设置为新值。详细说明参见[单行原子操作](/api/single-atomic#cas操作)。
+
+该方法与C++库中常见的[atomic_compare_exchange](https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange)语义基本保持一致。
+
+```java
+public static class CompareExchangeResult {
+    /**
+     * return value for CompareExchange
+     *
+     * @param setSucceed true if set value succeed.
+     * @param actualValue return the actual value if set value failed; null means the actual value is not exist.
+     */
+    boolean setSucceed;
+    byte[] actualValue;
+}
+
+/**
+ * Atomically compare and exchange value by key.
+ * <p>
+ * - if the original value for the key is equal to the expected value, then update it with the desired value,
+ *   set CompareExchangeResult.setSucceed to true, and set CompareExchangeResult.actualValue to null because
+ *   the actual value must be equal to the desired value.
+ * - if the original value for the key is not exist or not equal to the expected value, then set
+ *   CompareExchangeResult.setSucceed to false, and set the actual value in CompareExchangeResult.actualValue.
+ * <p>
+ * This method is very like the C++ function in {https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange}.
+ *
+ * @param tableName     the table name.
+ * @param hashKey       the hash key to compare and exchange.
+ * @param sortKey       the sort key to compare and exchange.
+ * @param expectedValue the value expected to be found for the key.
+ * @param desiredValue  the desired value to set if the original value for the key is equal to the expected value.
+ * @param ttlSeconds    time to live in seconds of the desired value, 0 means no ttl.
+ * @return CompareExchangeResult
+ * @throws PException throws exception if any error occurs.
+ */
+public PegasusTableInterface.CompareExchangeResult compareExchange(String tableName, byte[] hashKey, byte[] sortKey,
+                                                                   byte[] expectedValue, byte[] desiredValue,
+                                                                   int ttlSeconds) throws PException;
+```
+注:
+ * 参数:需传入TableName、HashKey、SortKey、ExpectedValue、DesiredValue、ttlSeconds。
+   * hashKey、sortKey:用于指定数据的key。
+   * expectedValue:期望的旧值。
+   * desiredValue:如果旧值等于expectedValue,需要设置的新值。
+   * ttlSeconds:新值的TTL时间;TTL必须>=0,0表示不设置TTL限制,当TTL<0时将抛出PException异常。
+ * 返回值:CompareExchangeResult,包括:
+   * setSucceed:是否set成功,如果旧数据不存在,则set失败。
+   * actualValue:如果set失败,返回该value的实际值;null表示不存在。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误、TTL<0等,会抛出 PException。
+
+### ttl
+获取单行数据的TTL时间。TTL表示Time To Live,表示该数据还能存活多久。如果超过存活时间,数据就读不到了。
+```java
+/**
+ * Get ttl time.
+ * @param tableName TableHandler name
+ * @param hashKey used to decide which partition to put this k-v,
+ *                if null or length == 0, means hash key is "".
+ * @param sortKey all the k-v under hashKey will be sorted by sortKey,
+ *                if null or length == 0, means sort key is "".
+ * @return ttl time in seconds; -1 if no ttl set; -2 if not exist.
+ * @throws PException
+ */
+public int ttl(String tableName, byte[] hashKey, byte[] sortKey) throws PException;
+```
+注:
+ * 参数:需传入TableName、HashKey、SortKey。
+ * 返回值:TTL时间,单位为秒。如果该数据没有设置TTL,返回-1;如果该数据不存在,返回-2。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+
+### exist
+检查数据是否存在。
+```java
+/**
+ * Check value exist by key from the cluster
+ * @param tableName TableHandler name
+ * @param hashKey used to decide which partition the key may exist.
+ * @param sortKey all keys under the same hashKey will be sorted by sortKey
+ *
+ * @return true if exist, false if not exist
+ * @throws PException
+ */
+public boolean exist(String tableName, byte[] hashKey, byte[] sortKey) throws PException;
+```
+注:
+ * 参数:需传入TableName、HashKey、SortKey。
+ * 返回值:如果存在返回true,否则返回false。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+
+### sortKeyCount
+获取某个HashKey下所有SortKey的个数。
+```java
+/**
+ * @param tableName TableHandler name
+ * @param hashKey used to decide which partition the key may exist.
+ * @return the count result for the hashKey
+ * @throws PException
+ */
+public long sortKeyCount(String tableName, byte[] hashKey) throws PException;
+```
+注:
+ * 参数:需传入TableName、HashKey。
+ * 返回值:返回HashKey下所有SortKey的个数。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+
+### multiGetSortKeys
+获取某个HashKey下SortKey列表。
+```java
+/**
+ * Get multiple sort keys under the same hash key.
+ * @param tableName table name
+ * @param hashKey used to decide which partition to put this k-v,
+ *                should not be null or empty.
+ * @param maxFetchCount max count of k-v pairs to be fetched.
+ *                      max_fetch_count <= 0 means no limit. default value is 100.
+ * @param maxFetchSize max size of k-v pairs to be fetched.
+ *                     max_fetch_size <= 0 means no limit. default value is 1000000.
+ * @param sortKeys output sort keys.
+ * @return true if all data is fetched; false if only partial data is fetched.
+ * @throws PException
+ */
+public boolean multiGetSortKeys(String tableName, byte[] hashKey, int maxFetchCount, int maxFetchSize, List<byte[]> sortKeys) throws PException;
+public boolean multiGetSortKeys(String tableName, byte[] hashKey, List<byte[]> sortKeys) throws PException;
+```
+注:
+ * 提供了两个版本的接口,其中第一个接口可以指定maxFetchCount和maxFetchSize。
+ * 参数:
+   * 传入参数:需传入TableName、HashKey;选择性传入maxFetchCount、maxFetchSize。
+   * 传出参数:数据通过sortKeys传出,sortKeys由用户在调用前new出来。
+   * maxFetchCount和maxFetchSize用于限制读取的数据量,maxFetchCount表示最多读取的数据条数,maxFetchSize表示最多读取的数据字节数,两者任一达到限制就停止读取。
+ * 返回值:如果用户指定了maxFetchCount或者maxFetchSize,单次查询可能只获取到部分结果。如果所有满足条件的数据都已经获取到,则返回true;否则返回false。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+
+### getScanner
+获取遍历某个HashKey下所有数据的迭代器,用于局部扫描。
+```java
+public enum FilterType {
+    FT_NO_FILTER(0),
+    FT_MATCH_ANYWHERE(1), // match filter string at any position
+    FT_MATCH_PREFIX(2),   // match filter string at prefix
+    FT_MATCH_POSTFIX(3);  // match filter string at postfix
+}
+ 
+ 
+public class ScanOptions {
+    public int timeoutMillis = 5000; // operation timeout in milli-seconds.
+                                     // if timeoutMillis > 0, it is a timeout value for current op,
+                                     // else the timeout value in the configuration file will be used.
+    public int batchSize = 1000; // internal buffer batch size
+    public boolean startInclusive = true; // if the startSortKey is included
+    public boolean stopInclusive = false; // if the stopSortKey is included
+    public FilterType hashKeyFilterType = FilterType.FT_NO_FILTER; // filter type for hash key
+    public byte[] hashKeyFilterPattern = null; // filter pattern for hash key
+    public FilterType sortKeyFilterType = FilterType.FT_NO_FILTER; // filter type for sort key
+    public byte[] sortKeyFilterPattern = null; // filter pattern for sort key
+    public boolean noValue = false; // only fetch hash_key and sort_key, but not fetch value
+}
+ 
+ 
+/**
+ * Get Scanner for {startSortKey, stopSortKey} within hashKey
+ * @param tableName TableHandler name
+ * @param hashKey used to decide which partition to put this k-v,
+ * @param startSortKey start sort key scan from
+ *                     if null or length == 0, means start from begin
+ * @param stopSortKey stop sort key scan to
+ *                    if null or length == 0, means stop to end
+ * @param options scan options like endpoint inclusive/exclusive
+ * @return scanner              
+ * @throws PException
+ */
+public PegasusScannerInterface getScanner(String tableName, byte[] hashKey, byte[] startSortKey, byte[] stopSortKey, ScanOptions options) throws PException;
+```
+注:
+ * 参数:需传入TableName、HashKey、StartSortKey、StopSortKey、ScanOptions。
+   * StartSortKey和StopSortKey用于指定scan的返回,并通过ScanOptions指定区间的开闭。
+   * 如果StartSortKey为null,表示从头开始;如果StopSortKey为null,表示一直读到尾。
+   * ScanOptions说明:
+     * timeoutMillis:从server端读取数据的超时时间,单位毫秒,默认值为5000。
+     * batchSize:从server端读取数据时每批数据的个数,默认值为1000。
+     * startInclusive:是否包含StartSortKey,默认为true。
+     * stopInclusive:是否包含StopSortKey,默认为false。
+     * hashKeyFilterType:HashKey的过滤类型,包括无过滤、任意位置匹配、前缀匹配和后缀匹配,默认无过滤。
+     * hashKeyFilterPattern:HashKey的过滤模式串,空串相当于无过滤。
+     * sortKeyFilterType:SortKey的过滤类型,包括无过滤、任意位置匹配、前缀匹配和后缀匹配,默认无过滤。
+     * sortKeyFilterPattern:SortKey的过滤模式串,空串相当于无过滤。
+     * noValue:只返回HashKey和SortKey,不返回Value数据,默认为false。
+ * 返回值:返回迭代器PegasusScannerInterface。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+
+### getUnorderedScanner
+获取遍历整个表的所有数据的迭代器,用于全局扫描。
+```
+/**
+ * Get Scanners for all data in database
+ * @param tableName TableHandler name
+ * @param maxSplitCount how many scanner expected
+ * @param options scan options like batchSize
+ * @return scanners, count of which would be no more than maxSplitCount
+ * @throws PException
+ */
+public List<PegasusScannerInterface> getUnorderedScanners(String tableName, int maxSplitCount, ScanOptions options) throws PException;
+```
+注:
+ * 参数:需传入TableName、maxSplitCount、ScanOptions。
+   * maxSplitCount:用于决定返回的迭代器的个数。当返回多个迭代器时,每个迭代器可以访问表中的部分数据。通过返回迭代器列表,用户可以进行并发scan或者在MapReduce中使用。如果不需要多个迭代器,可以将其设置为1。
+   * ScanOptions同上。
+ * 返回值:返回迭代器PegasusScannerInterface列表。
+ * 异常:如果出现异常,譬如网络错误、超时错误、服务端错误等,会抛出 PException。
+
+## 创建Table实例
+通过```PegasusClientInterface::openTable()```方法获取PegasusTableInterface的对象实例:
+```java
+/**
+* Open a table. Please notice that pegasus support two kinds of API:
+*     1. the client-interface way, which is provided in this class.
+*     2. the table-interface way, which is provided by {@link PegasusTableInterface}.
+* With the client-interface, you don't need to create PegasusTableInterface by openTable, so
+* you can access the pegasus cluster conveniently. However, the client-interface's api also has
+* some restrictions:
+*     1. we don't provide async methods in client-interface.
+*     2. the timeout in client-interface isn't as accurate as the table-interface.
+*     3. the client-interface may throw an exception when open table fails. It means that
+*        you may need to handle this exception in every data access operation, which is annoying.
+*     4. You can't specify a per-operation timeout.
+* So we recommend you to use the table-interface.
+* 
+* @param tableName the table should be exist on the server, which is created before by
+*                  the system administrator
+* @return the table handler
+* @throws PException
+*/
+public PegasusTableInterface openTable(String tableName) throws PException;
+```
+注:
+  * 如果网络超时或者表不存在,都会抛出异常。
+
+使用示例:
+```java
+  PegasusTableInterface table = client.openTable(tableName);
+```
+
+PegasusTableInterface中同时提供了同步和异步的API。
+
+同步API与PegasusClientInterface基本一致,区别在于:不用指定tableName参数;可以单独指定超时时间。
+
+同时,openTable提供了warmup功能,用于解决表的第一次rpc调用过慢的问题,具体可参考最佳实践一节。
+
+### 基于Future的异步API
+异步API使用Future模式,具体来说是使用的 io.netty.util.concurrent.Future (参见 https://netty.io/4.1/api/index.html )。每个异步接口的返回值都是一个Future\<T\>,其中T是该操作返回结果的类型。Future具有如下特性:
+ * 可以通过 addListener() 方法设置一个或者多个Listener,即异步回调函数。回调函数会在操作完成时被调用;如果在add时操作已经完成,回调函数就会被立即调用;回调函数被调用的顺序与添加的顺序一致。
+ * 可以通过 await() 方法等待操作完成。但是注意的是await()方法只能保证操作完成以及下面的三个方法可用,并不能保证回调函数已经被执行。
+ * 在操作完成后,可以通过 isSuccess() 方法判断操作是否成功;如果成功,可以通过 getNow() 方法获取结果;如果失败,可以通过 cause() 方法获取异常。
+
+**注意**:第一次调用一个表的异步API的时候,函数返回之前可能会有一些额外延迟(典型地10ms左右),这是因为第一次调用时需要从meta-server获取表的信息和路由信息。
+
+一个典型的异步使用样例:
+```java
+// 获取Table实例
+PegasusTableInterface table = client.openTable(tableName);
+  
+// 发起异步调用
+Future<Boolean> future = table.asyncExist(hashKey, sortKey, 0);
+  
+// 设置回调函数
+future.addListener(
+        new ExistListener() {
+            public void operationComplete(Future<Boolean> future) throws Exception {
+                if (future.isSuccess()) {
+                    Boolean result = future.getNow();
+                }
+                else {
+                    future.cause().printStackTrace();
+                }
+            }
+        }
+);
+  
+// 等待操作完成
+future.await();
+```
+
+## PegasusTableInterface接口
+
+### asyncGet
+异步读单行数据。
+```java
+public static interface GetListener extends GenericFutureListener<Future<byte[]>> {
+    /**
+     * This function will be called when listened asyncGet future is done.
+     * @param future the listened future
+     * @throws Exception
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<byte[]> future) throws Exception;
+}
+ 
+/**
+ * Get value for a specific (hashKey, sortKey) pair, async version
+ * @param hashKey used to decide which partition the key may exist
+ *                if null or empty, means hash key is "".
+ * @param sortKey all keys under the same hashKey will be sorted by sortKey
+ *                if null or empty, means sort key is "".
+ * @param timeout how long will the operation timeout in milliseconds.
+ *                if timeout > 0, it is a timeout value for current op,
+ *                else the timeout value in the configuration file will be used.
+ *
+ * @return the future for current op
+ *
+ * Future return:
+ *      On success: the got value
+ *      On failure: a throwable, which is an instance of PException
+ *
+ * Thread safety:
+ *      The api is thread safe.
+ *      All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ *      listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ *      But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<byte[]> asyncGet(byte[] hashKey, byte[] sortKey, int timeout/*ms*/);
+```
+注:
+ * 参数:需传入HashKey、SortKey、timeout。
+   * timeout单位为毫秒,如果<=0,表示使用配置文件中的默认超时。
+ * 返回值:Future\<byte[]\>。
+
+### asyncMultiGet
+异步读同一HashKey下的多行数据。
+```java
+public static class MultiGetResult {
+    /**
+     * return value for multiGet
+     * @param allFetched true if all data on the server are fetched; false if only partial data are fetched.
+     * @param values the got values. If sortKey in the input sortKeys is not found, it won't be in values.
+     *               The output values are ordered by the sortKey.
+     */
+    public boolean allFetched;
+    public List<Pair<byte[], byte[]>> values;
+}
+ 
+public static interface MultiGetListener extends GenericFutureListener<Future<MultiGetResult>> {
+    /**
+     * This function will be called when listened asyncMultiGet future is done.
+     * @param future the listened future
+     * @throws Exception
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<MultiGetResult> future) throws Exception;
+}
+ 
+/**
+ * get multiple key-values under the same hashKey, async version
+ * @param hashKey used to decide which partition the key may exist
+ *                should not be null or empty.
+ * @param sortKeys try to get values of sortKeys under the hashKey
+ *                 if null or empty, try to get all (sortKey,value) pairs under hashKey
+ * @param maxFetchCount max count of kv pairs to be fetched
+ *                      maxFetchCount <= 0 means no limit. default value is 100
+ * @param maxFetchSize max size of kv pairs to be fetched.
+ *                     maxFetchSize <= 0 means no limit. default value is 1000000.
+ * @param timeout how long will the operation timeout in milliseconds.
+ *                if timeout > 0, it is a timeout value for current op,
+ *                else the timeout value in the configuration file will be used.
+ *
+ * @return the future for current op
+ *
+ * Future return:
+ *      On success: An object of type MultiGetResult
+ *      On failure: a throwable, which is an instance of PException
+ *
+ * Thread safety:
+ *      All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ *      listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ *      But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<MultiGetResult> asyncMultiGet(byte[] hashKey, List<byte[]> sortKeys, int maxFetchCount, int maxFetchSize, int timeout/*ms*/);
+public Future<MultiGetResult> asyncMultiGet(byte[] hashKey, List<byte[]> sortKeys, int timeout/*ms*/);
+```
+注:
+ * 提供了两个版本的接口,其中第一个接口可以指定maxFetchCount和maxFetchSize。
+ * 参数:需传入HashKey、SortKeys、timeout;选择性传入maxFetchCount、maxFetchSize。
+   * SortKeys如果非空,则只读取指定的数据;SortKeys如果为空,则表示读取该HashKey下的所有数据。
+   * timeout单位为毫秒,如果<=0,表示使用配置文件中的默认超时。
+   * maxFetchCount和maxFetchSize用于限制读取的数据量,maxFetchCount表示最多读取的数据条数,maxFetchSize表示最多读取的数据字节数,两者任一达到限制就停止读取。
+ * 返回值:Future\<MultiGetResult\>。
+   * allFetched:如果用户指定了maxFetchCount或者maxFetchSize,单次查询可能只获取到部分结果。如果所有满足条件的数据都已经获取到,则设置为true;否则设置为false。
+
+asyncMultiGet还有另外一个版本的接口,可以支持SortKey的**范围查询**和**条件过滤**,只读取满足特定条件的数据。并且从1.8.0开始在MultiGetOptions中增加了reverse参数,支持**逆向扫描**数据。
+```java
+    /**
+     * get multiple key-values under the same hashKey with sortKey range limited, async version
+     * @param hashKey used to decide which partition the key may exist
+     *                should not be null or empty.
+     * @param startSortKey the start sort key.
+     *                     null means "".
+     * @param stopSortKey the stop sort key.
+     *                    null or "" means fetch to the last sort key.
+     * @param options multi-get options.
+     * @param maxFetchCount max count of kv pairs to be fetched
+     *                      maxFetchCount <= 0 means no limit. default value is 100
+     * @param maxFetchSize max size of kv pairs to be fetched.
+     *                     maxFetchSize <= 0 means no limit. default value is 1000000.
+     * @param timeout how long will the operation timeout in milliseconds.
+     *                if timeout > 0, it is a timeout value for current op,
+     *                else the timeout value in the configuration file will be used.
+     *
+     * @return the future for current op
+     *
+     * Future return:
+     *      On success: An object of type MultiGetResult
+     *      On failure: a throwable, which is an instance of PException
+     *
+     * Thread safety:
+     *      All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+     *      listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+     *      But listeners for different tables are not guaranteed to be dispatched in the same thread.
+     */
+    public Future<MultiGetResult> asyncMultiGet(byte[] hashKey, byte[] startSortKey, byte[] stopSortKey,
+                                                MultiGetOptions options, int maxFetchCount, int maxFetchSize,
+                                                int timeout/*ms*/);
+    public Future<MultiGetResult> asyncMultiGet(byte[] hashKey, byte[] startSortKey, byte[] stopSortKey,
+                                                MultiGetOptions options, int timeout/*ms*/);
+```
+注:
+  * 参数使用同[multiGet](#multiget)
+
+### asyncSet
+异步写单行数据。
+```java
+public static interface SetListener extends GenericFutureListener<Future<Void>> {
+    /**
+     * This function will be called when listened asyncSet future is done.
+     * @param future the listened future
+     * @throws Exception
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<Void> future) throws Exception;
+}
+ 
+/**
+ * Set value for a specific (hashKey, sortKey) pair, async version
+ * @param hashKey used to decide which partition the key may exist
+ *                if null or empty, means hash key is "".
+ * @param sortKey all keys under the same hashKey will be sorted by sortKey
+ *                if null or empty, means sort key is "".
+ * @param value should not be null
+ * @param ttlSeconds time to live in seconds
+ *                   0 means no ttl, default value is 0
+ * @param timeout how long will the operation timeout in milliseconds.
+ *                if timeout > 0, it is a timeout value for current op,
+ *                else the timeout value in the configuration file will be used.
+ *
+ * @return the future for current op
+ *
+ * Future return:
+ *      On success: no return
+ *      On failure: a throwable, which is an instance of PException
+ *
+ * Thread safety:
+ *      The api is thread safe.
+ *      All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ *      listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ *      But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<Void> asyncSet(byte[] hashKey, byte[] sortKey, byte[] value, int ttlSeconds, int timeout/*ms*/);
+public Future<Void> asyncSet(byte[] hashKey, byte[] sortKey, byte[] value, int timeout/*ms*/);
+```
+注:
+ * 提供了两个版本的接口,其中第一个接口可以指定TTL时间。
+ * 参数:需传入HashKey、SortKey、Value、timeout;选择性传入TTL。
+   * timeout单位为毫秒,如果<=0,表示使用配置文件中的默认超时。
+   * ttlSeconds是数据的TTL时间,单位为秒。TTL必须>=0, 0表示不设置TTL时间,当TTL<0时将抛出PException异常。
+ * 返回值:Future\<Void\>。
+
+### asyncMultiSet
+异步写同一HashKey下的多行数据。
+```java
+public static interface MultiSetListener extends GenericFutureListener<Future<Void>> {
+    /**
+     * This function will be called when listened asyncMultiSet future is done.
+     * @param future the listened future
+     * @throws Exception
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<Void> future) throws Exception;
+}
+ 
+/**
+ * Set key-values for a specific hashKey, async version
+ * @param hashKey used to decide which partition the key may exist
+ *                if null or empty, means hash key is "".
+ * @param values all (sortKey, value) pairs
+ *               should not be null or empty
+ * @param ttlSeconds time to live in seconds
+ *                   0 means no ttl, default value is 0
+ * @param timeout how long will the operation timeout in milliseconds.
+ *                if timeout > 0, it is a timeout value for current op,
+ *                else the timeout value in the configuration file will be used.
+ *
+ * @return the future for current op
+ *
+ * Future return:
+ *      On success: no return
+ *      On failure: a throwable, which is an instance of PException
+ *
+ * Thread safety:
+ *      All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ *      listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ *      But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<Void> asyncMultiSet(byte[] hashKey, List<Pair<byte[], byte[]>> values, int ttlSeconds, int timeout/*ms*/);
+public Future<Void> asyncMultiSet(byte[] hashKey, List<Pair<byte[], byte[]>> values, int timeout/*ms*/);
+```
+注:
+ * 提供了两个版本的接口,其中第一个接口可以指定TTL时间。
+ * 参数:需传入HashKey、Values、timeout;选择性传入ttlSeconds。
+   * Values是Pair列表,Pair的第一个元素是SortKey,第二个元素为value。
+   * timeout单位为毫秒,如果<=0,表示使用配置文件中的默认超时。
+   * ttlSeconds是数据的TTL时间,单位为秒。TTL必须>=0, 0表示不设置TTL时间,当TTL<0时将抛出PException异常。
+ * 返回值:Future\<Void\>。
+
+### asyncDel
+异步删单行数据。
+```java
+public static interface DelListener extends GenericFutureListener<Future<Void>> {
+    /**
+     * This function will be called when listened asyncDel future is done.
+     * @param future the listened future
+     * @throws Exception
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<Void> future) throws Exception;
+}
+ 
+/**
+ * delete value for a specific (hashKey, sortKey) pair, async version
+ * @param hashKey used to decide which partition the key may exist
+ *                if null or empty, means hash key is "".
+ * @param sortKey all keys under the same hashKey will be sorted by sortKey
+ *                if null or empty, means sort key is "".
+ * @param timeout how long will the operation timeout in milliseconds.
+ *                if timeout > 0, it is a timeout value for current op,
+ *                else the timeout value in the configuration file will be used.
+ *
+ * @return the future for current op
+ *
+ * Future return:
+ *      On success: no return
+ *      On failure: a throwable, which is an instance of PException
+ *
+ * Thread safety:
+ *      All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ *      listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ *      But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<Void> asyncDel(byte[] hashKey, byte[] sortKey, int timeout/*ms*/);
+```
+注:
+ * 参数:需传入HashKey、SortKey、timeout。
+   * timeout单位为毫秒,如果<=0,表示使用配置文件中的默认超时。
+ * 返回值:Future\<Void\>。
+
+### asyncMultiDel
+异步删同一HashKey下的多行数据。
+```java
+public static interface MultiDelListener extends GenericFutureListener<Future<Void>> {
+    /**
+     * This function will be called when listened asyncMultiDel future is done.
+     * @param future the listened future
+     * @throws Exception
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<Void> future) throws Exception;
+}
+ 
+/**
+ * delete mutiple values for a specific hashKey, async version
+ * @param hashKey used to decide which partition the key may exist
+ *                if null or empty, means hash key is "".
+ * @param sortKeys all the sortKeys need to be deleted
+ *                 should not be null or empty
+ * @param timeout how long will the operation timeout in milliseconds.
+ *                if timeout > 0, it is a timeout value for current op,
+ *                else the timeout value in the configuration file will be used.
+ *
+ * @return the future for current op
+ *
+ * Future return:
+ *      On success: no return
+ *      On failure: a throwable, which is an instance of PException
+ *
+ * Thread safety:
+ *      All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ *      listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ *      But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<Void> asyncMultiDel(byte[] hashKey, List<byte[]> sortKeys, int timeout/*ms*/);
+```
+注:
+ * 参数:需传入HashKey、SortKeys、timeout。
+   * SortKeys不允许为空,如果不知道该HashKey下面有哪些SortKey,可以通过multiGetSortKeys方法获取。
+   * timeout单位为毫秒,如果<=0,表示使用配置文件中的默认超时。
+ * 返回值:Future\<Void\>。
+
+### asyncIncr
+原子增(减)操作。[incr](#incr)的异步版本。
+
+```
+public static interface IncrListener extends GenericFutureListener<Future<Long>> {
+    /**
+     * This function will be called when listened asyncIncr future is done.
+     *
+     * @param future the listened future
+     * @throws Exception throw exception if any error occurs.
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<Long> future) throws Exception;
+}
+
+/**
+ * atomically increment value by key, async version
+ *
+ * @param hashKey   the hash key to increment.
+ * @param sortKey   the sort key to increment.
+ * @param increment the increment to be added to the old value.
+ * @param timeout   how long will the operation timeout in milliseconds.
+ *                  if timeout > 0, it is a timeout value for current op,
+ *                  else the timeout value in the configuration file will be used.
+ * @return the future for current op
+ * <p>
+ * Future return:
+ * On success: return new value.
+ * On failure: a throwable, which is an instance of PException
+ * <p>
+ * Thread safety:
+ * All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ * listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ * But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<Long> asyncIncr(byte[] hashKey, byte[] sortKey, long increment, int timeout/*ms*/);
+```
+注:
+ * 参数和返回值:参见同步接口[incr](#incr)。
+
+### asyncCheckAndSet
+单HashKey数据的原子CAS操作。[checkAndSet](#checkandset)的异步版本。
+
+```java
+public static class CheckAndSetResult {
+    /**
+     * return value for checkAndSet
+     *
+     * @param setSucceed true if set value succeed.
+     * @param checkValueReturned true if the check value is returned.
+     * @param checkValueExist true if the check value is exist; can be used only when checkValueReturned is true.
+     * @param checkValue return the check value if exist; can be used only when checkValueExist is true.
+     */
+    boolean setSucceed;
+    boolean checkValueReturned;
+    boolean checkValueExist;
+    byte[] checkValue;
+}
+
+public static interface CheckAndSetListener extends GenericFutureListener<Future<CheckAndSetResult>> {
+    /**
+     * This function will be called when listened asyncCheckAndSet future is done.
+     *
+     * @param future the listened future
+     * @throws Exception throw exception if any error occurs.
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<CheckAndSetResult> future) throws Exception;
+}
+
+/**
+ * atomically check and set value by key, async version.
+ * if the check condition is satisfied, then apply to set value.
+ *
+ * @param hashKey      the hash key to check and set.
+ * @param checkSortKey the sort key to check.
+ * @param checkType    the check type.
+ * @param checkOperand the check operand.
+ * @param setSortKey   the sort key to set value if check condition is satisfied.
+ * @param setValue     the value to set if check condition is satisfied.
+ * @param options      the check-and-set options.
+ * @param timeout      how long will the operation timeout in milliseconds.
+ *                     if timeout > 0, it is a timeout value for current op,
+ *                     else the timeout value in the configuration file will be used.
+ * @return the future for current op
+ * <p>
+ * Future return:
+ * On success: return CheckAndSetResult.
+ * On failure: a throwable, which is an instance of PException
+ * <p>
+ * Thread safety:
+ * All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ * listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ * But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<CheckAndSetResult> asyncCheckAndSet(byte[] hashKey, byte[] checkSortKey, CheckType checkType,
+                                                  byte[] checkOperand, byte[] setSortKey, byte[] setValue,
+                                                  CheckAndSetOptions options, int timeout/*ms*/);
+```
+注:
+ * 参数和返回值:参见同步接口[checkAndSet](#checkandset)。
+
+### asyncCompareExchange
+单HashKey数据的原子CAS操作。[compareExchange](#compareexchange)的异步版本。
+
+```java
+public static class CompareExchangeResult {
+    /**
+     * return value for CompareExchange
+     *
+     * @param setSucceed true if set value succeed.
+     * @param actualValue return the actual value if set value failed; null means the actual value is not exist.
+     */
+    boolean setSucceed;
+    byte[] actualValue;
+}
+
+public static interface CompareExchangeListener extends GenericFutureListener<Future<CompareExchangeResult>> {
+    /**
+     * This function will be called when listened asyncCompareExchange future is done.
+     *
+     * @param future the listened future
+     * @throws Exception throw exception if any error occurs.
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<CompareExchangeResult> future) throws Exception;
+}
+
+/**
+ * atomically compare and exchange value by key, async version.
+ * <p>
+ * - if the original value for the key is equal to the expected value, then update it with the desired value,
+ *   set CompareExchangeResult.setSucceed to true, and set CompareExchangeResult.actualValue to null because
+ *   the actual value must be equal to the desired value.
+ * - if the original value for the key is not exist or not equal to the expected value, then set
+ *   CompareExchangeResult.setSucceed to false, and set the actual value in CompareExchangeResult.actualValue.
+ * <p>
+ * this method is very like the C++ function in {https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange}.
+ *
+ * @param hashKey       the hash key to compare and exchange.
+ * @param sortKey       the sort key to compare and exchange.
+ * @param expectedValue the value expected to be found for the key.
+ * @param desiredValue  the desired value to set if the original value for the key is equal to the expected value.
+ * @param ttlSeconds    time to live in seconds of the desired value, 0 means no ttl.
+ * @param timeout       how long will the operation timeout in milliseconds.
+ *                      if timeout > 0, it is a timeout value for current op,
+ *                      else the timeout value in the configuration file will be used.
+ * @return the future for current op
+ * <p>
+ * Future return:
+ * On success: return CompareExchangeResult.
+ * On failure: a throwable, which is an instance of PException
+ * <p>
+ * Thread safety:
+ * All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ * listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ * But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<CompareExchangeResult> asyncCompareExchange(byte[] hashKey, byte[] sortKey,
+                                                          byte[] expectedValue, byte[] desiredValue,
+                                                          int ttlSeconds, int timeout/*ms*/);
+```
+注:
+ * 参数和返回值:参见同步接口[compareExchange](#compareexchange)。
+
+### asyncTTL
+异步获取单行数据的TTL时间,即该数据还能存活多久,单位为秒。
+```java
+public static interface TTLListener extends GenericFutureListener<Future<Integer>> {
+    /**
+     * This function will be called when listened asyncTTL future is done.
+     * @param future the listened future
+     * @throws Exception
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<Integer> future) throws Exception;
+}
+ 
+/**
+ * get TTL value for a specific (hashKey, sortKey) pair, async version
+ * @param hashKey used to decide which partition the key may exist
+ *                if null or empty, means hash key is "".
+ * @param sortKey all keys under the same hashKey will be sorted by sortKey
+ *                if null or empty, means sort key is "".
+ * @param timeout how long will the operation timeout in milliseconds.
+ *                if timeout > 0, it is a timeout value for current op,
+ *                else the timeout value in the configuration file will be used.
+ *
+ * @return the future for current op
+ *
+ * Future return:
+ *      On success: ttl time in seconds; -1 if no ttl set; -2 if not exist.
+ *      On failure: a throwable, which is an instance of PException
+ *
+ * Thread safety:
+ *      All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ *      listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ *      But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<Integer> asyncTTL(byte[] hashKey, byte[] sortKey, int timeout/*ms*/);
+```
+注:
+ * 参数:需传入HashKey、SortKey、timeout。
+   * timeout单位为毫秒,如果<=0,表示使用配置文件中的默认超时。
+ * 返回值:Future\<Integer\>。
+   * 返回结果为TTL时间,单位为秒。如果该数据没有设置TTL,返回-1;如果该数据不存在,返回-2。
+
+### asyncExist
+异步检查数据是否存在。
+```java
+public static interface ExistListener extends GenericFutureListener<Future<Boolean>> {
+    /**
+     * This function will be called when listened asyncExist future is done.
+     * @param future the listened future
+     * @throws Exception
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<Boolean> future) throws Exception;
+}
+ 
+/**
+ * Check value existence for a specific (hashKey, sortKey) pair of current table, async version
+ * @param hashKey used to decide which partition the key may exist
+ *                if null or length == 0, means hash key is "".
+ * @param sortKey all keys under the same hashKey will be sorted by sortKey
+ *                if null or length == 0, means sort key is "".
+ * @param timeout how long will the operation timeout in milliseconds.
+ *                if timeout > 0, it is a timeout value for current op,
+ *                else the timeout value in the configuration file will be used.
+ *
+ * @return A future for current op.
+ *
+ * Future return:
+ *      On success: true if exist, false if not exist
+ *      On failure: a throwable, which is an instance of PException
+ *
+ * Thread safety:
+ *      The api is thread safe.
+ *      All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ *      listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ *      But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<Boolean> asyncExist(byte[] hashKey, byte[] sortKey, int timeout/*ms*/);
+```
+注:
+ * 参数:需传入HashKey、SortKey、timeout。
+   * timeout单位为毫秒,如果<=0,表示使用配置文件中的默认超时。
+ * 返回值:Future\<Boolean\>。
+   *  返回结果是个布尔值。如果存在返回true,否则返回false。
+
+### asyncSortKeyCount
+异步获取某个HashKey下所有SortKey的个数。
+```java
+public static interface SortKeyCountListener extends GenericFutureListener<Future<Long>> {
+    /**
+     * This function will be called when listened asyncSortKeyCount future is done.
+     * @param future the listened future
+     * @throws Exception
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<Long> future) throws Exception;
+}
+ 
+/**
+ * Count the sortkeys for a specific hashKey, async version
+ * @param hashKey used to decide which partition the key may exist
+ *                should not be null or empty
+ * @param timeout how long will the operation timeout in milliseconds.
+ *                if timeout > 0, it is a timeout value for current op,
+ *                else the timeout value in the configuration file will be used.
+ *
+ * @return the future for current op
+ *
+ * Future return:
+ *      On success: the count result for the hashKey
+ *      On failure: a throwable, which is an instance of PException
+ *
+ * Thread safety:
+ *      The api is thread safe.
+ *      All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ *      listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ *      But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<Long> asyncSortKeyCount(byte[] hashKey, int timeout/*ms*/);
+```
+注:
+ * 参数:需传入HashKey、timeout。
+   * timeout单位为毫秒,如果<=0,表示使用配置文件中的默认超时。
+ * 返回值:Future\<Long\>。
+   * 返回结果为HashKey下所有SortKey的个数。
+
+### asyncMultiGetSortKeys
+异步获取某个HashKey下SortKey列表。
+```java
+public static class MultiGetSortKeysResult {
+    /**
+     * return value for multiGetSortkeys
+     * @param allFetched true if all data on the server are fetched; false if only partial data are fetched.
+     * @param keys the got keys.
+     *             The output keys are in order.
+     */
+    public boolean allFetched;
+    public List<byte[]> keys;
+};
+ 
+public static interface MultiGetSortKeysListener extends GenericFutureListener<Future<MultiGetSortKeysResult>> {
+    /**
+     * This function will be called when listened asyncMultiGetSortKeys future is done.
+     * @param future the listened future
+     * @throws Exception
+     *
+     * Notice: User shouldn't do any operations that may block or time-consuming
+     */
+    @Override
+    public void operationComplete(Future<MultiGetSortKeysResult> future) throws Exception;
+}
+ 
+/**
+ * get all the sortKeys for the same hashKey
+ * @param hashKey used to decide which partition the key may exist
+ *                should not be null or empty.
+ * @param maxFetchCount max count of kv pairs to be fetched
+ *                      maxFetchCount <= 0 means no limit. default value is 100
+ * @param maxFetchSize max size of kv pairs to be fetched.
+ *                     maxFetchSize <= 0 means no limit. default value is 1000000.
+ * @param timeout how long will the operation timeout in milliseconds.
+ *                if timeout > 0, it is a timeout value for current op,
+ *                else the timeout value in the configuration file will be used.
+ *
+ * @return the future for current op
+ *
+ * Future return:
+ *      On success: An object of type MultiGetSortKeysResult
+ *      On failure: a throwable, which is an instance of PException
+ *
+ * Thread safety:
+ *      All the listeners for the same table are guaranteed to be dispatched in the same thread, so all the
+ *      listeners for the same future are guaranteed to be executed as the same order as the listeners added.
+ *      But listeners for different tables are not guaranteed to be dispatched in the same thread.
+ */
+public Future<MultiGetSortKeysResult> asyncMultiGetSortKeys(byte[] hashKey, int maxFetchCount, int maxFetchSize, int timeout/*ms*/);
+public Future<MultiGetSortKeysResult> asyncMultiGetSortKeys(byte[] hashKey, int timeout/*ms*/);
+```
+注:
+ * 提供了两个版本的接口,其中第一个接口可以指定maxFetchCount和maxFetchSize。
+ * 参数:需传入HashKey、timeout;选择性传入maxFetchCount、maxFetchSize。
+   * timeout单位为毫秒,如果<=0,表示使用配置文件中的默认超时。
+   * maxFetchCount和maxFetchSize用于限制读取的数据量,maxFetchCount表示最多读取的数据条数,maxFetchSize表示最多读取的数据字节数,两者任一达到限制就停止读取。
+ * 返回值:Future\<MultiGetSortKeysResult\>。
+   * allFetched:如果用户指定了maxFetchCount或者maxFetchSize,单次查询可能只获取到部分结果。如果所有满足条件的数据都已经获取到,则设置为true;否则设置为false。
+
+## PegasusScannerInterface接口
+
+### next
+在scan操作时,同步获取下一条数据。
+```java
+/**
+ * Get the next item.
+ * @return item like <<hashKey, sortKey>, value>; null returned if scan completed.
+ * @throws PException
+ */
+public Pair<Pair<byte[], byte[]>, byte[]> next() throws PException;
+```
+注:
+  * 返回值:Pair\<Pair\<byte[], byte[]\>, byte[]\>。
+    * 下一条kv-pair;若scan操作完成,则返回null。
+
+### asyncNext
+在scan操作时,异步获取下一条数据。
+```java
+/**
+ * Get the next item asynchronously.
+ * @return A future for current op.
+ *
+ * Future return:
+ *      On success: if scan haven't reach the end then return the kv-pair, else return null.
+ *      On failure: a throwable, which is an instance of PException.
+ */
+public Future<Pair<Pair<byte[], byte[]>, byte[]>> asyncNext();
+```
+注:
+  * 返回值:Future\<Pair\<Pair\<byte[], byte[]\>, byte[]\>\>。
+  * 在scan未扫描完成之前,会返回需要的kv-pair;当scan扫描完成之后,返回null。
+
+## 常见异常
+
+### ERR_OBJECT_NOT_FOUND
+表名不存在。可能原因:
+* 集群中没有建表。
+* 访问了错误的集群。在日志中搜索`meta_servers`,看集群的配置是否正确。
+* 表名拼写错误。检查代码中的表名是否正确;在日志中搜索`initialize table handler`,看表名是否正确。
+
+### ERR_TIMEOUT
+访问超时。可能原因:
+* 网络连接出错。
+* 读写延迟超过了超时时间。
+* 服务出现抖动。
+
+### ERR_SESSION_RESET
+服务端状态出错。可能原因:
+* 服务端正在做replica迁移,发生了状态切换。
+* 服务端有节点宕机,造成备份数不够,为了保证数据一致性,服务降级,变得不可用。
+* 如果是客户端初始化时得到该错误,可能是由于 meta 配置不正确,请检查配置
+
+### ERR_BUSY
+服务端流控达到限制。原因是:
+* 集群服务端对表设置了[表级写流量控制](/administration/throttling#服务端流控)。
+* 此时该表的瞬时流量(在这1秒内的写入操作数)达到了阈值,触发了reject流控操作,返回`ERR_BUSY`错误码。
+
+# Java客户端工具
+
+Pegasus Java客户端还提供了`PegasusCli`工具,支持简单的读写操作。
+
+使用mvn编译:
+```
+mvn clean package -DskipTests
+```
+
+编译后,在target文件夹下可以找到压缩包`pegasus-client-{version}-bin.tar.gz`,解压后得到`pegasus-client-{version}`文件夹,进入该文件夹,就可以找到PegasusCli工具。
+
+用法:
+```
+USAGE: PegasusCli <config-path> <table-name> <op-name> ...
+
+       <config-path> should be:
+         - zookeeper path  : zk://host1:port1,host2:port2,host3:port3/path/to/config
+         - local file path : file:///path/to/config
+         - java resource   : resource:///path/to/config
+
+       <op-name> should be:
+         - get <hash-key> <sort-key>
+         - set <hash-key> <sort-key> <value> [ttl_seconds]
+         - del <hash-key> <sort-key>
+         - multi_get <hash_key> [sort_key...]
+         - multi_get_sort_keys <hash_key>
+         - multi_set <hash_key> <sort_key> <value> [sort_key value...]
+         - multi_del <hash_key> <sort_key> [sort_key...]
+         - incr <hash_key> <sort_key> [increment]
+         - scan <hash_key> [start_sort_key] [stop_sort_key] [max_count]
+         - scan_all [max_count]
+         - copy_data <target_cluster_config_path> <target_table_name> [read_uncompress_type(none|zstd)] [write_compress_type(none|zstd)] [max_count]
+
+       For example:
+           PegasusCli file://./pegasus.properties temp get hash_key sort_key
+           PegasusCli file://./pegasus.properties temp scan hash_key '' '' 100
+```
+
+# 最佳实践
+
+## 流量控制
+经常有业务有集中灌数据的场景,灌数据的过程可能是单机的也可能是分布式的,譬如使用Spark处理后将数据写入Pegasus中。
+
+如果不做流控,很可能产生很高的QPS峰值写,对Pegasus系统造成较大压力:
+  * 写QPS太大,会影响读性能,造成读操作延迟上升;
+  * 写QPS太大,可能会造成集群无法承受压力而停止服务;
+
+因此,强烈建议业务方在灌数据的时候对写QPS进行流量控制。
+
+客户端流控的思路就是:
+* 首先定好总的QPS限制是多少(譬如10000/s),有多少个并发的客户端访问线程(譬如50个),然后计算出每个线程的QPS限制(譬如10000/50=200)。
+* 对于单个客户端线程,通过流控工具将QPS限制在期望的范围内。如果超过了QPS限制,就采用简单的sleep方式来等待。我们提供了一个流控工具类[com.xiaomi.infra.pegasus.tools.FlowController](https://github.com/XiaoMi/pegasus-java-client/blob/thrift-0.11.0-inlined/src/main/java/com/xiaomi/infra/pegasus/tools/FlowController.java),把计算QPS和执行sleep的逻辑封装起来,方便用户使用。
+
+FlowController用法:
+  * 构造函数接受一个QPS参数,用于指定流量限制,譬如单线程QPS只允许200/s,就传入200;
+  * 用户在每次需要执行写操作之前调用cntl.getToken()方法,该方法产生两种可能:
+    * 如果当前未达到流量控制,则无阻塞直接返回,继续执行后面的写操作;
+    * 如果当前已经达到流量限制,则该方法会阻塞(sleep)一段时间才返回,以达到控制流量的效果。
+  * 该工具尽量配合同步接口使用,对于异步接口可能效果没那么好。
+
+使用方法很简单:
+```java
+FlowController cntl = new FlowController(qps);
+while (true) {
+    // call getToken before operation
+    cntl.getToken();
+    client.set(...);
+}
+cntl.stop();
+```
+
+在分布式灌数据的场景下,用户可以先确定分布式的Task并发数,然后通过```总QPS限制 / Task并发数```,得到单个Task的QPS限制,再使用FlowController进行控制。
+
+## 分页查询
+
+类似实现网页列表的分页功能。
+典型地,一个HashKey下有很多SortKey,一页只显示固定数量的SortKey,下一页时再显示接下来的固定数量的SortKey。
+
+分页查询在Pegasus下有多种实现方式:
+
+1. 一次性获取HaskKey下的全部数据,在业务端缓存下来,由业务端自己实现分页逻辑。
+2. **顺序分页**:可以使用[multiGet()](#multiget)和[getScanner()](#getscanner)方法,这两者都支持SortKey的范围查询
+3. **逆序分页**:请使用[multiGet()](#multiget)方法,其支持SortKey的逆序查询
+
+### 顺序分页
+
+使用 `getScanner` 接口:
+
+```java
+ScanOptions options = new ScanOptions();
+options.startInclusive = true;
+options.stopInclusive = false;
+options.batchSize = 20; // 限制每页的大小为 20
+byte[] startSortKey = null;
+byte[] stopSortKey = null;
+PegasusScannerInterface scanner =
+  client.getScanner(tableName, hashKey, startSortKey, stopSortKey, options);
+
+// 同步方式获取
+Pair<Pair<byte[], byte[]>, byte[]> item;
+while ((item = scanner.next()) != null) {
+  // ... //
+}
+
+// 异步方式获取
+Future<Pair<Pair<byte[], byte[]>, byte[]>> item;
+while (true) {
+  item = scanner.asyncNext();
+  try {
+    Pair<Pair<byte[], byte[]>, byte[]> pair = item.get();
+    if (pair == null) {
+      break;
+    }
+    // ... //
+  } catch (Exception e) {
+    e.printStackTrace();
+  }
+}
+```
+
+如果你使用 `multiGet`,在 `MultiGetOptions` 中还需设置 `maxFetchCount`,限制每页条数:
+
+```java
+// 查第一页
+MultiGetOptions options = new MultiGetOptions();
+options.startInclusive = true;
+options.stopInclusive = false;
+int maxFetchCount = 20; // 限制每页的大小为 20
+int maxFetchSize = 20000; // 限制每页的总字节数为 20000
+byte[] startSortKey = null;
+byte[] stopSortKey = null;
+List<Pair<byte[], byte[]>> values = new ArrayList<>();
+boolean allFetched =
+    client.multiGet(
+        tableName, hashKey, startSortKey, stopSortKey, options,
+        maxFetchCount, maxFetchSize, values);
+if (allFetched) {
+  return;
+}
+
+// ... //
+
+// 查下一页
+options.startInclusive = false;
+options.stopInclusive = false;
+startSortKey = values.get(values.size() - 1); // 以上一页的最后(最大)一个值作为下一页查询的开始
+stopSortKey = null;
+allFetched =
+    client.multiGet(
+        tableName, hashKey, startSortKey, stopSortKey, options,
+        maxFetchCount, maxFetchSize, values);
+if (allFetched) {
+  return;
+}
+```
+
+### 逆序分页
+
+逆序分页需要使用`multiGet`接口,并在选项中设置`reverse=true`。
+
+```java
+MultiGetOptions options = new MultiGetOptions();
+options.startInclusive = true;
+options.stopInclusive = false;
+options.reverse = true;
+```
+
+## 数据序列化
+
+Pegasus的key和value都是原始的字节串(Java中就是byte[]),而用户存储数据一般用struct或者class。因此,在将数据存储到Pegasus中时,需要将用户数据转化为字节串,这就是**序列化**;在从Pegasus中读取数据时,又需要将字节串转化为用户的数据结构,这就是**反序列化**。序列化和反序列化通常都是成对出现了,后面我们只描述序列化。
+
+通常序列化有这些方式:
+* json:好处是数据可读性好;坏处是比较占空间。不推荐。
+* thrift:提供了多种Compact协议,常见的有binary协议。但是推荐用tcompact协议,因为这种协议的压缩率更高。
+* protobuf:与thrift类似,推荐序列化为binary格式。
+
+对于Thrift结构,使用tcompact协议进行序列化的样例:
+```java
+    import org.apache.thrift.TSerializer;
+    import org.apache.thrift.protocol.TCompactProtocol;
+
+    TSerializer serializer = new TSerializer(new TCompactProtocol.Factory()); 
+    byte[] bytes = serializer.serialize(data);
+```
+
+## 数据压缩
+
+对于value较大(>=2kb)的业务,我们推荐在客户端使用[facebook/Zstandard](https://github.com/facebook/zstd)压缩算法(简称 Zstd)对数据进行压缩,以减少value的数据长度,提升Pegasus的服务稳定性和读写性能。Zstd算法在压缩比和压缩速率上取得较好的平衡,适合通用场景。
+
+从Java Client 1.11.3版本开始,我们提供了Zstd压缩工具类[com.xiaomi.infra.pegasus.tools.ZstdWrapper](https://github.com/XiaoMi/pegasus-java-client/blob/thrift-0.11.0-inlined/src/main/java/com/xiaomi/infra/pegasus/tools/ZstdWrapper.java),方便用户实现压缩功能。
+
+使用示例:
+```java
+    byte[] value = "xxx";
+
+    // write the record into pegasus
+    table.set("h".getBytes(), "s".getBytes(), ZstdWrapper.compress(value), 1000);
+
+    // read the record from pegasus
+    byte[] compressedBuf = table.get("h".getBytes(), "s".getBytes(), 1000);
+
+    // decompress the value
+    byte[] orginalValue = ZstdWrapper.decompress(compressedBuf);
+```
+
+也可以参考测试用例代码 [TestZstdWrapper.java](https://github.com/XiaoMi/pegasus-java-client/blob/thrift-0.11.0-inlined/src/test/java/com/xiaomi/infra/pegasus/tools/TestZstdWrapper.java)。
+
+以上两个优化 [数据序列化](#数据序列化) 和 [数据压缩](#数据压缩) 可以在客户端同时使用,都是用客户端的CPU换取Pegasus集群的稳定性和读写性能。在通常情况下这都是值得的。
+
+有时候,业务方在开始使用Pegasus的时候,没有采用客户端压缩,但是在使用一段时间后,发现单条数据的value比较大,希望能通过压缩的办法改进性能。可以分两步:
+* [评估压缩收益](#评估压缩收益):评估通过客户端压缩是否能够获得足够好的压缩率。
+* [使用兼容性压缩](#使用兼容性压缩):升级业务端使用Pegasus Java客户端的逻辑,增加客户端压缩支持,同时兼容原来未压缩的数据。
+
+### 评估压缩收益
+
+对于已经存在的表,原来没有采用客户端压缩,如何快速评估采用客户端压缩后有多大收益?
+
+原料:
+* 业务集群:user_cluster,meta配置地址为`${user_cluster_meta_list}`,其中用户表为user_table。
+* 测试集群:test_cluster,meta配置地址为`${test_cluster_meta_list}`。
+* [Shell工具](/overview/shell):使用1.11.3及以上版本;修改配置文件`src/shell/config.ini`,添加访问test_cluster集群的配置项。
+* [Java客户端工具](#java客户端工具):使用1.11.4及以上版本;修改配置文件`pegasus.properties`,设置`meta_servers = ${test_cluster_meta_list}`。
+
+步骤:
+* 使用Shell工具的create命令,在test_cluster集群中新建测试表user_table_no_compress和user_table_zstd_compress:
+```
+./run.sh shell --cluster ${test_cluster_meta_list}
+>>> create user_table_no_compress -p 8 -r 3
+>>> create user_table_zstd_compress -p 8 -r 3
+```
+* 使用Shell工具的copy_data命令,将业务集群的user_table表的部分数据复制到测试集群的user_table_no_compress表中(在复制足够条数后通过Ctrl-C中断执行):
+```
+./run.sh shell --cluster ${user_cluster_meta_list}
+>>> use user_table
+>>> copy_data -c test_cluster -a user_table_no_compress
+```
+* 使用Java客户端工具的copy_data命令,将测试集群user_table_no_compress表的数据复制到user_table_zstd_compress表中,并设置数据写出时采用zstd压缩:
+```
+./PegasusCli file://./pegasus.properties user_table_no_compress \
+    copy_data file://./pegasus.properties user_table_zstd_compress none zstd
+```
+* 使用Shell工具的count_data命令,分别统计两个测试表的数据大小,然后计算压缩率:
+```
+./run.sh shell --cluster ${test_cluster_meta_list}
+>>> use user_table_no_compress 
+>>> count_data -a
+>>> use user_table_zstd_compress 
+>>> count_data -a
+```
+
+### 使用兼容性压缩
+
+业务表原来已经有未压缩的数据,如果应用了客户端压缩,写入新的已压缩的数据,但是hashKey和sortKey保持不变,就会出现未压缩数据和已压缩数据**混合存在**的情况:有的value存储的是未压缩的数据,有的value存储的是已压缩的数据。
+
+这就要求业务端在读数据的时候**保证兼容性**:既能读取未压缩的数据,又能读取已压缩的数据。
+
+基于**未压缩的数据采用zstd进行解压缩时基本都会失败**这一事实,业务端读取的逻辑可以这样:
+* 首先,尝试将客户端读到的value数据进行解压缩,如果成功,则说明是已压缩的数据。
+* 如果上一步解压缩失败,则说明读到的是未压缩的数据,不需要解压。
+
+示例代码:
+```java
+    // decompress the value
+    byte[] decompressedValue = null;
+    try {
+        decompressedValue = ZstdWrapper.decompress(value);
+    } catch (PException e) {
+        // decompress fail
+        decompressedValue = value;
+    }
+```
+
+与此同时,可以使用后台工具将未压缩数据逐渐替换掉为已压缩数据,并在替换过程中保证数据的一致性:扫描表,逐条读取数据,如果数据是未压缩的,则将其转换为已压缩的,使用check_and_set原子操作进行数据替换。
+
+## 客户端连接预热(Warm Up)
+
+我们提供了提供了客户端连接预热(warmup)功能,在进行openTable时提前拉取路由表并建立连接。这样可以避免在该表的第一次rpc调用时,由于执行上述步骤而导致的该次rpc调用过慢的问题。
+
+示例代码:
+```java
+  PegasusTableInterface table = client.openTable(tableName);
+```
+
+# 常见问题
+
diff --git a/_clients/node-client.md b/_clients/node-client.md
new file mode 100755
index 0000000..02f45a9
--- /dev/null
+++ b/_clients/node-client.md
@@ -0,0 +1,258 @@
+---
+title: NodeJs客户端文档
+layout: page
+show_sidebar: false
+menubar: clients_menu
+---
+
+# 安装NodeJs客户端
+项目地址:[Pegasus NodeJS Client](https://github.com/XiaoMi/pegasus-nodejs-client)  
+下载并将客户端依赖添加到package.json中:  
+`npm install pegasus-nodejs-client --save`
+# 创建/关闭客户端
+## create
+```
+let pegasusClient = require('pegasus-nodejs-client');
+/**
+ * Create a client instance
+ * @param   {Object}  configs
+ *          {Array}   configs.metaServers          required
+ *          {String}  configs.metaServers[i]       required
+ *          {Number}  configs.operationTimeout(ms) optional
+ *          {Object}  configs.log                  optional
+ * @return  {Client}  client instance
+ * @throws  {InvalidParamException}
+ */
+client = pegasusClient.create({
+    metaServers: ['127.0.0.1:34601', '127.0.0.1:34602', '127.0.0.1:34603'],
+    operationTimeout : 5000,
+    log : log,
+});
+```
+* metaServers为meta server地址列表,为必填项
+* operationTimeout为本次操作的超时时间,单位是毫秒,默认是1000ms
+* log为日志库实例  
+ * 我们使用日志库 [log4js](https://github.com/log4js-node/log4js-node)
+ * 默认使用的日志配置在`log_config.js`文件中,配置如下:
+ ```
+let filename = "./logs/"+process.pid+"/pegasus-nodejs-client.log";
+let logConfig = {
+    appenders: { 
+      pegasus: {
+        type: "file", 
+        filename: filename, 
+        maxLogSize: 104857600, 
+        backups: 10
+      } 
+    },
+    categories: { 
+      default: { appenders: ["pegasus"], level: "INFO" } 
+    }
+};
+ ```
+ 上述配置表示,会将错误级别等于及高于INFO级别的日志存储在文件中,每个日志文件最大100M,最多保留10个日志文件
+ * 若不想使用默认配置,则需要重新定义如上的logConfig对象,作为创建客户端时的log对象
+* 当参数有误时,会抛出异常,停止后续操作
+
+## close
+```
+// close client when you do not need to use it
+client.close();
+```
+
+# 接口
+## get
+读单行数据
+```
+/**
+ * Get value
+ * @param {String}      tableName
+ * @param {Object}      args
+ *        {Buffer}      args.hashKey      required
+ *        {Buffer}      args.sortKey      required
+ *        {Number}      args.timeout(ms)  optional
+ * @param {Function}    callback
+ * @throws{InvalidParamException} callback is not function
+ */
+ client.get(
+   tableName,  
+   args, 
+   function(err, result){
+     // if get operation succeed, err will be null,
+     // result.hashKey is hashKey, result.sortKey is sortKey, result.value is value
+     // else err will be instance of PException, result will be null
+   }
+ );
+```
+* get操作的必填参数有表名,hashKey,sortKey和callback
+* hashKey,sortKey和value都是Buffer对象,这与pegasus服务端key与value均为byte的语义保持一致
+* 超时时间为可选参数,默认为创建客户端时设定的超时时间
+* 当读操作成功时,callback的err为空,result.value为读到的值
+* 当读操作失败时,callback的result为空
+* 客户端不认为读不到值时错误,因此当读不到值时,err仍为空,result.value为`Buffer('')`
+
+## set
+写单行数据
+```
+/**
+ * Set Value
+ * @param {String}      tableName
+ * @param {Object}      args
+ *        {Buffer}      args.hashKey      required
+ *        {Buffer}      args.sortKey      required
+ *        {Buffer}      args.value        required
+ *        {Number}      args.ttl(s)       optional
+ *        {Number}      args.timeout(ms)  optional
+ * @param {Function}    callback
+ * @throws{InvalidParamException} callback is not function
+ */
+ client.set(
+   tableName,  
+   args, 
+   function(err){
+     // if set operation succeed, err will be null
+     // else err will be instance of PException
+   }
+ );
+```
+* set操作的必填参数有表名,hashKey,sortKey,value和callback
+* ttl的含义为过期时间,单位为秒,默认ttl为0,则表示该数据不过期,若用户设置ttl为86400s,则表示该数据将在1天之后过期,用户在1天之后将无法读取到该数据
+
+## del
+删除单行数据
+```
+/**
+ * Delete value
+ * @param {String}      tableName
+ * @param {Object}      args
+ *        {Buffer}      args.hashKey      required
+ *        {Buffer}      args.sortKey      required
+ *        {Number}      args.timeout(ms)  optional
+ * @param {Function}    callback
+ * @throws{InvalidParamException} callback is not function
+ */
+ client.del(
+   tableName,  
+   args, 
+   function(err){
+     // if set operation succeed, err will be null
+     // else err will be instance of PException
+   }
+ );
+```
+* del操作的必填参数有表名,hashKey,sortKey和callback
+
+## multiGet
+读同一个hashKey下的多行数据
+```
+/**
+ * Multi Get
+ * @param {String}      tableName
+ * @param {Object}      args
+ *        {Buffer}      args.hashKey         required
+ *        {Array}       args.sortKeyArray    required
+ *        {Buffer}      args.sortKeyArray[i] required
+ *        {Number}      args.timeout(ms)     optional
+ *        {Number}      args.maxFetchCount   optional
+ *        {Number}      args.maxFetchSize    optional
+ * @param {Function}    callback
+ * @throws{InvalidParamException} callback is not function
+ */
+ client.multiGet(
+   tableName,  
+   args, 
+   function(err, result){
+     // if operation succeed, err will be null,
+     // result[i].hashKey is hashKey, result[i].sortKey is sortKey, result[i].value is value
+     // else err will be instance of PException, result will be null
+   }
+ );
+```
+* multiGet操作的必填参数为表名,hashKey,sortKey数组和callback
+* 若sortKey数据为空数组,则表示期望获取该hashKey下的所有sortKey的值
+* maxFetchCount为最多获取数据的条数,默认为100
+* maxFetchSize为最大获取数据的大小,默认为1000000字节
+
+## batchGet
+读取一批数据
+```
+/**
+ * Batch Get value
+ * @param {String}      tableName
+ * @param {Array}       argsArray
+ *        {Buffer}      argsArray[i].hashKey      required
+ *        {Buffer}      argsArray[i].sortKey      required
+ *        {Number}      argsArray[i].timeout(ms)  optional
+ * @param {Function}    callback
+ * @throws{InvalidParamException} callback is not function
+ */
+ client.batchGet(
+   tableName,  
+   argsArray, 
+   function(err, result){
+     // err will be always be null, result is {'error': err, 'data': result} array
+     // if batchGet[i] operation succeed, result[i].error will be null
+     // result[i].data.hashKey is hashKey, result[i].data.sortKey is sortKey, result[i].data.value is value
+     // else result[i].error will be instance of PException, result[i].data will be null
+   }
+ );
+```
+* batchGet操作的必填参数为表名,hashKey数组,sortKey数组和callback
+* 与multiGet不同的是,batchGet支持读多个hashKey的值
+* batchGet将等待所有本次batch的所有get操作都返回结果后才返回
+* callback的err总是为null
+* callback的result是一个数组,result[i].error表示第i个get操作的出错情况,result[i].data表示第i个get操作的结果
+
+## multiSet
+写同一个hashKey下的多行数据
+```
+/**
+ * Multi Set
+ * @param {String}      tableName
+ * @param {Object}      args
+ *        {Buffer}      args.hashKey           required
+ *        {Array}       args.sortKeyValueArray required
+ *                      {'key' : sortKey, 'value' : value}
+ *        {Number}      args.timeout(ms)       optional
+ *        {Number}      args.ttl(s)            optional
+ * @param {Function}    callback
+ * @throws{InvalidParamException} callback is not function
+ */
+ client.multiSet(
+   tableName,  
+   args, 
+   function(err){
+     // if set operation succeed, err will be null
+     // else err will be instance of PException
+   }
+ );
+```
+* multiSet操作的必填参数为表名,hashKey,sortKey-value对象数组和callback
+
+## batchSet
+写入一批数据
+```
+/**
+ * Batch Set value
+ * @param {String}      tableName
+ * @param {Array}       argsArray
+ *        {Buffer}      argsArray[i].hashKey      required
+ *        {Buffer}      argsArray[i].sortKey      required
+ *        {Buffer}      argsArray[i].value        required
+ *        {Number}      argsArray[i].ttl          optional
+ *        {Number}      argsArray[i].timeout(ms)  optional
+ * @param {Function}    callback
+ * @throws{InvalidParamException} callback is not function
+ */
+ client.batchSet(
+   tableName,  
+   argsArray, 
+   function(err, result){
+     // err will be always be null, result is {'error': err} array
+     // if batchSet[i] operation succeed, result[i].error will be null
+     // else result[i].error will be instance of PException
+   }
+ );
+```
+* batchSet操作的必填参数为表名,hashKey-sortKey-value对象数组和callback
+* callback的err总是为null,result[i].error表示第i个set操作的出错情况
diff --git a/_clients/python-client.md b/_clients/python-client.md
new file mode 100755
index 0000000..76a7651
--- /dev/null
+++ b/_clients/python-client.md
@@ -0,0 +1,468 @@
+---
+title: Python客户端文档
+layout: page
+show_sidebar: false
+menubar: clients_menu
+---
+
+# pegasus python client
+
+## 项目地址
+
+<https://github.com/XiaoMi/pegasus-python-client>
+
+## 版本要求
+
+Python 2.*
+
+## 安装
+
+`pip install pypegasus`
+
+## 使用
+
+pegasus python client使用了[twisted](https://github.com/twisted/twisted), 编写的代码会带有twisted的影子。
+
+### 示例
+
+完整的示例请参考[sample](https://github.com/XiaoMi/pegasus-python-client/blob/master/sample.py)。以下是简单的示例:
+
+```
+#!/usr/bin/env python
+# coding:utf-8
+
+from pypegasus.pgclient import Pegasus
+
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks
+
+
+@inlineCallbacks
+def basic_test():
+    # init
+    c = Pegasus(['127.0.0.1:34601', '127.0.0.1:34602'], 'temp')
+
+    suc = yield c.init()
+    if not suc:
+        reactor.stop()
+        print('ERROR: connect pegasus server failed')
+        return
+
+    # set
+    try:
+        ret = yield c.set('hkey1', 'skey1', 'value', 0, 500)
+        print('set ret: ', ret)
+    except Exception as e:
+        print(e)
+
+    # get
+    ret = yield c.get('hkey1', 'skey1')
+    print('get ret: ', ret)
+
+    reactor.stop()
+
+
+if __name__ == "__main__":
+    reactor.callWhenRunning(basic_test)
+    reactor.run()
+```
+
+### log配置文件
+
+pegasus python client使用了[logging](https://docs.python.org/2/library/logging.html)日志包,默认配置如下:
+
+```
+[loggers]
+keys=root
+[logger_root]
+level=INFO
+handlers=hand01
+propagate=0
+[handlers]
+keys=hand01
+[handler_hand01]
+class=handlers.RotatingFileHandler
+formatter=form01
+args=('pegasus.log', 'a', 100*1024*1024, 10)
+[formatters]
+keys=form01
+[formatter_form01]
+format=%(asctime)s [%(thread)d] [%(levelname)s] %(filename)s:%(lineno)d %(message)s
+datefmt=%Y-%m-%d %H:%M:%S
+```
+
+如果用户有定制需求,可以在自己的代码目录添加配置文件`logger.conf`
+
+### API说明
+
+#### 初始化
+
+初始化先构造Pegasus对象,在使用init函数完成初始化:
+
+```
+class Pegasus(object):
+    """
+    Pegasus client class.
+    """
+    
+    def __init__(self, meta_addrs=None, table_name='',
+                 timeout=DEFAULT_TIMEOUT):
+        """
+        :param meta_addrs: (list) pagasus meta servers list.
+                           example: ['127.0.0.1:34601', '127.0.0.1:34602', '127.0.0.1:34603']
+        :param table_name: (str) table name/app name used in pegasus.
+        :param timeout: (int) default timeout in milliseconds when communicate with meta sever and replica server.
+        """
+```
+
+```
+    def init(self):
+        """
+        Initialize the client before you can use it.
+
+        :return: (DeferredList) True when initialized succeed, others when failed.
+        """
+```
+
+#### ttl
+
+判断key的剩余的ttl时间
+
+```
+def ttl(self, hash_key, sort_key, timeout=0):
+    """
+    Get ttl(time to live) of the data.
+
+    :param hash_key: (str) which hash key used for this API.
+    :param sort_key: (str) which sort key used for this API.
+    :param timeout: (int) how long will the operation timeout in milliseconds.
+                    if timeout > 0, it is a timeout value for current operation,
+                    else the timeout value specified to create the instance will be used.
+    :return: (tuple<error_types.code.value, int>) (code, ttl)
+             code: error_types.ERR_OK.value when data exist, error_types.ERR_OBJECT_NOT_FOUND.value when data not found.
+             ttl: in seconds, -1 means forever.
+    """
+```
+
+#### exist
+
+判断key是否存在
+
+```
+def exist(self, hash_key, sort_key, timeout=0):
+    """
+    Check value exist.
+
+    :param hash_key: (str) which hash key used for this API.
+    :param sort_key: (str) which sort key used for this API.
+    :param timeout: (int) how long will the operation timeout in milliseconds.
+                    if timeout > 0, it is a timeout value for current operation,
+                    else the timeout value specified to create the instance will be used.
+    :return: (tuple<error_types.code.value, None>) (code, ign)
+             code: error_types.ERR_OK.value when data exist, error_types.ERR_OBJECT_NOT_FOUND.value when data not found.
+             ign: useless, should be ignored.
+    """
+```
+
+#### set
+
+插入一条数据(若已存在则会覆盖)
+
+```
+def set(self, hash_key, sort_key, value, ttl=0, timeout=0):
+    """
+    Set value to be stored in <hash_key, sort_key>.
+
+    :param hash_key: (str) which hash key used for this API.
+    :param sort_key: (str) which sort key used for this API.
+    :param value: (str) value to be stored under <hash_key, sort_key>.
+    :param ttl: (int) ttl(time to live) in seconds of this data.
+    :param timeout: (int) how long will the operation timeout in milliseconds.
+                    if timeout > 0, it is a timeout value for current operation,
+                    else the timeout value specified to create the instance will be used.
+    :return: (tuple<error_types.code.value, None>) (code, ign)
+             code: error_types.ERR_OK.value when data stored succeed.
+             ign: useless, should be ignored.
+    """
+```
+
+#### multi_set
+
+同时写一条hashkey的多条sortkey数据
+
+```
+def multi_set(self, hash_key, sortkey_value_dict, ttl=0, timeout=0):
+    """
+    Set multiple sort_keys-values under hash_key to be stored.
+
+    :param hash_key: (str) which hash key used for this API.
+    :param sortkey_value_dict: (dict) <sort_key, value> pairs in dict.
+    :param ttl: (int) ttl(time to live) in seconds of these data.
+    :param timeout: (int) how long will the operation timeout in milliseconds.
+                    if timeout > 0, it is a timeout value for current operation,
+                    else the timeout value specified to create the instance will be used.
+    :return: (tuple<error_types.code.value, _>) (code, ign)
+             code: error_types.ERR_OK.value when data stored succeed.
+             ign: useless, should be ignored.
+    """
+```
+
+#### get
+
+获取一条数据
+
+```
+def get(self, hash_key, sort_key, timeout=0):
+    """
+    Get value stored in <hash_key, sort_key>.
+
+    :param hash_key: (str) which hash key used for this API.
+    :param sort_key: (str) which sort key used for this API.
+    :param timeout: (int) how long will the operation timeout in milliseconds.
+                    if timeout > 0, it is a timeout value for current operation,
+                    else the timeout value specified to create the instance will be used.
+    :return: (tuple<error_types.code.value, str>) (code, value).
+             code: error_types.ERR_OK.value when data got succeed, error_types.ERR_OBJECT_NOT_FOUND.value when data not found.
+             value: data stored in this <hash_key, sort_key>
+    """
+```
+
+#### multi_get
+
+同时读一条hashkey的多条sortkey数据
+
+```
+def multi_get(self, hash_key,
+              sortkey_set,
+              max_kv_count=100,
+              max_kv_size=1000000,
+              no_value=False,
+              timeout=0):
+    """
+    Get multiple values stored in <hash_key, sortkey> pairs.
+
+    :param hash_key: (str) which hash key used for this API.
+    :param sortkey_set: (set) sort keys in set.
+    :param max_kv_count: (int) max count of k-v pairs to be fetched. max_fetch_count <= 0 means no limit.
+    :param max_kv_size: (int) max total data size of k-v pairs to be fetched. max_fetch_size <= 0 means no limit.
+    :param no_value: (bool) whether to fetch value of these keys.
+    :param timeout: (int) how long will the operation timeout in milliseconds.
+                    if timeout > 0, it is a timeout value for current operation,
+                    else the timeout value specified to create the instance will be used.
+    :return: (tuple<error_types.code.value, dict>) (code, kvs)
+             code: error_types.ERR_OK.value when data got succeed.
+             kvs: <sort_key, value> pairs in dict.
+    """
+```
+
+#### multi_get_opt
+
+同时读一条hashkey的多条sortkey数据, 读取的数据根据`multi_get_options`参数指定的模式确定。
+
+```
+def multi_get_opt(self, hash_key,
+                  start_sort_key, stop_sort_key,
+                  multi_get_options,
+                  max_kv_count=100,
+                  max_kv_size=1000000,
+                  timeout=0):
+    """
+    Get multiple values stored in hash_key, and sort key range in [start_sort_key, stop_sort_key) as default.
+
+    :param hash_key: (str) which hash key used for this API.
+    :param start_sort_key: (str) returned k-v pairs is start from start_sort_key.
+    :param stop_sort_key: (str) returned k-v pairs is stop at stop_sort_key.
+    :param multi_get_options: (MultiGetOptions) configurable multi_get options.
+    :param max_kv_count: (int) max count of k-v pairs to be fetched. max_fetch_count <= 0 means no limit.
+    :param max_kv_size: (int) max total data size of k-v pairs to be fetched. max_fetch_size <= 0 means no limit.
+    :param timeout: (int) how long will the operation timeout in milliseconds.
+                    if timeout > 0, it is a timeout value for current operation,
+                    else the timeout value specified to create the instance will be used.
+    :return: (tuple<error_types.code.value, dict>) (code, kvs)
+             code: error_types.ERR_OK.value when data got succeed.
+             kvs: <sort_key, value> pairs in dict.
+    """
+```
+
+其中,`MultiGetOptions`可以指定sortkey的范围、是否包含边界、子串匹配、是否返回value、是否逆序等,具体定义如下:
+
+```
+class MultiGetOptions(object):
+    """
+    configurable options for multi_get.
+    """
+
+    def __init__(self):
+        self.start_inclusive = True
+        self.stop_inclusive = False
+        self.sortkey_filter_type = filter_type.FT_NO_FILTER
+        self.sortkey_filter_pattern = ""
+        self.no_value = False
+        self.reverse = False
+
+class filter_type:
+  FT_NO_FILTER = 0
+  FT_MATCH_ANYWHERE = 1
+  FT_MATCH_PREFIX = 2
+  FT_MATCH_POSTFIX = 3
+```
+
+#### remove
+
+删除一条数据
+
+```
+def remove(self, hash_key, sort_key, timeout=0):
+    """
+    Remove the entire <hash_key, sort_key>-value in pegasus.
+
+    :param hash_key: (str) which hash key used for this API.
+    :param sort_key: (str) which sort key used for this API.
+    :param timeout: (int) how long will the operation timeout in milliseconds.
+                    if timeout > 0, it is a timeout value for current operation,
+                    else the timeout value specified to create the instance will be used.
+    :return: (tuple<error_types.code.value, None>) (code, ign)
+             code: error_types.ERR_OK.value when data stored succeed.
+             ign: useless, should be ignored.
+    """
+```
+
+#### multi_del
+
+批量删除一个hashkey下的多条sortkey数据
+
+```
+def multi_del(self, hash_key, sortkey_set, timeout=0):
+    """
+    Remove multiple entire <hash_key, sort_key>-values in pegasus.
+
+    :param hash_key: (str) which hash key used for this API.
+    :param sortkey_set: (set) sort keys in set.
+    :param timeout: (int) how long will the operation timeout in milliseconds.
+                    if timeout > 0, it is a timeout value for current operation,
+                    else the timeout value specified to create the instance will be used.
+    :return: (tuple<error_types.code.value, int>) (code, count).
+             code: error_types.ERR_OK.value when data got succeed.
+             count: count of deleted k-v pairs.
+    """
+```
+
+#### sort_key_count
+
+获取一个hashkey下的sortkey数量
+
+```
+def sort_key_count(self, hash_key, timeout=0):
+    """
+    Get the total sort key count under the hash_key.
+
+    :param hash_key: (str) which hash key used for this API.
+    :param timeout: (int) how long will the operation timeout in milliseconds.
+                    if timeout > 0, it is a timeout value for current operation,
+                    else the timeout value specified to create the instance will be used.
+    :return: (tuple<error_types.code.value, count>) (code, count)
+             code: error_types.ERR_OK.value when data got succeed, error_types.ERR_OBJECT_NOT_FOUND.value when data not found.
+             value: total sort key count under the hash_key.
+    """
+```
+
+#### get_sort_keys
+
+获取一个hashkey下的sortkey值
+
+```
+def get_sort_keys(self, hash_key,
+                  max_kv_count=100,
+                  max_kv_size=1000000,
+                  timeout=0):
+    """
+    Get multiple sort keys under hash_key.
+
+    :param hash_key: (str) which hash key used for this API.
+    :param max_kv_count: (int) max count of k-v pairs to be fetched. max_fetch_count <= 0 means no limit.
+    :param max_kv_size: (int) max total data size of k-v pairs to be fetched. max_fetch_size <= 0 means no limit.
+    :param timeout: (int) how long will the operation timeout in milliseconds.
+                    if timeout > 0, it is a timeout value for current operation,
+                    else the timeout value specified to create the instance will be used.
+    :return: (tuple<error_types.code.value, set>) (code, ks)
+             code: error_types.ERR_OK.value when data got succeed.
+             ks: <sort_key, ign> pairs in dict, ign will always be empty str.
+    """
+```
+
+#### get_scanner
+
+获取scanner对象,用于指定范围的数据扫描。可以通过`scan_options`参数指定扫描的模式。
+
+```
+def get_scanner(self, hash_key,
+                start_sort_key, stop_sort_key,
+                scan_options):
+    """
+    Get scanner for hash_key, start from start_sort_key, and stop at stop_sort_key.
+    Whether the scanner include the start_sort_key and stop_sort_key is configurable by scan_options
+
+    :param hash_key: (str) which hash key used for this API.
+    :param start_sort_key: (str) returned scanner is start from start_sort_key.
+    :param stop_sort_key: (str) returned scanner is stop at stop_sort_key.
+    :param scan_options: (ScanOptions) configurable scan options.
+    :return: (PegasusScanner) scanner, instance of PegasusScanner.
+    """
+```
+
+其中,`ScanOptions`可以指定是否包含边界、超时时间、一次从replica server批量获取的sortkey-value数量等,具体定义如下:
+
+```
+class ScanOptions(object):
+    """
+    configurable options for scan.
+    """
+
+    def __init__(self):
+        self.timeout_millis = 5000
+        self.batch_size = 1000
+        self.start_inclusive = True
+        self.stop_inclusive = False
+        self.snapshot = None                   # for future use
+```
+
+#### get_unordered_scanners
+
+一次性获取多个scanner,用于整个table的数据扫描。可以通过`scan_options`参数指定扫描的模式。
+
+```
+def get_unordered_scanners(self, max_split_count, scan_options):
+    """
+    Get scanners for the whole pegasus table.
+
+    :param max_split_count: (int) max count of scanners will be returned.
+    :param scan_options: (ScanOptions) configurable scan options.
+    :return: (list) instance of PegasusScanner list.
+             each scanner in this list can scan separate part of the whole pegasus table.
+    """
+```
+
+#### scanner对象
+
+用于数据扫描的对象,由`get_scanner`和`get_unordered_scanners`返回。使用它的`next`函数执行扫描过程。
+
+```
+class PegasusScanner(object):
+    """
+    Pegasus scanner class, used for scanning data in pegasus table.
+    """
+```
+
+#### get_next
+
+获取扫描得到的数据,需要循环执行,直到返回`None`结束扫描。
+
+```
+def get_next(self):
+    """
+    scan the next k-v pair for the scanner.
+    
+    :return: (tuple<tuple<hash_key, sort_key>, value> or None)
+             all the sort_keys returned by this API are in ascend order.
+    """
+```
diff --git a/_clients/scala-client.md b/_clients/scala-client.md
new file mode 100755
index 0000000..6656aba
--- /dev/null
+++ b/_clients/scala-client.md
@@ -0,0 +1,372 @@
+---
+title: Scala客户端文档
+layout: page
+show_sidebar: false
+menubar: clients_menu
+---
+
+
+# 获取客户端  
+项目地址:[Pegasus scala client](https://github.com/xiaomi/pegasus-scala-client)  
+下载:
+```bash
+git clone https://github.com/XiaoMi/pegasus-scala-client.git
+cd pegasus-scala-client
+```
+选择所使用的版本并构建,建议使用master版本。同时注意,scala客户端构建依赖[Java客户端](https://github.com/XiaoMi/pegasus-java-client),请参考[获取Java客户端](/clients/java-client#获取java客户端)在项目中添加Java依赖。你可以打包成Jar包进行使用:
+```bash
+sbt package
+```
+或者,安装到本地的sbt repository,方便在sbt项目中使用:
+```bash
+sbt publish-local
+```
+或者,安装到本地的maven repository:
+```bash
+sbt publish-m2
+```
+项目默认使用scala-2.11进行构建,打包发布时则同时发布2.11版本(pegasus-scala-client_2.11)和2.12版本(pegasus-scala-client_2.12),如果你的项目使用sbt构建,则可配置为:
+```sbt
+//使用sbt仓库,不需要添加后缀,默认使用当前scala版本号,即使用2.12
+scalaVersion := "2.12.8"
+libraryDependencies ++= Seq(
+    "com.xiaomi.infra" %% "pegasus-scala-client" % "1.11.4-1-SNAPSHOT"
+)
+```
+或者配置为:
+```sbt
+//使用maven仓库(你可以使用resolvers ++= Seq()添加自定义maven仓库),需要添加后缀
+scalaVersion := "2.12.8"
+libraryDependencies ++= Seq(
+    "com.xiaomi.infra" % "pegasus-scala-client_2.11" % "1.11.4-1-SNAPSHOT"
+)
+```
+如果你的项目通过maven构建,则可通过maven配置在项目中使用,例如:
+```xml
+<dependency>
+    <groupId>com.xiaomi.infra</groupId>
+    <artifactId>pegasus-scala-client_2.11</artifactId>
+    <version>1.11.4-1</version>
+</dependency>
+```
+# 使用客户端
+
+## 获取实例
+通过指定server配置信息获取实例,Scala提供两种获取实例的接口:  
+**1、文件路径作为配置参数:**  参见[Java客户端文件配置](/clients/java-client#配置文件)  
+```scala
+def createClient(configPath: String): ScalaPegasusClient
+```
+例如:
+```scala
+val pegasusClient = ScalaPegasusClientFactory.createClient("resource:///pegasus.properties")
+```
+**2、Properties对象作为配置:**  
+```scala
+def createClient(props: Properties): ScalaPegasusClient
+```
+例如:
+```scala
+Properties pegasusConfig = new Properties();
+pegasusConfig.setProperty("meta_servers", "127.0.0.1:34601,127.0.0.1:34602,127.0.0.1:34603");
+pegasusConfig.setProperty("operation_timeout", 100);
+val pegasusClient = ScalaPegasusClientFactory.createClient(pegasusConfig)
+```
+## 数据操作
+注意:调用函数前请确认导入`Serializers._`,详情参阅[实现原理](#实现原理)
+```scala
+val hashKey = 12345L
+pegasusClient.set(table, hashKey, "sort_1", "value_1")
+val value = pegasusClient.get(table, hashKey, "sort_1").as[String]
+pegasusClient.del(table, hashKey, "sort_1")
+pegasusClient.exists(table, hashKey, "sort_1") 
+pegasusClient.sortKeyCount(table, hashKey)
+pegasusClient.close
+```
+# 接口定义
+scala的客户端类地址为:`com.xiaomi.infra.pegasus.scalaclient`,主要包括以下四个类:
+
+| 类名                      | 功能                                                   |
+| ------------------------- | ------------------------------------------------------ |
+| ScalaPegasusClientFactory | Client工厂类,用于创建Client实例                       |
+| ScalaPegasusClient        | Client类,封装了各种**同步API**,也可用于创建Table实例 |
+| ScalaPegasusTable         | Table类,封装了操作单个Table数据的**同步API**          |
+| ScalaPegasusAsyncTable    | Table类,封装了操作单个Table数据的**异步API**          |
+
+用户可以选择使用Client类(ScalaPegasusClient)或者是Table类(ScalaPegasusTable或者ScalaPegasusAsyncTable)存取数据,区别如下:
+  * Client类直接在参数中指定表名,省去了打开表的动作,使用更便捷。
+  * Table类同时支持**同步和异步API**,而Client类只支持**同步API**。
+  * Table类可以为每个操作设置单独的超时,而Client类无法单独指定超时,只能使用配置文件中的默认超时。
+  * Table类的超时更准确,而Client类在首次读写请求时可能需要在内部初始化Table对象,所以首次读写的超时可能不太准确。
+## ScalaPegasusClient接口
+### 实现原理
+`ScalaPegasusClient`接口通过持有`ScalaPegasusTable`实现对特定表的访问,而`ScalaPegasusTable`实际是封装了Java client的接口`PegasusTableInterface`而实现的。函数形式如下所示:
+```scala
+def get[H, S](table: String, hashKey: H, sortKey: S)(implicit hSer: SER[H], sSer: SER[S]) = {
+    getTable(table).get(hashKey, sortKey)
+}
+```
+每一个数据表的操作函数都被定义为泛型函数,参数列表`(table: String, hashKey: H, sortKey: S)`是实际传入的参数,同时使用隐式参数`(implicit hSer: SER[H], sSer: SER[S])`完成对参数列表`(table: String, hashKey: H, sortKey: S)`泛型的转换。其中SER[H]是类`Serializers`的泛型声明,该类包含对不同泛型对象的隐式转换函数(转换成Java client中`PegasusTableInterface`的`byte[]参数`,在scala中对应为`Array[Byte]`,例子展示的是当泛型在使用的时候被定义为`String`时的隐式转换函数:
+```scala
+implicit object Utf8String extends Serializer[String] {
+    override def serialize(obj: String): Array[Byte] = if (obj == null) null else obj.getBytes("UTF-8")
+    override def deserialize(bytes: Array[Byte]): String = if (bytes == null) null else  new String(bytes, "UTF-8")
+}
+```
+客户端在调用`ScalaPegasusClient`提供的方法时,当对第一个参数列表的泛型参数传入`String`类型变量的时候,将被自动转换为`Array[Byte]`类型变量,并传入`PegasusTableInterface`的对应方法中。请确保包含`Serializers._`,否则无法完成参数的类型转换,你可以使用:
+```scala
+import com.xiaomi.infra.pegasus.scalaclient.Serializers._
+```
+导入依赖,目前接受的自动类型转换包括`String`、`Boolean`、`Int`、`Long`、`Short`、`Double`,这些类型可自动转换为`Array[Byte]`。
+### API功能
+#### exists
+判断key是否存在,参见[Java客户端文档#exist](/clients/java-client#exist)
+```scala
+def exists[H, S](table: String, hashKey: H, sortKey: S)
+```
+table:表名,通常为`String`类型  
+hashKey:通常为`String`类型  
+sortKey:通常为`String`类型  
+return: 返回是否存在,`boolean`类型
+#### sortKeyCount
+获取一个hashkey下的sortkey值,参见[Java客户端文档#sortKeyCount](/clients/java-client#sortkeycount)
+```scala
+def sortKeyCount[H](table: String, hashKey: H)
+```
+table:表名,通常为`String`类型  
+hashKey:通常为`String`类型  
+return:返回sortKeys个数,`long`类型
+#### get
+获取一条数据,参见[Java客户端文档#get](/clients/java-client#get)
+```scala
+def get[H, S](table: String, hashKey: H, sortKey: S)
+```
+table:表名,通常为`String`类型  
+hashKey:通常为`String`类型  
+sortKey:通常为`String`类型  
+return:返回获取值,`Array[Byte]`类型,你可以使用`as[String]`转换为`String`类型  
+#### batchGet
+读取一批数据,对get函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常。如果抛出了异常,则values中的结果是未定义的,参见[Java客户端文档#batchGet](/clients/java-client#batchget)
+```scala
+def batchGet[H, S](table: String, keys: List[PegasusKey[H, S]])
+```
+table:表名,通常为`String`类型  
+keys:PegasusKey列表,由hashKey和SortKey组成  
+return:返回获取值列表,`PegasusResultList`类型
+#### batchGet2
+读取一批数据,对get函数的批量封装。该函数并发地向server发送异步请求,但与上面batchGet不同的是,无论请求成功还是失败,它都会等待所有请求结束,参见[Java客户端文档#batchGet2](/clients/java-client#batchget)
+```scala
+def batchGet2[H, S](table: String, keys: Seq[PegasusKey[H, S]])
+```
+table:表名,通常为`String`类型  
+keys:PegasusKey列表,有hashKey和SortKey组成  
+return:返回获取值列表,`PegasusResultList`类型
+#### multiGet
+Java client包含多种multiGet接口,提供读同一HashKey下的多行数据功能,这里封装的是:
+```Java
+public boolean multiGet(String tableName, byte[] hashKey, 
+                        List<byte[]> sortKeys, int maxFetchCount, 
+                        int maxFetchSize, List<Pair<byte[], byte[]>> values) throws PException;
+```
+支持最大数据量`maxFetchCount`和最大数据大小`maxFetchSize`的参数设置,参见[Java客户端文档#multiGet](/clients/java-client#multiget)
+```scala
+def multiGet[H, S](table: String, hashKey: H, sortKeys: Seq[S],
+            maxFetchCount: Int = 100, maxFetchSize: Int = 1000000)
+```
+table:表名,通常为`String`类型  
+hashKey:通常为`String`类型  
+sortKeys:sortKey列表  
+maxFetchCount:最大获取数据量,这里默认为100  
+maxFetchSize:最大获取数据值大小,这里默认为1000000字节  
+return:返回获取值列表,`convertMultiGetResult`类型
+#### multiGetRange
+Java client包含多种multiGet接口,提供读同一HashKey下的多行数据功能,这里封装的是:
+```java
+public boolean multiGet(String tableName, byte[] hashKey,
+                    byte[] startSortKey, byte[] stopSortKey, MultiGetOptions options,
+                    int maxFetchCount, int maxFetchSize,
+                    List<Pair<byte[], byte[]>> values) throws PException;
+```
+可以支持SortKey的范围查询和条件过滤,只读取满足特定条件的数据,参见[Java客户端文档#multiGet](/clients/java-client#multiget)
+```scala
+def multiGetRange[H, S](hashKey: H, startSortKey: S, stopSortKey: S, 
+                        options: Options.MultiGet,
+                        maxFetchCount: Int = 100, maxFetchSize: Int = 1000000, 
+                        timeout: Duration = 0 milli)
+```
+table:表名,通常为`String`类型  
+hashKey:hashKey,通常为`String`类型  
+startSortKey:sortKey范围的起始值  
+stopSortKey:sortKey范围的终止值  
+options:查询条件  
+maxFetchCount:最大数据量,默认为100  
+maxFetchSize:最大数据值大小,默认为1000000字节  
+timeout:获取数据超时时间,默认为0,表示使用配置文件中的数值  
+return:返回获取值列表,`convertMultiGetResult`类型
+#### batchMultiGet
+对multiGet函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常。如果抛出了异常,则values中的结果是未定义的,参见[Java客户端文档#batchMultiGet](/clients/java-client#batchmultiget)
+```scala
+def batchMultiGet[H, S](keys: Seq[(H, Seq[S])], timeout: Duration = 0 milli)
+```
+keys:hashKey-sortKeys列表,如:`Seq(("1",Seq("1","2")),("1",Seq("1","2")))`  
+timeout:获取数据超时时间,默认为0,表示使用配置文件中的数值  
+return:返回获取值列表,`List`类型  
+#### batchMultiGet2
+对multiGet函数的批量封装。该函数并发地向server发送异步请求,并等待结果。但与上面batchMultiGet不同的是,无论请求成功还是失败,它都会等待所有请求结束,参见[Java客户端文档#batchMultiGet2](/clients/java-client#batchmultiget2)
+```scala
+def batchMultiGet2[H, S](keys: Seq[(H, Seq[S])], timeout: Duration = 0 milli)
+```
+keys:hashKey-sortKeys列表,如:`Seq(("1",Seq("1","2")),("1",Seq("1","2")))`  
+timeout:获取数据超时时间,默认为0,表示使用配置文件中的数值  
+return:返回获取值列表,`List`类型  
+#### set
+写单行数据
+```scala
+def set[H, S, V](hashKey: H, sortKey: S, value: V, ttl: Duration = 0 second, timeout: Duration = 0 milli)
+```
+hashKey:通常为`String`类型  
+sortKey:通常为`String`类型  
+value:对应key的写入值,通常为`String`类型  
+ttl:写入值保留时间,默认为0,表示永久保留  
+timeout:获取数据超时时间,默认为0,表示使用配置文件中的数值  
+return:无返回值  
+#### batchSet
+写一批数据,对set函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常,参见[Java客户端文档#batchSet](/clients/java-client#batchset)
+```scala
+def batchSet[H, S, V](table: String, items: Seq[SetItem[H, S, V]])
+```
+table:表名,通常为`String`类型  
+items:写入值列表,由hashKey、sortKey、value组成  
+return:请求成功的个数(该方法不是原子的,有可能出现部分成功部分失败的情况,用户可以选择只使用成功的结果)
+#### batchSet2
+对set函数的批量封装。该函数并发地向server发送异步请求,并等待结果。但与上面batchSet不同的是,无论请求成功还是失败,它都会等待所有请求结束,参见[Java客户端文档#batchSet2](/clients/java-client#batchset2)
+#### multiSet
+Java client有两种接口,提供写同一HashKey下的多行数据,这里封装的是:
+```java
+public void multiSet(String tableName, byte[] hashKey, 
+                    List<Pair<byte[], byte[]>> values, 
+                    int ttl_seconds) throws PException;
+```
+支持数据过期时间设定
+```scala
+def multiSet[H, S, V](table: String, hashKey: H, values: Seq[(S, V)], ttl: Duration = 0 second)
+```
+table:表名,通常为`String`类型  
+hashKey:通常为`String`类型  
+value:写入值列表,由sortkey、value组成,如`Seq(("hashKey1","sortKey1"),("hashKey2","sortKey2"))`  
+ttl:写入值保留时间,默认为0,表示永久保留  
+return:无返回值
+#### batchMultitSet
+对multiSet函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常,参见[Java客户端文档#batchMultiSet](/clients/java-client#batchmultiset)
+```scala
+def batchMultitSet[H, S, V](table: String, items: Seq[HashKeyData[H, S, V]], ttl: Duration = 0 second)
+```
+table:表名,通常为`String`类型  
+items:批量写入数据列表  
+ttl:写入值保留时间,默认为0,表示永久保留  
+return:请求成功的个数(该方法不是原子的,有可能出现部分成功部分失败的情况,用户可以选择只使用成功的结果)
+#### batchMultitSet2
+对multiSet函数的批量封装。该函数并发地向server发送异步请求,并等待结果。但与上面batchMultiSet不同的是,无论请求成功还是失败,它都会等待所有请求结束,参见[Java客户端文档#batchMultitSet2](/clients/java-client#batchmultiset2)
+```scala
+def batchMultiSet2[H, S, V](table: String, items: Seq[HashKeyData[H, S, V]], ttl: Duration = 0 second)
+```
+table:表名,通常为`String`类型  
+items:批量写入数据列表  
+ttl:写入值保留时间,默认为0,表示永久保留  
+return:请求成功的个数(该方法不是原子的,有可能出现部分成功部分失败的情况,用户可以选择只使用成功的结果)
+#### del
+删除单行数据,参见[Java客户端文档#del](/clients/java-client#del)
+```scala
+def del[H, S](table: String, hashKey: H, sortKey: S)
+```
+table:表名,通常为`String`类型  
+hashKey:通常为`String`类型  
+sortkey:通常为`String`类型  
+return:无返回值  
+#### batchDel
+删除一批数据,对del函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常,参见[Java客户端文档#batchDel](/clients/java-client#batchdel)
+```scala
+batchDel[H, S](table: String, keys: Seq[PegasusKey[H, S]])
+```
+table:表名,通常为`String`类型  
+keys:键值列表,由hashKey和sortKey组成  
+return:请求成功的个数(该方法不是原子的,有可能出现部分成功部分失败的情况,用户可以选择只使用成功的结果)
+#### batchDel2
+对del函数的批量封装。该函数并发地向server发送异步请求,并等待结果。但与上面batchDel不同的是,无论请求成功还是失败,它都会等待所有请求结束,参见[Java客户端文档#batchDel2](/clients/java-client#batchdel2)
+```scala
+def batchDel2[H, S](table: String, keys: Seq[PegasusKey[H, S]])
+```
+#### multiDel
+删同一HashKey下的多行数据,参见[Java客户端文档#multiDel](/clients/java-client#multidel)
+```scala
+def multiDel[H, S](table: String, hashKey: H, sortKeys: Seq[S])
+```
+table:表名,通常为`String`类型  
+hashKey:通常为`String`类型  
+sortKeys:sortKey列表  
+return:无返回值  
+#### batchMultiDel
+对multiDel函数的批量封装。该函数并发地向server发送异步请求,并等待结果。如果有任意一个请求失败,就提前终止并抛出异常,参见[Java客户端文档#batchMultiDel](/clients/java-client#batchmultidel)
+```scala
+def batchMultiDel[H, S](table: String, keys: Seq[(H, Seq[S])])
+```
+table:表名,通常为`String`类型  
+keys:键列表,由hashKey、sortKeys组成,如`Seq(("hashKey1",(sortKey1,sortKey2),("hashKey2",(sortKey3,sortKey4))`  
+return:无返回值
+#### batchMultiDel2
+对del函数的批量封装。该函数并发地向server发送异步请求,并等待结果。但与上面batchMultiDel不同的是,无论请求成功还是失败,它都会等待所有请求结束,参见[Java客户端文档#batchMultiDel2](/clients/java-client#batchmultidel2)
+```scala
+def batchMultiDel2[H, S](table: String, keys: Seq[(H, Seq[S])])
+```
+table:表名,通常为`String`类型  
+keys:键列表,由hashKey、sortKeys组成,如`Seq(("hashKey1",(sortKey1,sortKey2),("hashKey2",(sortKey3,sortKey4))`  
+return:无返回值
+#### ttl
+获取单行数据的TTL时间。TTL表示Time To Live,表示该数据还能存活多久。如果超过存活时间,数据就读不到了,参见[Java客户端文档#ttl](/clients/java-client#ttl)
+```scala
+def ttl[H, S](table: String, hashKey: H, sortKey: S)
+```
+table:表名,通常为`String`类型  
+hashKey:通常为`String`类型  
+sortKeys:通常为`String`类型  
+return:TTL时间,单位为秒。如果该数据没有设置TTL,返回-1;如果该数据不存在,返回-2
+#### incr
+单行原子增(减)操作,详细说明参见[单行原子操作](/api/single-atomic),该操作先将key所指向的value的字节串转换为int64类型(实现上类似于Java的Long.parseLong()函数),然后加上increment,将结果转换为字节串设置为新值。当参数increment为正数时,即原子加;当参数increment为负数时,即原子减,参见[Java客户端文档#incr](/clients/java-client#incr)。
+```scala
+def incr[H, S](table: String, hashKey: H, sortKey: S, increment: Long, ttl: Duration = 0 milli)
+```
+table:表名,通常为`String`类型  
+hashKey:通常为`String`类型  
+sortKey:通常为`String`类型  
+increment:增加值  
+ttl:值保留时间,默认为0,表示永久保留  
+return:操作成功后的新值
+## ScalaPegasusTable接口
+ScalaPegasusTable接口提供的方法均为同步API,`ScalaPegasusClient`接口即默认封装该接口,详细API信息参见[ScalaPegasusClient接口](#scalapegasusclient接口)
+## ScalaPegasusAsyncTable
+ScalaPegasusAsyncTable接口提供的方法均为异步API,封装了java client的异步接口。对应API功能可参考[ScalaPegasusClient接口](#scalapegasusclient接口)和[Java客户端文档#PegasusTableInterface接口](/clients/java-client#pegasustableinterface接口),接口封装形式如:
+```scala
+@throws[PException]
+    def multiGet[H, S](hashKey: H, sortKeys: Seq[S], maxFetchCount: Int = 100, maxFetchSize: Int = 1000000, timeout: Duration = 0 milli)
+            (implicit hSer: SER[H], sSer: SER[S]): Future[MultiGetResult[S, Array[Byte]]] = {
+        val result = table.asyncMultiGet(hashKey, sortKeys, maxFetchCount, maxFetchSize, timeout)
+        toScala(result)(convertMultiGetResult[S])
+    }
+```
+其中`table.asyncMultiGet(hashKey, sortKeys, maxFetchCount, maxFetchSize, timeout)`即Java client接口,参数传递原理参见[实现原理](#实现原理),`toScala(result)(convertMultiGetResult[S])`的完整形式如下:
+```scala
+implicit private [scalaclient] def toScala[A, B](future: NFuture[A])(implicit f: A => B): Future[B] = {
+        val promise = Promise[B]()
+        future.addListener(new GenericFutureListener[NFuture[_ >: A]] {
+            override def operationComplete(future: NFuture[_ >: A]): Unit = {
+                if (future.isSuccess) {
+                    promise.success(f(future.get.asInstanceOf[A]))
+                } else {
+                    promise.failure(future.cause())
+                }
+            }
+        })
+        promise.future
+    }
+```
+使用`隐式转换`实现Java的异步编程到Scala的异步编程变换,该函数利用`io.netty.util.concurrent.{GenericFutureListener, Future => NFuture}`实现异步编程。
diff --git a/_community/coding-guides.md b/_community/coding-guides.md
new file mode 100644
index 0000000..f03a7f9
--- /dev/null
+++ b/_community/coding-guides.md
@@ -0,0 +1,143 @@
+---
+title: 开发工具栈
+layout: page
+---
+
+## Clang-Format
+
+我们使用 clang-format-3.9 来格式化代码。在 ubuntu 下安装 clang-format-3.9:
+
+```bash
+sudo apt-get install clang-format-3.9
+```
+
+clang-format 的配置文件参见 [src/.clang-format](https://github.com/XiaoMi/pegasus/blob/master/src/.clang-format) 。
+
+## Clang-Tidy
+
+我们使用 [clang-tidy](http://clang.llvm.org/extra/clang-tidy/) 来进行静态代码质量检查,它能够帮助我们 **发现代码中潜在的问题和风险**(例如 使用未初始化的变量,使用被 moved 的变量 等),**加强代码的可读性**,并且 **帮助你的代码和 pegasus 保持一致**。这需要你首先安装 clang-tidy(一般我们使用 >=3.8 版本)。
+
+例如我们想要用其检查 rdsn 的字符串转换库 `rdsn/include/dsn/utility/string_conv.h`,我们可以检查其单元测试代码
+
+```bash
+cd rdsn
+./run.sh build
+clang-tidy-3.8 src/core/tests/string_conv_test.cpp -p builder -header-filter=.*
+```
+
+**注意**:clang-tidy 只能检查源文件(.cpp/.cc),不能检查头文件。直接检查头文件通常会看到 “Compile command not found” 的错误。
+
+```bash
+➜  rdsn git:(master) clang-tidy-3.8 include/dsn/utility/string_conv.h  -p builder -header-filter=.* 
+Skipping /home/mi/git/release/pegasus/rdsn/include/dsn/utility/string_conv.h. Compile command not found.
+```
+
+**注意**:不可以直接在未构建的 pegasus/rdsn 下使用 clang-tidy 
+
+```bash
+➜  pegasus git:(master) ✗ clang-tidy-3.8 include/dsn/utility/string_conv.h -header-filter=.* 
+Error while trying to load a compilation database:
+Could not auto-detect compilation database for file "include/dsn/utility/string_conv.h"
+No compilation database found in /home/mi/git/release/pegasus/include/dsn/utility or any parent directory
+```
+
+clang-tidy 需要 `-p` 选项指定 “compilation database” 来确定项目结构。在 Pegasus 和 rDSN 都构建完成后,我们可以在 `pegasus/src/builder/` 和 `rdsn/builder/` 下都看到一个名为 `compile_commands.json` 的文件,这是 cmake 构建生成的 [JSONCompilationDatabase](https://clang.llvm.org/docs/JSONCompilationDatabase.html)。
+
+## CCache
+
+我们支持用 ccache 加速编译,你需要做的只是安装 ccache。一旦 cmake 构建脚本检测到系统安装了 ccache,在编译时便会自动将编译结果缓存至 ccache 中。首次编译的速度可能较慢,但后续编译的速度会得到极大提升。
+通常我们推荐使用 5GB 大小的缓存空间。
+
+```bash
+sudo apt install ccache
+ccache -M 5G
+```
+
+## Gcov
+
+我们使用 Gcov 和 Gcovr 来进行代码测试覆盖率的统计,请保证在你的系统上装有这两个工具。gcov 通常由 gcc 自带,而 gcovr 可以通过 pip 安装:
+
+```bash
+pip install --user gcovr
+```
+
+使用 gcovr 需要修改编译选项,我们提供一键式编译脚本,方法很简单:
+
+```bash
+cd rdsn
+./run.sh test --enable_gcov
+```
+
+最终的覆盖率统计报告会以 html 的方式生成在 rdsn/gcov_report 内。
+
+## Valgrind
+
+遇到内存泄漏时我们使用 valgrind 检查和定位泄漏位置。
+
+- 因为 pegasus 默认使用 tcmalloc(gperf) 优化内存分配,而 tcmalloc 不兼容 valgrind,所以编译时需要指定不链接 gperf 而使用 libc 的原生 malloc。
+
+```bash
+./run.sh build --disable_gperf -c
+```
+
+- 例如我们发现 meta server 发生了内存泄漏,我们可以针对 `dsn.meta.tests` 进行 valgrind 检查:
+
+```bash
+> cd builder/bin/dsn.meta.tests/
+> valgrind --log-file=log.txt --tool=memcheck --leak-check=full ./dsn.meta.tests config-test.ini
+```
+
+运行测试后,valgrind 的结果会输出到 log.txt,通过分析日志 "definitely lost" 和 "possibly lost" 可以找到内存泄漏的原因。
+
+## Heap Profiling
+
+如何定位 Pegasus Server 中内存开销大的函数?我们使用 tcmalloc 和 pprof 工具。
+
+* 首先你要保证编译时开启 tcmalloc(默认开启,但在 `--disable_gperf` 时禁用)。
+* 在启动服务前,设置环境变量:
+
+```bash
+export TCMALLOC_SAMPLE_PARAMETER=524288
+./run.sh start_onebox
+```
+
+* 使用 pprof 脚本即可查看 heap 信息
+
+```bash
+pprof --svg http://127.0.0.1:34801/pprof/heap > heap.svg
+```
+
+## Thrift
+
+Pegasus 借助 [apache/thrift](https://thrift.apache.org/) 来进行 RPC 数据的序列化/反序列化。
+如果你想要修改 Pegasus 内一些 RPC 结构定义,或者你想要增加 RPC 类型,在修改相应 `.thrift` 文件后,
+你可以运行:
+
+```sh
+cd rdsn
+./compile_thrift.py
+```
+
+或者
+
+```sh
+cd pegasus
+./src/idl/recompile_thrift.sh
+```
+
+thrift 是 Pegasus 的第三方库依赖之一,在运行 `./run.sh build` 后会自动安装。
+
+## 持续集成(CI)
+
+我们使用 Travis CI 来保证每个 pull-request 符合质量要求。其中我们会:
+
+1. 检查 PR 的标题是否符合 [conventional commit](https://www.conventionalcommits.org/en/v1.0.0-beta.2/#specification) 的要求。
+2. 检查代码格式是否符合标准,不符合的提交将无法通过 CI,请参考 [clang-format](#Clang-Format) 一节。
+3. 保证你的改动能够通过单元测试。你可以运行 `./run.sh test` 来验证。
+
+## 开发建议
+
+总的来说,我们推崇 [Google Code Style](https://google.github.io/styleguide/cppguide.html),除了:
+
+* 函数的传出参数推荐使用引用而不是指针
+* 头文件的 [Include Guard](https://en.wikipedia.org/wiki/Include_guard) 推荐使用 `#pragma once`
diff --git a/_community/index.md b/_community/index.md
new file mode 100755
index 0000000..9157c76
--- /dev/null
+++ b/_community/index.md
@@ -0,0 +1,55 @@
+---
+title: Pegasus 社区
+subtitle: 我们以纯粹开放的态度,愿与你共同构筑 Pegasus
+layout: community
+header_style: is-black
+---
+
+Pegasus 目前是由小米主导的开源项目,至今在敝公司内部已稳定使用了4年以上。
+随着系统的不断迭代发展,我们相信 Pegasus 所积累的技术能力同样能够服务于广大互联网存储场景。
+
+## 联系我们
+
+- 邮件组:<pe...@xiaomi.com>
+- gitter.im: <https://gitter.im/XiaoMi/Pegasus>
+- 微信:你可以联系 `qinzuoyan` 或 `wutao5003` 以加入我们的微信交流群。
+- 知乎:[Pegasus 技术专栏](https://zhuanlan.zhihu.com/c_1059090995865608192)
+
+## 开源
+
+Pegasus 是 100% 开源的项目,我们所有的开发都在 Github 之上,所有的组件都使用
+Apache 2 协议(部分代码为 MIT 协议)发布。
+
+我们的版本发布详情在 [Labels: release-note](https://github.com/XiaoMi/pegasus/labels/release-note) 下可以看到。
+
+## 参与贡献
+
+我们欢迎社区用户为 Pegasus 做出贡献,贡献的方式有很多,能力各异的参与者都可以为 Pegasus 添砖加瓦,
+其中包括但不限于以下几种方式:
+
+### 使用 Pegasus
+
+使用 Pegasus,并告诉我们你的使用场景和需求。
+你可以分享你关于 Pegasus 的博客和PPT到 [Github Issues](https://github.com/XiaoMi/pegasus/issues) 上,我们会择优收集。
+
+### 反馈 Bug 和新功能建议
+
+新功能提议和 Bug 详述可以提交到 [Github Issues](https://github.com/XiaoMi/pegasus/issues),
+你在使用 Pegasus 的过程中遇到的问题,和 Pegasus 当前对你所缺乏的需求,都可以及时反馈给我们。
+当然,我们希望在 Issue 中你能提供尽可能多的信息,方便我们跟进。
+
+当前已知的 bug 会被整理在 [Issues: type/bug](https://github.com/XiaoMi/pegasus/labels/type%2Fbug)。
+
+与新功能相关的提案会被整理在 [Issues: type/enhancement](https://github.com/XiaoMi/pegasus/labels/type%2Fenhancement)。
+
+### 提交代码和测试
+
+我们的开发流程使用 [Github Pull Request](https://github.com/XiaoMi/pegasus/pulls) 机制。
+你的提交至少需要两个 Pegasus Committer 的 Code Review 都通过才可合入 Master 分支。
+即使你并不是 Pegasus Committer,你也可以对代码提出 review,你的建议可能会有助于我们发现问题。
+
+### 文档修改
+
+我们极其重视文档的规范化和易读性,这能够帮助我们更好的接入新用户。如果你在阅读文档时发现了问题,请通过
+[Issues](https://github.com/XiaoMi/pegasus/issues) 反馈,或直接通过 [Pull Request](https://github.com/XiaoMi/pegasus/pulls)
+修改文档。
diff --git a/_community/proposal.md b/_community/proposal.md
new file mode 100644
index 0000000..dc96132
--- /dev/null
+++ b/_community/proposal.md
@@ -0,0 +1,304 @@
+---
+title: Pegasus Proposal
+layout: page
+---
+
+## Abstract
+
+Pegasus is a distributed key-value storage system which is designed to
+be horizontally scalable, strongly consistent and high-performance.
+
+- Pegasus codebase: <https://github.com/XiaoMi/pegasus>
+- Website: <https://pegasus-kv.github.io>
+
+## Proposal
+
+Pegasus is a key-value database that delivers low-latency data access together
+with horizontal scalability, using hash-based partitioning. Pegasus uses [PacificA](https://www.microsoft.com/en-us/research/wp-content/uploads/2008/02/tr-2008-25.pdf)
+protocol for strong consistency and [RocksDB](https://github.com/facebook/rocksdb) as the underlying storage engine.
+
+We propose to contribute the Pegasus codebase and associated artifacts
+(e.g., documentation, website content, etc.) to the Apache Software Foundation,
+and aim to build an open community around Pegasus's continued development
+in the 'Apache Way'.
+
+## Background
+
+[Apache HBase](https://hbase.apache.org/) was recognized as mostly the only large-scale KV store solution in [XiaoMi Corp](https://www.mi.com/global)
+until Pegasus came out in 2015. The original purpose of Pegasus was to solve the problems
+caused by HBase's two-level architecture and implementation, including high latency
+because of Java GC and RPC overhead of the underlying distributed filesystem, and long
+failover time because of single point of RegionServer and recovery overhead of splitting
+and replaying the HLog files.
+
+Pegasus aims to fill the gap between [Redis](https://redis.io/) and HBase. As the former
+is in-memory, low latency, but does not provide a strong-consistency guarantee.
+And unlike the latter, Pegasus server is entirely written in C++ and its read-write path
+relies merely on the local filesystem.
+
+Apart from performance requirements, we also need a storage system
+to ensure multiple-level data safety and support fast data migration
+among data centers, automatic load balancing, and online partition splitting.
+
+After investigating lots of existing storage systems in the open source world,
+we could hardly find a suitable solution to satisfy all the requirements.
+So the journey of Pegasus begins.
+
+### Rationale
+
+Pegasus is a mature and active project which has been widely adopted in XiaoMi.
+After the initial release of open source project in 2017, we have seen a great amount
+of interest across a diverse set of users and companies.
+
+Our experiences at committers and PMC members on other Apache projects have convinced
+us that having a long-term home at Apache foundation would be a great fit for the project,
+to ensure that processes and procedures are in place to keep project and community 'healthy'
+and free of any commercial, political or legal faults.
+
+### Initial Goal
+
+- Move the existing codebase, website, documentation, and mailing lists to
+  Apache-hosted infrastructure.
+- Work with the infrastructure team to implement and approve our code review, build,
+  and testing workflows in the context of the ASF.
+- Incremental development and releases among with Apache guidelines.
+
+## Current Status
+
+Pegasus has been an open source project on GitHub <https://github.com/XiaoMi/pegasus>
+since October 2017.
+
+### Meritocracy
+
+The intent of this proposal is to start building a diverse developer and user community
+around Pegasus following the ASF meritocracy model.
+We plan to invite more people as committers if they contribute to this project.
+
+### Releases
+
+Pegasus has undergone multiple public releases, listed here: <https://github.com/XiaoMi/pegasus/releases>.
+
+These old releases were not performed in the typical ASF fashion.
+We will adopt the ASF source release process upon joining the incubator.
+
+### Code Reviews
+
+Pegasus's code reviews are currently public on Github <https://github.com/XiaoMi/pegasus/pulls>.
+
+### Community
+
+Pegasus seeks to develop developer and user communities during incubation.
+
+### Core Developers
+
+Currently most of the core developers of Pegasus are working in
+the KV-Storage Team of Xiaomi. Yingchun Lai is one of the [Apache Kudu](https://github.com/apache/kudu) PMC members.
+Zuoyan Qin is an experienced open source developer who created [sofa-pbrpc](https://github.com/baidu/sofa-pbrpc)
+in his last job in Baidu. Wei Huang is also an active contributor of [Apache Doris (Incubating)](https://github.com/apache/incubator-doris).
+
+- Zuoyan Qin (<https://github.com/qinzuoyan>)
+- Yuchen He (<https://github.com/hycdong>)
+- Tao Wu (<https://github.com/neverchanje>)
+- Yingchun Lai (<https://github.com/acelyc111>)
+- Wei Huang (<https://github.com/vagetablechicken>)
+- Shuo Jia (<https://github.com/Shuo-Jia>)
+- Liwei Zhao (<https://github.com/levy5307>)
+
+### Alignment
+
+Pegasus is aligned with several other ASF projects.
+
+We are working on a new feature to load data from the HDFS filesystem.
+Pegasus can also generate and store checkpoints to HDFS, for both backup and analysis purpose.
+We currently support offline analysis on checkpoints powered by [Apache Spark](https://spark.apache.org/).
+
+## Known Risks
+
+### Orphaned Products
+
+The core developers of XiaoMi's Pegasus team work full time on this project.
+There is very little risk of Pegasus getting orphaned since at least one large company
+(XiaoMi) is extensively using it in production, with currently a scale of
+70+ clusters, 800+ tables, and more than 70TB data.
+Furthermore, since Pegasus was open sourced at the beginning of October 2017,
+it has received more than 1200 stars and been forked more than 200 times, and also
+received some [issues](https://github.com/XiaoMi/pegasus/issues) and pull requests
+from developers and users outside XiaoMi.
+We plan to extend and diversify this community further through Apache.
+
+### Inexperience with Open Source
+
+The core developers are all active users and followers of open source.
+They are already committers and contributors to the Pegasus Github project.
+All have been involved with the source code that has been released under an
+open source license, and several of them also have experience developing
+code in an open source environment.
+
+Several of the developers in XiaoMi's storage team are committers and/or PMC
+members on other ASF projects (Kudu, HBase, Doris, etc.). They will guide others to practice
+the Apache Way together along with other incubator mentors.
+
+### Homogenous Developers
+
+The project has received some contributions from developers outside of XiaoMi,
+and is starting to attract a user community as well. We hope to continue to
+encourage contributions from these developers and community members, and grow
+them into committers as they have time to continue their contributions.
+
+### Reliance on Salaried Developers
+
+XiaoMi invested in Pegasus as a general key-value storage used in company widely.
+The core developers have been dedicated to this project for nearly five years.
+
+Besides, we look forward to attracting more people outside XiaoMi to contribute to this project,
+either payed engineers working on storage area, or individual volunteers, as long as they have
+enthusiasm for the Pegasus project.
+
+### An Excessive Fascination with the Apache Brand
+
+Pegasus is proposing to enter incubation at Apache in order to help efforts
+to diversify the committer-base, not so much to capitalize on the Apache brand.
+The Pegasus project is in production use already inside XiaoMi,
+but is not expected to be a XiaoMi product for external customers.
+As such, the Pegasus project is not seeking to use the Apache brand as
+a marketing tool.
+
+## Documentation
+
+Information about Pegasus can be found at <https://github.com/XiaoMi/pegasus>.
+The following links provide more information about Pegasus in open source:
+
+- Pegasus Website: <https://pegasus-kv.github.io>
+- Codebase at Github: <https://github.com/XiaoMi/pegasus>
+- Issue Tracking: <https://github.com/XiaoMi/pegasus/issues>
... 65005 lines suppressed ...


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pegasus.apache.org
For additional commands, e-mail: commits-help@pegasus.apache.org