Repository: matrixorigin/matrixone
Pull request #2970 (2022-06-14 02:43:13)

Initial commit of LogService and HAKeeper
**What type of PR is this?**

- [ ] API-change
- [ ] BUG
- [ ] Improvement
- [ ] Documentation
- [x] Feature
- [ ] Test and CI
- [ ] Code Refactoring

**Which issue(s) this PR fixes:**

issue #

**What this PR does / why we need it:**

Initial commit of the Log Service and HAKeeper modules. 

**Special notes for your reviewer:**

Not Available

**Additional documentation (e.g. design docs, usage docs, etc.):**

Not Available



Patch:
diff --git a/.github/workflows/build_matrixone.yml b/.github/workflows/build_matrixone.yml
deleted file mode 100644
index 10a15211d..000000000
--- a/.github/workflows/build_matrixone.yml
+++ /dev/null
@@ -1,77 +0,0 @@
-# Define workflow name.
-name: Build MatrixOne
-
-# Controls when the workflow will run.
-on:
-  # Triggers the workflow on push or pull request events but only for the main branch.
-  pull_request:
-    types: [ opened, synchronize, reopened ]
-    branches: main
-    paths-ignore:
-      - 'docs/**'
-
-  # Allows dev to run this workflow manually from the Actions tab
-  workflow_dispatch:
-
-  # Trigger workflow at 21:00 UTC to cover time-consuming tests
-  # schedule:
-  # * is a special character in YAML so you have to quote this string
-  # - cron:  '00 21 * * *'
-
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel.
-jobs:
-  # This workflow contains a single job called "build".
-  build:
-    # The type of runner that the job will run on.
-    runs-on: ubuntu-latest
-    name: Build on CentOS/x64
-
-    # Steps represent a sequence of tasks that will be executed as part of the job.
-    steps:
-      # Checks-out your repository under $GITHUB_WORKSPACE, so the job can access it.
-      - uses: actions/checkout@v2
-        with:
-          fetch-depth: '3'
-
-      - name: Set up Go
-        uses: actions/setup-go@v2
-        with:
-          go-version: 1.18
-
-      - name: Formats Check
-        run: |
-          cd $GITHUB_WORKSPACE && make clean
-          if [ "$(make fmt | grep -v gofmt | wc -l)" -gt 0 ]; then exit 1; fi
-
-      - name: Detect non-ascii characters
-        run: |
-          na_characters=$(find ${{ github.workspace }} -type f -name "*.go" -exec grep -P  '[\xE5-\xE9][\x80-\xBF][\x80-\xBF]|\xE4[\xB8-\xBF][\x80-\xBF]' {} + || true)
-          if [[ -n $na_characters ]]; then
-            echo "Detected non-ascii characters in annotation or log info. Please refer to CONTRIBUTING.md."
-            printf '%s\n' "${na_characters[*]}"
-            exit 1
-          fi
-
-      - name: Build MatrixOne
-        run: |
-          cd $GITHUB_WORKSPACE && make clean && make config && make build
-
-      - name: Static Code Analysis
-        run: |
-          cd $GITHUB_WORKSPACE
-          make install-static-check-tools
-          make static-check
-
-      - name: Unit Testing
-        run: |
-          make ut
-
-      - uses: actions/upload-artifact@v2
-        if: ${{ always() }}
-        continue-on-error: true
-        with:
-          name: test_reports
-          path: |
-            ${{ github.workspace }}
-          retention-days: 7 
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4302bfa71..3129b7569 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,152 +13,64 @@ on:
 
 # A workflow run is made up of one or more jobs that can run sequentially or in parallel.
 jobs:
-  tae-build:
+  tae-ut:
     # The type of runner that the job will run on.
     runs-on: ubuntu-latest
-    name: Build Plan2-TAE on CentOS/x64
-    timeout-minutes: 30
-    env:
-      #Job-Status: 2-uncertain, 1-fail, 0-success
-      Job-Status: 2
+    name: Staticcheck and UT Test on CentOS/x64
 
     # Steps represent a sequence of tasks that will be executed as part of the job.
     steps:
       # Checks-out your repository under $GITHUB_WORKSPACE, so the job can access it.
-      - name: Dump GitHub context
-        env:
-          GITHUB_CONTEXT: ${{ toJson(github) }}
-        run: echo "$GITHUB_CONTEXT"
-
-      - name: checkout head
-        uses: actions/checkout@v2
-        if: ${{ env.Job-Status }} == 2
+      - uses: actions/checkout@v2
         with:
           fetch-depth: '3'
-          path: ./head
 
       - name: Set up Go
         uses: actions/setup-go@v2
-        if: ${{ env.Job-Status }} == 2
         with:
           go-version: 1.18
 
-      - name: Build MatrixOne
-        if: ${{ env.Job-Status }} == 2
+      - name: Formats Check
         run: |
-          cd $GITHUB_WORKSPACE/head && make clean && make config && make build
-          git rev-parse --short HEAD 
-          echo "base sha: ${{ github.event.pull_request.base.sha }}"
-          echo "head sha: ${{ github.event.pull_request.head.sha }}"
-          cat system_vars_config.toml | sed 's/usePlan2 = false/usePlan2 = true/g' | sed 's/storageEngine = "aoe"/storageEngine = "tae"/g' > config.toml
+          cd $GITHUB_WORKSPACE && make clean
+          if [ "$(make fmt | grep -v gofmt | wc -l)" -gt 0 ]; then exit 1; fi
 
-      - name: Start MO
-        if: ${{ env.Job-Status }} == 2
+      - name: Detect non-ascii characters
         run: |
-          cd $GITHUB_WORKSPACE/head
-          ./mo-server ./config.toml &>mo-server.log &
+          na_characters=$(find ${{ github.workspace }} -type f -name "*.go" -exec grep -P  '[\xE5-\xE9][\x80-\xBF][\x80-\xBF]|\xE4[\xB8-\xBF][\x80-\xBF]' {} + || true)
+          if [[ -n $na_characters ]]; then
+            echo "Detected non-ascii characters in annotation or log info. Please refer to CONTRIBUTING.md."
+            printf '%s\n' "${na_characters[*]}"
+            exit 1
+          fi
 
-      - name: Clone test-tool repository
-        if: ${{ env.Job-Status }} == 2
-        uses: actions/checkout@v2
-        with:
-          repository: matrixorigin/mo-tester
-          path: ./mo-tester
-          ref: main
-
-      - name: Set up JDK 8 for x64
-        if: ${{ env.Job-Status }} == 2
-        uses: actions/setup-java@v3
-        with:
-          java-version: '8'
-          distribution: 'adopt'
-          architecture: x64
-
-      - name: Start BVT Test
-        if: ${{ env.Job-Status }} == 2
-        id: bvt_on_pr_version
-        continue-on-error: true
-        run: |
-          sleep 60s
-          cd $GITHUB_WORKSPACE/mo-tester
-          ./run.sh -n 2>&1
-
-      - name: Stop Current MO-Server
-        if: ${{ env.Job-Status }} == 2
-        run: |
-          if [ ${{ steps.bvt_on_pr_version.outcome }} == 'success' ]; then echo "Job-Status=0" >> $GITHUB_ENV; exit 0; fi
-          echo "BVT on pr version failed. Stop running mo-server， switch to the base sha."
-          cd $GITHUB_WORKSPACE/mo-tester
-          mv log head-log
-          mv report head-report
-          if [ "$(ps -ef | grep 'mo-server' | grep -v "grep" | wc -l)" -gt 0 ]; then pkill mo-server; else echo 'current mo-server has already crashed'; echo "Job-Status=1" >> $GITHUB_ENV; exit 1; fi
-
-
-      - uses: actions/checkout@v2
-        if: ${{ env.Job-Status }} == 2
-        with:
-          fetch-depth: '3'
-          path: ./base
-          ref: ${{ github.event.pull_request.base.sha }}
-
-      - name: Build MatrixOne@base
-        if: ${{ env.Job-Status }} == 2
-        run: |
-          cd $GITHUB_WORKSPACE/base && make clean && make config && make build
-          git rev-parse --short HEAD 
-          cat system_vars_config.toml | sed 's/usePlan2 = false/usePlan2 = true/g' | sed 's/storageEngine = "aoe"/storageEngine = "tae"/g' > config.toml
-
-
-      - name: Start MatrixOne@base
-        if: ${{ env.Job-Status }} == 2
-        run: |
-          cd $GITHUB_WORKSPACE/base
-          ./mo-server ./config.toml &>mo-server.log &
-
-      - name: Start BVT Test for Comparing
-        id: bvt_on_base
-        continue-on-error: true
-        if: ${{ env.Job-Status }} == 2
+      - name: Build MatrixOne
         run: |
-          cd $GITHUB_WORKSPACE/mo-tester
-          sleep 60s
-          ./run.sh -n 2>&1
+          cd $GITHUB_WORKSPACE && make clean && make config && make build
 
-      - name: BVT Compare
-        if: ${{ env.Job-Status }} == 2
+      - name: Static Code Analysis
         run: |
-          cd $GITHUB_WORKSPACE/mo-tester
-          mv log base-log
-          mv report base-report
-          if [ ${{ steps.bvt_on_base.outcome }} == 'success' ]; then echo 'Base sha passed all bvt cases. Pls check head version'; exit 1; fi
-          cat ./base-report/success.txt ./head-report/success.txt ./head-report/success.txt | grep -v '^$' | sort > cmp.txt
-          echo '--------Cases only passed in base----------'
-          uniq -u cmp.txt
-          if [ "$(uniq -u cmp.txt | wc -l)" -gt 0 ]; then echo 'Some cases passed in base but failed in head. Pls check head version'; exit 3; fi
+          cd $GITHUB_WORKSPACE
+          make install-static-check-tools
+          make static-check
 
-      - name: remove some files
-        if: ${{ always() }}
-        continue-on-error: true
+      - name: Unit Testing
         run: |
-          cd $GITHUB_WORKSPACE
-          if  [ -d  "./head/store/"  ]; then rm -rf ./head/store/; fi
-          if  [ -d  "./base/store/"  ]; then rm -rf ./base/store/; fi
-          rm -rf ./head/mo-server
-          rm -rf ./base/mo-server
+          make ut
 
       - uses: actions/upload-artifact@v2
         if: ${{ always() }}
         continue-on-error: true
         with:
-          name: tae_test_reports
+          name: test_reports
           path: |
             ${{ github.workspace }}
           retention-days: 7
 
-  aoe-build:
+  tae-bvt:
     # The type of runner that the job will run on.
     runs-on: ubuntu-latest
-    name: Build Plan2-AOE on CentOS/x64
+    name: BVT Test on CentOS/x64
     timeout-minutes: 30
     env:
       #Job-Status: 2-uncertain, 1-fail, 0-success
@@ -192,7 +104,7 @@ jobs:
           git rev-parse --short HEAD 
           echo "base sha: ${{ github.event.pull_request.base.sha }}"
           echo "head sha: ${{ github.event.pull_request.head.sha }}"
-          cat system_vars_config.toml | sed 's/usePlan2 = false/usePlan2 = true/g' | sed 's/storageEngine = "aoe"/storageEngine = "aoe"/g' > config.toml
+          cat system_vars_config.toml | sed 's/usePlan2 = false/usePlan2 = true/g' | sed 's/storageEngine = "aoe"/storageEngine = "tae"/g' > config.toml
 
       - name: Start MO
         if: ${{ env.Job-Status }} == 2
@@ -223,7 +135,7 @@ jobs:
         run: |
           sleep 60s
           cd $GITHUB_WORKSPACE/mo-tester
-          ./run.sh -n -g 2>&1
+          ./run.sh -n 2>&1
 
       - name: Stop Current MO-Server
         if: ${{ env.Job-Status }} == 2
@@ -248,7 +160,7 @@ jobs:
         run: |
           cd $GITHUB_WORKSPACE/base && make clean && make config && make build
           git rev-parse --short HEAD 
-          cat system_vars_config.toml | sed 's/usePlan2 = false/usePlan2 = true/g' | sed 's/storageEngine = "aoe"/storageEngine = "aoe"/g' > config.toml
+          cat system_vars_config.toml | sed 's/usePlan2 = false/usePlan2 = true/g' | sed 's/storageEngine = "aoe"/storageEngine = "tae"/g' > config.toml
 
 
       - name: Start MatrixOne@base
@@ -264,7 +176,7 @@ jobs:
         run: |
           cd $GITHUB_WORKSPACE/mo-tester
           sleep 60s
-          ./run.sh -n -g 2>&1
+          ./run.sh -n 2>&1
 
       - name: BVT Compare
         if: ${{ env.Job-Status }} == 2
@@ -273,7 +185,7 @@ jobs:
           mv log base-log
           mv report base-report
           if [ ${{ steps.bvt_on_base.outcome }} == 'success' ]; then echo 'Base sha passed all bvt cases. Pls check head version'; exit 1; fi
-          cat ./base-report/success.txt ./head-report/success.txt ./head-report/success.txt | sort > cmp.txt
+          cat ./base-report/success.txt ./head-report/success.txt ./head-report/success.txt | grep -v '^$' | sort > cmp.txt
           echo '--------Cases only passed in base----------'
           uniq -u cmp.txt
           if [ "$(uniq -u cmp.txt | wc -l)" -gt 0 ]; then echo 'Some cases passed in base but failed in head. Pls check head version'; exit 3; fi
@@ -292,7 +204,7 @@ jobs:
         if: ${{ always() }}
         continue-on-error: true
         with:
-          name: aoe_test_reports
+          name: tae_test_reports
           path: |
             ${{ github.workspace }}
           retention-days: 7
diff --git a/go.mod b/go.mod
index 90edaef1f..4568fc5e7 100644
--- a/go.mod
+++ b/go.mod
@@ -13,7 +13,7 @@ require (
 	github.com/axiomhq/hyperloglog v0.0.0-20220105174342-98591331716a
 	github.com/bxcodec/faker/v3 v3.8.0
 	github.com/cespare/xxhash/v2 v2.1.2
-	github.com/cockroachdb/pebble v0.0.0-20210526183633-dd2a545f5d75
+	github.com/cockroachdb/pebble v0.0.0-20220407171941-2120d145e292
 	github.com/fagongzi/goetty v1.13.0
 	github.com/fagongzi/util v0.0.0-20210923134909-bccc37b5040d
 	github.com/go-sql-driver/mysql v1.6.0
@@ -21,7 +21,9 @@ require (
 	github.com/golang/mock v1.6.0
 	github.com/google/btree v1.0.1
 	github.com/google/gofuzz v1.2.0
-	github.com/matrixorigin/matrixcube v0.3.1-0.20220511071845-cfc4bac02bb4
+	github.com/lni/dragonboat/v4 v4.0.0-20220604123758-e40bf3f57b59
+	github.com/lni/goutils v1.3.1-0.20220604063047-388d67b4dbc4
+	github.com/matrixorigin/matrixcube v0.3.1-0.20220606032431-c944d801f1e5
 	github.com/matrixorigin/simdcsv v0.0.0-20210926114300-591bf748a770
 	github.com/minio/minio-go/v7 v7.0.27
 	github.com/panjf2000/ants/v2 v2.4.6
@@ -39,6 +41,8 @@ require (
 )
 
 require (
+	github.com/VictoriaMetrics/metrics v1.18.1 // indirect
+	github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect
 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.2 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6 // indirect
@@ -47,15 +51,27 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.7 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.6 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.6 // indirect
+	github.com/getsentry/sentry-go v0.12.0 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/google/uuid v1.1.2 // indirect
+	github.com/google/uuid v1.3.0 // indirect
+	github.com/hashicorp/errwrap v1.0.0 // indirect
+	github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
+	github.com/hashicorp/go-msgpack v0.5.3 // indirect
+	github.com/hashicorp/go-multierror v1.0.0 // indirect
+	github.com/hashicorp/go-sockaddr v1.0.0 // indirect
+	github.com/hashicorp/golang-lru v0.5.1 // indirect
+	github.com/hashicorp/memberlist v0.3.1 // indirect
 	github.com/klauspost/cpuid v1.3.1 // indirect
-	github.com/lni/goutils v1.3.0 // indirect
+	github.com/miekg/dns v1.1.26 // indirect
 	github.com/minio/md5-simd v1.1.0 // indirect
 	github.com/minio/sha256-simd v0.1.1 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
+	github.com/pierrec/lz4/v4 v4.1.14 // indirect
 	github.com/rs/xid v1.2.1 // indirect
+	github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
 	github.com/smartystreets/assertions v1.2.0 // indirect
+	github.com/valyala/fastrand v1.1.0 // indirect
+	github.com/valyala/histogram v1.2.0 // indirect
 	gopkg.in/ini.v1 v1.57.0 // indirect
 )
 
@@ -64,10 +80,9 @@ require (
 	github.com/DataDog/zstd v1.5.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bits-and-blooms/bitset v1.2.0 // indirect
-	github.com/cockroachdb/errors v1.8.2 // indirect
-	github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect
-	github.com/cockroachdb/redact v1.0.8 // indirect
-	github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect
+	github.com/cockroachdb/errors v1.9.0
+	github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect
+	github.com/cockroachdb/redact v1.1.3 // indirect
 	github.com/coreos/go-semver v0.3.0 // indirect
 	github.com/coreos/go-systemd/v22 v22.3.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
@@ -86,12 +101,12 @@ require (
 	github.com/jonboulle/clockwork v0.2.2 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/jtolds/gls v4.20.0+incompatible // indirect
-	github.com/juju/ratelimit v1.0.1 // indirect
+	github.com/juju/ratelimit v1.0.2-0.20191002062651-f60b32039441 // indirect
 	github.com/klauspost/compress v1.13.6 // indirect
 	github.com/klauspost/cpuid/v2 v2.0.3 // indirect
 	github.com/kr/pretty v0.3.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
-	github.com/lni/vfs v0.2.1-0.20210810090357-27c7525cf64f // indirect
+	github.com/lni/vfs v0.2.1-0.20220408085249-8be85be1c3c1
 	github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
diff --git a/go.sum b/go.sum
index 3deecfabc..f4a75cda9 100644
--- a/go.sum
+++ b/go.sum
@@ -52,7 +52,9 @@ github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU
 github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
+github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
 github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
+github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
 github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
 github.com/DataDog/zstd v1.5.0 h1:+K/VEwIAaPcHiMtQvpLD4lqW7f0Gk3xdYZmI1hD+CXo=
 github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
@@ -65,6 +67,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
 github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXlp4riwo=
 github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
 github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
+github.com/VictoriaMetrics/metrics v1.18.1 h1:OZ0+kTTto8oPfHnVAnTOoyl0XlRhRkoQrD2n2cOuRw0=
+github.com/VictoriaMetrics/metrics v1.18.1/go.mod h1:ArjwVz7WpgpegX/JpB0zpNF2h2232kErkEnzH1sxMmA=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -74,6 +78,7 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/aws/aws-sdk-go-v2 v1.16.5 h1:Ah9h1TZD9E2S1LzHpViBO3Jz9FPL5+rmflmb8hXirtI=
@@ -139,23 +144,26 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
 github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
-github.com/cockroachdb/datadriven v1.0.0 h1:uhZrAfEayBecH2w2tZmhe20HJ7hDvrrA4x2Bg9YdZKM=
 github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4=
+github.com/cockroachdb/datadriven v1.0.1-0.20211007161720-b558070c3be0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4=
+github.com/cockroachdb/datadriven v1.0.1-0.20220214170620-9913f5bc19b7 h1:GCR5egmFNSTyGOv9IvMh636aELybEhZOlpPlW2NtuiU=
+github.com/cockroachdb/datadriven v1.0.1-0.20220214170620-9913f5bc19b7/go.mod h1:hi0MtSY3AYDQNDi83kDkMH5/yqM/CsIrsOITkSoH7KI=
 github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
 github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM=
 github.com/cockroachdb/errors v1.7.5/go.mod h1:m/IWRCPXYZ6TvLLDuC0kfLR1pp/+BiZ0h16WHaBMRMM=
 github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac=
-github.com/cockroachdb/errors v1.8.2 h1:rnnWK9Nn5kEMOGz9531HuDx/FOleL4NVH20VsDexVC8=
-github.com/cockroachdb/errors v1.8.2/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac=
-github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY=
+github.com/cockroachdb/errors v1.8.8/go.mod h1:z6VnEL3hZ/2ONZEvG7S5Ym0bU2AqPcEKnIiA1wbsSu0=
+github.com/cockroachdb/errors v1.9.0 h1:B48dYem5SlAY7iU8AKsgedb4gH6mo+bDkbtLIvM/a88=
+github.com/cockroachdb/errors v1.9.0/go.mod h1:vaNcEYYqbIqB5JhKBhFV9CneUqeuEbB2OYJBK4GBNYQ=
 github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
-github.com/cockroachdb/pebble v0.0.0-20210503173641-1387689d3d7c/go.mod h1:1XpB4cLQcF189RAcWi4gUc110zJgtOfT7SVNGY8sOe0=
-github.com/cockroachdb/pebble v0.0.0-20210526183633-dd2a545f5d75 h1:rvbFUnq/+3udiF//O+UfPxh1MLXSW7UMH/ERJmNwvqk=
-github.com/cockroachdb/pebble v0.0.0-20210526183633-dd2a545f5d75/go.mod h1:1XpB4cLQcF189RAcWi4gUc110zJgtOfT7SVNGY8sOe0=
+github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f h1:6jduT9Hfc0njg5jJ1DdKCFPdMBrp/mdZfCpa5h+WM74=
+github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
+github.com/cockroachdb/pebble v0.0.0-20220407171941-2120d145e292 h1:7fLxcRpQTdi1iic0cIUpK+hKKLM5XdPhEbWRRrxfvLU=
+github.com/cockroachdb/pebble v0.0.0-20220407171941-2120d145e292/go.mod h1:buxOO9GBtOcq1DiXDpIPYrmxY020K2A8lOrwno5FetU=
 github.com/cockroachdb/redact v1.0.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
-github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw=
 github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
-github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM=
+github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ=
+github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
 github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
@@ -215,6 +223,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
+github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk=
+github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
 github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
@@ -242,6 +252,7 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
+github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
@@ -249,6 +260,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -284,7 +296,6 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -331,8 +342,9 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
@@ -340,6 +352,7 @@ github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gosimple/slug v1.1.1/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0=
@@ -354,27 +367,37 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
 github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
 github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
 github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/memberlist v0.3.1 h1:MXgUXLqva1QvpVEDQW1IQLG0wivQAtmFlHRQ+1vWZfM=
+github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE=
+github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
@@ -383,6 +406,8 @@ github.com/influxdata/influxdb v1.7.6/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOpr
 github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
 github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
 github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
+github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
+github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
 github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
@@ -391,6 +416,7 @@ github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9q
 github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -401,22 +427,29 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
 github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
-github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY=
 github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
+github.com/juju/ratelimit v1.0.2-0.20191002062651-f60b32039441 h1:b5Jqi7ir58EzfeZDyp7OSYQG/IVgyY4JWfHuJUF2AZI=
+github.com/juju/ratelimit v1.0.2-0.20191002062651-f60b32039441/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
 github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
 github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
+github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
 github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
+github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
 github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
+github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
 github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
+github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
+github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
@@ -437,34 +470,45 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
+github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
-github.com/lni/goutils v1.3.0 h1:oBhV7Z5DjNWbcy/c3fFj6qo4SnHcpyTY28qfKvLy6UM=
+github.com/lni/dragonboat/v4 v4.0.0-20220604123758-e40bf3f57b59 h1:QQnr5/JOG9upSf0WXP6LVg18vBlbycJbY5egnpyWcKQ=
+github.com/lni/dragonboat/v4 v4.0.0-20220604123758-e40bf3f57b59/go.mod h1:5dd7hX8qUJ+72UVKdZZIBw5UbhZTaGY5PIt/TTdIeSY=
 github.com/lni/goutils v1.3.0/go.mod h1:PUPtBAnZlRPUKWUXCsYkIRWubJbtNHpTAee0sczhlf4=
-github.com/lni/vfs v0.2.1-0.20210810090357-27c7525cf64f h1:ykmePP2E9ZuNRhfZL2bqjMdoYitgzYvIdVogbvnlvhs=
-github.com/lni/vfs v0.2.1-0.20210810090357-27c7525cf64f/go.mod h1:Z98QaAJnPYmgpw5VsksNdEPKpHDp4yeHTTvfHbX5JWE=
+github.com/lni/goutils v1.3.1-0.20220604063047-388d67b4dbc4 h1:6gzI38ZJmbzZ7oZebXz6jII0uVK+MZ3+ds+7mIt1ioI=
+github.com/lni/goutils v1.3.1-0.20220604063047-388d67b4dbc4/go.mod h1:LIHvF0fflR+zyXUQFQOiHPpKANf3UIr7DFIv5CBPOoU=
+github.com/lni/vfs v0.2.1-0.20220408085249-8be85be1c3c1 h1:350K9Ph9R+ExT49poOnsNa8Ev3yb0YZgODfeBsLATHc=
+github.com/lni/vfs v0.2.1-0.20220408085249-8be85be1c3c1/go.mod h1:LOatfyR8Xeej1jbXybwYGVfCccR0u+BQRG9xg7BD7xo=
 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/matrixorigin/etcd/raft/v3 v3.5.1-0.20210824022435-0203115049c2 h1:s7CQEsRxL8+/sAPW23uIqpwR+M8Aje81AH6w3/ZkxQw=
 github.com/matrixorigin/etcd/raft/v3 v3.5.1-0.20210824022435-0203115049c2/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
-github.com/matrixorigin/matrixcube v0.3.1-0.20220511071845-cfc4bac02bb4 h1:dRHX6Z7DzZ2nNaunhXWVAnKIxWOhpDmVUvsGnHiNxn0=
-github.com/matrixorigin/matrixcube v0.3.1-0.20220511071845-cfc4bac02bb4/go.mod h1:la08nf8VKIcYmAH/uc/stLiOLzwUJUXluFpaAbfIq+A=
+github.com/matrixorigin/matrixcube v0.3.1-0.20220606032431-c944d801f1e5 h1:b+9G1IWfgxbccwkw3OgF6jLC1hq+3zwX1Mv8vIh7JuM=
+github.com/matrixorigin/matrixcube v0.3.1-0.20220606032431-c944d801f1e5/go.mod h1:DVpm2zTKCao8/xafx3VASK6AVrFnMF8EDGEXXF/SBC0=
 github.com/matrixorigin/simdcsv v0.0.0-20210926114300-591bf748a770 h1:apc228jeCtUvvfkaydzJygk1jdH8y+THma2o73Yv4Vg=
 github.com/matrixorigin/simdcsv v0.0.0-20210926114300-591bf748a770/go.mod h1:A7O+LRuZcr/BbOLsMzM/q69ZmoLENUMpptYG0pPzTFQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
 github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
+github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
 github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
 github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
 github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
 github.com/minio/minio-go/v7 v7.0.27 h1:yJCvm78B+2+ll1PqO9eSD1as6Ibw3IYnnD8PyBEB2zo=
@@ -494,14 +538,18 @@ github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
 github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
 github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
+github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
 github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
+github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@@ -509,12 +557,15 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/panjf2000/ants/v2 v2.4.6 h1:drmj9mcygn2gawZ155dRbo+NfXEfAssjZNU1qoIb4gQ=
 github.com/panjf2000/ants/v2 v2.4.6/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/phf/go-queue v0.0.0-20170504031614-9abe38d0371d h1:U+PMnTlV2tu7RuMK5etusZG3Cf+rpow5hqQByeCzJ2g=
 github.com/phf/go-queue v0.0.0-20170504031614-9abe38d0371d/go.mod h1:lXfE4PvvTW5xOjO6Mba8zDPyw8M93B6AQ7frTGnMlA8=
 github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
 github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
+github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuMyLH/4HMTViQOJevkGiik3wW2AN9zb2fNQ=
 github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
@@ -572,8 +623,11 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
 github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shirou/gopsutil/v3 v3.22.3 h1:UebRzEomgMpv61e3hgD1tGooqX5trFbdU/ehphbHd00=
 github.com/shirou/gopsutil/v3 v3.22.3/go.mod h1:D01hZJ4pVHPpCTZ3m3T2+wDF2YAGfd+H4ifUguaQzHM=
@@ -624,11 +678,18 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1
 github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
 github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
+github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
+github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
+github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
@@ -726,9 +787,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
 golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -789,6 +854,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -814,6 +880,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
 golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -867,6 +934,8 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -875,6 +944,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -910,12 +980,15 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -935,6 +1008,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
 golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -956,6 +1030,7 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1158,6 +1233,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
 gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
 gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
 gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
@@ -1176,6 +1252,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/common/moerr/error.go b/pkg/common/moerr/error.go
index 08ed9f018..53d649c15 100644
--- a/pkg/common/moerr/error.go
+++ b/pkg/common/moerr/error.go
@@ -34,6 +34,13 @@ const (
 	// Group 2: numeric
 	DIVIVISION_BY_ZERO = 2000 + iota
 	OUT_OF_RANGE
+
+	// Group 3: invalid input
+	BAD_CONFIGURATION = 3000 + iota
+	INVALID_INPUT
+
+	// Group 4: unexpected state
+	INVALID_STATE = 4000 + iota
 )
 
 type Error struct {
diff --git a/pkg/hakeeper/rsm.go b/pkg/hakeeper/rsm.go
new file mode 100644
index 000000000..5ae0a3843
--- /dev/null
+++ b/pkg/hakeeper/rsm.go
@@ -0,0 +1,243 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package hakeeper
+
+import (
+	"encoding/binary"
+	"encoding/gob"
+	"io"
+	"time"
+
+	"github.com/lni/dragonboat/v4/logger"
+	sm "github.com/lni/dragonboat/v4/statemachine"
+
+	"github.com/matrixorigin/matrixone/pkg/common/moerr"
+	"github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+var (
+	plog = logger.GetLogger("hakeeper")
+)
+
+var (
+	binaryEnc = binary.BigEndian
+)
+
+const (
+	// TickDuration defines the frequency of ticks.
+	TickDuration = time.Second
+	// DefaultHAKeeperShardID is the shard ID assigned to the special HAKeeper
+	// shard.
+	DefaultHAKeeperShardID uint64 = 0
+
+	headerSize = 2
+)
+
+const (
+	createLogShardTag uint16 = iota + 0xAE01
+	tickTag
+	dnHeartbeatTag
+	logHeartbeatTag
+)
+
+type logShardIDQuery struct {
+	name string
+}
+
+type logShardIDQueryResult struct {
+	id    uint64
+	found bool
+}
+
+type stateMachine struct {
+	replicaID uint64
+
+	Tick   uint64
+	NextID uint64
+
+	LogShards map[string]uint64
+	DNState   DNState
+	LogState  LogState
+}
+
+func parseCmdTag(cmd []byte) uint16 {
+	return binaryEnc.Uint16(cmd)
+}
+
+func getCreateLogShardCmd(name string) []byte {
+	return getLogShardCmd(name, createLogShardTag)
+}
+
+func getLogShardCmd(name string, tag uint16) []byte {
+	cmd := make([]byte, headerSize+len(name))
+	binaryEnc.PutUint16(cmd, tag)
+	copy(cmd[headerSize:], []byte(name))
+	return cmd
+}
+
+func isCreateLogShardCmd(cmd []byte) (string, bool) {
+	return isLogShardCmd(cmd, createLogShardTag)
+}
+
+func isDNHeartbeatCmd(cmd []byte) bool {
+	return isHeartbeatCmd(cmd, dnHeartbeatTag)
+}
+
+func isLogHeartbeatCmd(cmd []byte) bool {
+	return isHeartbeatCmd(cmd, logHeartbeatTag)
+}
+
+func isHeartbeatCmd(cmd []byte, tag uint16) bool {
+	if len(cmd) <= headerSize {
+		return false
+	}
+	return parseCmdTag(cmd) == tag
+}
+
+func parseHeartbeatCmd(cmd []byte) []byte {
+	return cmd[headerSize:]
+}
+
+func isLogShardCmd(cmd []byte, tag uint16) (string, bool) {
+	if len(cmd) <= headerSize {
+		return "", false
+	}
+	if parseCmdTag(cmd) == tag {
+		return string(cmd[headerSize:]), true
+	}
+	return "", false
+}
+
+func isTickCmd(cmd []byte) bool {
+	return len(cmd) == headerSize && binaryEnc.Uint16(cmd) == tickTag
+}
+
+func GetTickCmd() []byte {
+	cmd := make([]byte, headerSize)
+	binaryEnc.PutUint16(cmd, tickTag)
+	return cmd
+}
+
+func GetLogStoreHeartbeatCmd(data []byte) []byte {
+	return getHeartbeatCmd(data, logHeartbeatTag)
+}
+
+func GetDNStoreHeartbeatCmd(data []byte) []byte {
+	return getHeartbeatCmd(data, dnHeartbeatTag)
+}
+
+func getHeartbeatCmd(data []byte, tag uint16) []byte {
+	cmd := make([]byte, headerSize+len(data))
+	binaryEnc.PutUint16(cmd, tag)
+	copy(cmd[headerSize:], data)
+	return cmd
+}
+
+func NewStateMachine(shardID uint64, replicaID uint64) sm.IStateMachine {
+	if shardID != DefaultHAKeeperShardID {
+		panic(moerr.NewError(moerr.INVALID_INPUT, "invalid HAKeeper shard ID"))
+	}
+	return &stateMachine{
+		replicaID: replicaID,
+		LogShards: make(map[string]uint64),
+		DNState:   NewDNState(),
+		LogState:  NewLogState(),
+	}
+}
+
+func (s *stateMachine) Close() error {
+	return nil
+}
+
+func (s *stateMachine) assignID() uint64 {
+	s.NextID++
+	return s.NextID
+}
+
+func (s *stateMachine) handleCreateLogShardCmd(cmd []byte) (sm.Result, error) {
+	name, ok := isCreateLogShardCmd(cmd)
+	if !ok {
+		panic(moerr.NewError(moerr.INVALID_INPUT, "not create log shard cmd"))
+	}
+	if shardID, ok := s.LogShards[name]; ok {
+		data := make([]byte, 8)
+		binaryEnc.PutUint64(data, shardID)
+		return sm.Result{Value: 0, Data: data}, nil
+	}
+	s.LogShards[name] = s.assignID()
+	return sm.Result{Value: s.NextID}, nil
+}
+
+func (s *stateMachine) handleDNHeartbeat(cmd []byte) (sm.Result, error) {
+	data := parseHeartbeatCmd(cmd)
+	var hb logservice.DNStoreHeartbeat
+	if err := hb.Unmarshal(data); err != nil {
+		panic(err)
+	}
+	s.DNState.Update(hb, s.Tick)
+	return sm.Result{}, nil
+}
+
+func (s *stateMachine) handleLogHeartbeat(cmd []byte) (sm.Result, error) {
+	data := parseHeartbeatCmd(cmd)
+	var hb logservice.LogStoreHeartbeat
+	if err := hb.Unmarshal(data); err != nil {
+		panic(err)
+	}
+	s.LogState.Update(hb, s.Tick)
+	return sm.Result{}, nil
+}
+
+func (s *stateMachine) handleTick(cmd []byte) (sm.Result, error) {
+	s.Tick++
+	return sm.Result{}, nil
+}
+
+func (s *stateMachine) Update(e sm.Entry) (sm.Result, error) {
+	cmd := e.Cmd
+	if _, ok := isCreateLogShardCmd(cmd); ok {
+		return s.handleCreateLogShardCmd(cmd)
+	} else if isDNHeartbeatCmd(cmd) {
+		return s.handleDNHeartbeat(cmd)
+	} else if isLogHeartbeatCmd(cmd) {
+		return s.handleLogHeartbeat(cmd)
+	} else if isTickCmd(cmd) {
+		return s.handleTick(cmd)
+	}
+	panic(moerr.NewError(moerr.INVALID_INPUT, "unexpected haKeeper cmd"))
+}
+
+func (s *stateMachine) Lookup(query interface{}) (interface{}, error) {
+	if q, ok := query.(*logShardIDQuery); ok {
+		id, ok := s.LogShards[q.name]
+		if ok {
+			return &logShardIDQueryResult{found: true, id: id}, nil
+		}
+		return &logShardIDQueryResult{found: false}, nil
+	}
+	panic("unknown query type")
+}
+
+func (s *stateMachine) SaveSnapshot(w io.Writer,
+	_ sm.ISnapshotFileCollection, _ <-chan struct{}) error {
+	enc := gob.NewEncoder(w)
+	return enc.Encode(s)
+}
+
+func (s *stateMachine) RecoverFromSnapshot(r io.Reader,
+	_ []sm.SnapshotFile, _ <-chan struct{}) error {
+	dec := gob.NewDecoder(r)
+	return dec.Decode(s)
+}
diff --git a/pkg/hakeeper/state.go b/pkg/hakeeper/state.go
new file mode 100644
index 000000000..f7bcccb20
--- /dev/null
+++ b/pkg/hakeeper/state.go
@@ -0,0 +1,138 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package hakeeper
+
+import (
+	"reflect"
+
+	"github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+// FIXME: dragonboat should have a public value indicating what is NoLeader node id
+const (
+	// NoLeader is the replica ID of the leader node.
+	NoLeader uint64 = 0
+)
+
+// DNShardInfo contins information on a list of shards.
+type DNShardInfo struct {
+	Tick   uint64
+	Shards []logservice.DNShardInfo
+}
+
+// DNState contains all DN details known to the HAKeeper.
+type DNState struct {
+	// Stores is keyed by DN store UUID, it contains details found on each DN
+	// store.
+	Stores map[string]DNShardInfo
+}
+
+// NewDNState creates a new DNState.
+func NewDNState() DNState {
+	return DNState{
+		Stores: make(map[string]DNShardInfo),
+	}
+}
+
+// Update applies the incoming DNStoreHeartbeat into HAKeeper. Tick is the
+// current tick of the HAKeeper which can be used as the timestamp of the
+// heartbeat.
+func (s *DNState) Update(hb logservice.DNStoreHeartbeat, tick uint64) {
+	shardInfo, ok := s.Stores[hb.UUID]
+	if !ok {
+		shardInfo = DNShardInfo{}
+	}
+	shardInfo.Tick = tick
+	shardInfo.Shards = hb.Shards
+	s.Stores[hb.UUID] = shardInfo
+}
+
+// LogShardInfo contains information of all replicas found on a Log store.
+type LogShardInfo struct {
+	Tick           uint64
+	RaftAddress    string
+	ServiceAddress string
+	GossipAddress  string
+	Shards         []logservice.LogShardInfo
+}
+
+type LogState struct {
+	// Shards is keyed by ShardID, it contains details aggregated from all Log
+	// stores. Each logservice.LogShardInfo here contains data aggregated from
+	// different replicas and thus reflect a more accurate description on each
+	// shard.
+	Shards map[uint64]logservice.LogShardInfo
+	// Stores is keyed by log store UUID, it contains details found on each store.
+	// Each LogShardInfo here reflects what was last reported by each Log store.
+	Stores map[string]LogShardInfo
+}
+
+// NewLogState creates a new LogState.
+func NewLogState() LogState {
+	return LogState{
+		Shards: make(map[uint64]logservice.LogShardInfo),
+		Stores: make(map[string]LogShardInfo),
+	}
+}
+
+// Update applies the incoming heartbeat message to the LogState with the
+// specified tick used as the timestamp.
+func (s *LogState) Update(hb logservice.LogStoreHeartbeat, tick uint64) {
+	s.updateStores(hb, tick)
+	s.updateShards(hb)
+}
+
+func (s *LogState) updateStores(hb logservice.LogStoreHeartbeat, tick uint64) {
+	shardInfo, ok := s.Stores[hb.UUID]
+	if !ok {
+		shardInfo = LogShardInfo{}
+	}
+	shardInfo.Tick = tick
+	shardInfo.RaftAddress = hb.RaftAddress
+	shardInfo.ServiceAddress = hb.ServiceAddress
+	shardInfo.GossipAddress = hb.GossipAddress
+	shardInfo.Shards = hb.Shards
+	s.Stores[hb.UUID] = shardInfo
+}
+
+func (s *LogState) updateShards(hb logservice.LogStoreHeartbeat) {
+	for _, incoming := range hb.Shards {
+		recorded, ok := s.Shards[incoming.ShardID]
+		if !ok {
+			recorded = logservice.LogShardInfo{
+				ShardID:  incoming.ShardID,
+				Replicas: make(map[uint64]string),
+			}
+		}
+
+		if incoming.Epoch > recorded.Epoch {
+			recorded.Epoch = incoming.Epoch
+			recorded.Replicas = incoming.Replicas
+		} else if incoming.Epoch == recorded.Epoch && incoming.Epoch > 0 {
+			if !reflect.DeepEqual(recorded.Replicas, incoming.Replicas) {
+				plog.Panicf("inconsistent replicas, %+v, %+v, nil: %t, nil: %t",
+					recorded.Replicas, incoming.Replicas,
+					recorded.Replicas == nil, incoming.Replicas == nil)
+			}
+		}
+
+		if incoming.Term > recorded.Term && incoming.LeaderID != NoLeader {
+			recorded.Term = incoming.Term
+			recorded.LeaderID = incoming.LeaderID
+		}
+
+		s.Shards[incoming.ShardID] = recorded
+	}
+}
diff --git a/pkg/logservice/README.md b/pkg/logservice/README.md
new file mode 100644
index 000000000..c0b8394de
--- /dev/null
+++ b/pkg/logservice/README.md
@@ -0,0 +1,3 @@
+## About
+
+Log Service provides reliable logging for MatrixOne.
diff --git a/pkg/logservice/client.go b/pkg/logservice/client.go
new file mode 100644
index 000000000..dbcd10412
--- /dev/null
+++ b/pkg/logservice/client.go
@@ -0,0 +1,199 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"context"
+	"net"
+
+	"github.com/matrixorigin/matrixone/pkg/common/moerr"
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+var (
+	// ErrIncompatibleClient is returned when write requests are made on read-only clients.
+	ErrIncompatibleClient = moerr.NewError(moerr.INVALID_INPUT, "incompatible client")
+)
+
+type LogServiceClientConfig struct {
+	ReadOnly  bool
+	ShardID   uint64
+	ReplicaID uint64
+	// LogService nodes service addresses
+	ServiceAddresses []string
+}
+
+type Client interface {
+	Close() error
+	Config() LogServiceClientConfig
+	Append(ctx context.Context, rec pb.LogRecord) (Lsn, error)
+	Read(ctx context.Context, firstIndex Lsn, maxSize uint64) ([]pb.LogRecord, Lsn, error)
+	Truncate(ctx context.Context, index Lsn) error
+	GetTruncatedIndex(ctx context.Context) (Lsn, error)
+}
+
+type client struct {
+	conn net.Conn
+	cfg  LogServiceClientConfig
+	buf  []byte
+}
+
+var _ Client = (*client)(nil)
+
+func CreateClient(ctx context.Context,
+	name string, cfg LogServiceClientConfig) (Client, error) {
+	c := &client{
+		cfg: cfg,
+		buf: make([]byte, reqBufSize),
+	}
+	var e error
+	for _, addr := range cfg.ServiceAddresses {
+		conn, err := getConnection(ctx, addr)
+		if err != nil {
+			e = err
+			continue
+		}
+		c.conn = conn
+		if cfg.ReadOnly {
+			if err := c.connectReadOnly(ctx); err == nil {
+				return c, nil
+			} else {
+				e = err
+			}
+		} else {
+			if err := c.connectReadWrite(ctx); err == nil {
+				return c, nil
+			} else {
+				e = err
+			}
+		}
+	}
+	return nil, e
+}
+
+func (c *client) Close() error {
+	if err := sendPoison(c.conn, poisonNumber[:]); err != nil {
+		return err
+	}
+	return waitPoisonAck(c.conn)
+}
+
+func (c *client) Config() LogServiceClientConfig {
+	return c.cfg
+}
+
+func (c *client) Append(ctx context.Context, rec pb.LogRecord) (Lsn, error) {
+	if c.readOnly() {
+		return 0, ErrIncompatibleClient
+	}
+	// TODO: check piggybacked hint on whether we are connected to the leader node
+	return c.append(ctx, rec)
+}
+
+func (c *client) Read(ctx context.Context,
+	firstIndex Lsn, maxSize uint64) ([]pb.LogRecord, Lsn, error) {
+	return c.read(ctx, firstIndex, maxSize)
+}
+
+func (c *client) Truncate(ctx context.Context, lsn Lsn) error {
+	if c.readOnly() {
+		return ErrIncompatibleClient
+	}
+	return c.truncate(ctx, lsn)
+}
+
+func (c *client) GetTruncatedIndex(ctx context.Context) (Lsn, error) {
+	return c.getTruncatedIndex(ctx)
+}
+
+func (c *client) readOnly() bool {
+	return c.cfg.ReadOnly
+}
+
+func (c *client) connectReadWrite(ctx context.Context) error {
+	if c.readOnly() {
+		panic(ErrIncompatibleClient)
+	}
+	return c.connect(ctx, pb.MethodType_CONNECT)
+}
+
+func (c *client) connectReadOnly(ctx context.Context) error {
+	return c.connect(ctx, pb.MethodType_CONNECT_RO)
+}
+
+func (c *client) request(ctx context.Context,
+	mt pb.MethodType, payload []byte, index Lsn,
+	maxSize uint64) (pb.Response, []pb.LogRecord, error) {
+	timeout, err := getTimeoutFromContext(ctx)
+	if err != nil {
+		return pb.Response{}, nil, err
+	}
+	req := pb.Request{
+		Method:      mt,
+		ShardID:     c.cfg.ShardID,
+		DNID:        c.cfg.ReplicaID,
+		Timeout:     int64(timeout),
+		Index:       index,
+		MaxSize:     maxSize,
+		PayloadSize: uint64(len(payload)),
+	}
+	if err := writeRequest(c.conn, req, c.buf, payload); err != nil {
+		return pb.Response{}, nil, err
+	}
+	resp, recs, err := readResponse(c.conn, c.buf)
+	if err != nil {
+		return pb.Response{}, nil, err
+	}
+	err = toError(resp)
+	if err != nil {
+		return pb.Response{}, nil, err
+	}
+	return resp, recs.Records, nil
+}
+
+func (c *client) connect(ctx context.Context, mt pb.MethodType) error {
+	_, _, err := c.request(ctx, mt, nil, 0, 0)
+	return err
+}
+
+func (c *client) append(ctx context.Context, rec pb.LogRecord) (Lsn, error) {
+	resp, _, err := c.request(ctx, pb.MethodType_APPEND, rec.Data, 0, 0)
+	if err != nil {
+		return 0, err
+	}
+	return resp.Index, nil
+}
+
+func (c *client) read(ctx context.Context,
+	firstIndex Lsn, maxSize uint64) ([]pb.LogRecord, Lsn, error) {
+	resp, recs, err := c.request(ctx, pb.MethodType_READ, nil, firstIndex, maxSize)
+	if err != nil {
+		return nil, 0, err
+	}
+	return recs, resp.LastIndex, nil
+}
+
+func (c *client) truncate(ctx context.Context, lsn Lsn) error {
+	_, _, err := c.request(ctx, pb.MethodType_TRUNCATE, nil, lsn, 0)
+	return err
+}
+
+func (c *client) getTruncatedIndex(ctx context.Context) (Lsn, error) {
+	resp, _, err := c.request(ctx, pb.MethodType_GET_TRUNCATE, nil, 0, 0)
+	if err != nil {
+		return 0, err
+	}
+	return resp.Index, nil
+}
diff --git a/pkg/logservice/config.go b/pkg/logservice/config.go
new file mode 100644
index 000000000..7e70dda83
--- /dev/null
+++ b/pkg/logservice/config.go
@@ -0,0 +1,99 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"github.com/cockroachdb/errors"
+	"github.com/lni/vfs"
+
+	"github.com/matrixorigin/matrixone/pkg/common/moerr"
+)
+
+const (
+	defaultDataDir        = "mo-logservice-data"
+	defaultServiceAddress = "0.0.0.0:32000"
+	defaultRaftAddress    = "0.0.0.0:32001"
+	defaultGossipAddress  = "0.0.0.0:32002"
+)
+
+var (
+	ErrInvalidConfig = moerr.NewError(moerr.BAD_CONFIGURATION, "invalid log service configuration")
+)
+
+// TODO: add toml or json support
+// Config defines the Configurations supported by the Log Service.
+type Config struct {
+	FS                   vfs.FS
+	DeploymentID         uint64
+	NodeHostID           string
+	RTTMillisecond       uint64
+	DataDir              string
+	ServiceAddress       string
+	ServiceListenAddress string
+	RaftAddress          string
+	RaftListenAddress    string
+	GossipAddress        string
+	GossipListenAddress  string
+	GossipSeedAddresses  []string
+}
+
+// Validate validates the configuration.
+func (c *Config) Validate() error {
+	if c.DeploymentID == 0 {
+		return errors.Wrapf(ErrInvalidConfig, "DeploymentID not set")
+	}
+	// when *ListenAddress is not empty and *Address is empty, consider it as an
+	// error
+	if len(c.ServiceAddress) == 0 && len(c.ServiceListenAddress) != 0 {
+		return errors.Wrapf(ErrInvalidConfig, "ServiceAddress not set")
+	}
+	if len(c.RaftAddress) == 0 && len(c.RaftListenAddress) != 0 {
+		return errors.Wrapf(ErrInvalidConfig, "RaftAddress not set")
+	}
+	if len(c.GossipAddress) == 0 && len(c.GossipListenAddress) != 0 {
+		return errors.Wrapf(ErrInvalidConfig, "GossipAddress not set")
+	}
+	if len(c.GossipSeedAddresses) == 0 {
+		return errors.Wrapf(ErrInvalidConfig, "GossipSeedAddresses not set")
+	}
+	return nil
+}
+
+func (c *Config) Fill() {
+	if c.RTTMillisecond == 0 {
+		c.RTTMillisecond = 200
+	}
+	if len(c.DataDir) == 0 {
+		c.DataDir = defaultDataDir
+	}
+	if len(c.ServiceAddress) == 0 {
+		c.ServiceAddress = defaultServiceAddress
+		c.ServiceListenAddress = defaultServiceAddress
+	} else if len(c.ServiceAddress) != 0 && len(c.ServiceListenAddress) == 0 {
+		c.ServiceListenAddress = c.ServiceAddress
+	}
+	if len(c.RaftAddress) == 0 {
+		c.RaftAddress = defaultRaftAddress
+		c.RaftListenAddress = defaultRaftAddress
+	} else if len(c.RaftAddress) != 0 && len(c.RaftListenAddress) == 0 {
+		c.RaftListenAddress = c.RaftAddress
+	}
+	if len(c.GossipAddress) == 0 {
+		c.GossipAddress = defaultGossipAddress
+		c.GossipListenAddress = defaultGossipAddress
+	} else if len(c.GossipAddress) != 0 && len(c.GossipListenAddress) == 0 {
+		c.GossipListenAddress = c.GossipAddress
+	}
+}
diff --git a/pkg/logservice/errors.go b/pkg/logservice/errors.go
new file mode 100644
index 000000000..2bdddaeb8
--- /dev/null
+++ b/pkg/logservice/errors.go
@@ -0,0 +1,85 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"github.com/cockroachdb/errors"
+	"github.com/lni/dragonboat/v4"
+
+	"github.com/matrixorigin/matrixone/pkg/common/moerr"
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+var (
+	ErrUnknownError = moerr.NewError(moerr.INVALID_STATE, "unknown error")
+)
+
+type errorToCode struct {
+	err     error
+	code    pb.ErrorCode
+	reverse bool
+}
+
+var errorToCodeMappings = getErrorToCodeMapping()
+
+func getErrorToCodeMapping() []errorToCode {
+	return []errorToCode{
+		{dragonboat.ErrTimeout, pb.ErrorCode_Timeout, true},
+		{dragonboat.ErrShardNotFound, pb.ErrorCode_InvalidShard, true},
+		// TODO: why ErrTimeoutTooSmall is possible
+		{dragonboat.ErrTimeoutTooSmall, pb.ErrorCode_Timeout, false},
+		{dragonboat.ErrPayloadTooBig, pb.ErrorCode_InvalidPayloadSize, true},
+		{dragonboat.ErrRejected, pb.ErrorCode_Rejected, true},
+		{dragonboat.ErrShardNotReady, pb.ErrorCode_ShardNotReady, true},
+		{dragonboat.ErrSystemBusy, pb.ErrorCode_ShardNotReady, false},
+		{dragonboat.ErrClosed, pb.ErrorCode_SystemClosed, true},
+
+		{ErrInvalidTruncateIndex, pb.ErrorCode_IndexAlreadyTruncated, true},
+		{ErrNotLeaseHolder, pb.ErrorCode_NotLeaseHolder, true},
+		{ErrOutOfRange, pb.ErrorCode_OutOfRange, true},
+	}
+}
+
+func toErrorCode(err error) (pb.ErrorCode, string) {
+	if err == nil {
+		return pb.ErrorCode_NoError, ""
+	}
+	for _, rec := range errorToCodeMappings {
+		if errors.Is(err, rec.err) {
+			plog.Errorf("error: %v, converted to code %d", err, rec.code)
+			return rec.code, ""
+		}
+	}
+	plog.Errorf("unrecognized error %v, converted to %d", err,
+		pb.ErrorCode_OtherSystemError)
+	return pb.ErrorCode_OtherSystemError, err.Error()
+}
+
+func toError(resp pb.Response) error {
+	if resp.ErrorCode == pb.ErrorCode_NoError {
+		return nil
+	} else if resp.ErrorCode == pb.ErrorCode_OtherSystemError {
+		return errors.Wrapf(ErrUnknownError, resp.ErrorMessage)
+	}
+
+	for _, rec := range errorToCodeMappings {
+		if rec.code == resp.ErrorCode && rec.reverse {
+			return rec.err
+		}
+	}
+	plog.Panicf("Unknown error code: %d", resp.ErrorCode)
+	// will never reach here
+	return nil
+}
diff --git a/pkg/logservice/rsm.go b/pkg/logservice/rsm.go
new file mode 100644
index 000000000..753c9b080
--- /dev/null
+++ b/pkg/logservice/rsm.go
@@ -0,0 +1,233 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"encoding/binary"
+	"encoding/gob"
+	"io"
+
+	sm "github.com/lni/dragonboat/v4/statemachine"
+)
+
+var (
+	binaryEnc = binary.BigEndian
+)
+
+const (
+	headerSize = 2
+)
+
+const (
+	leaseHolderIDTag uint16 = iota + 0xBF01
+	truncatedIndexTag
+	userEntryTag
+	indexTag
+)
+
+type leaseHistoryQuery struct {
+	index uint64
+}
+
+func getAppendCmd(cmd []byte, replicaID uint64) []byte {
+	if len(cmd) < headerSize+8 {
+		panic("cmd too small")
+	}
+	binaryEnc.PutUint16(cmd, userEntryTag)
+	binaryEnc.PutUint64(cmd[headerSize:], replicaID)
+	return cmd
+}
+
+func parseCmdTag(cmd []byte) uint16 {
+	return binaryEnc.Uint16(cmd)
+}
+
+func parseTruncatedIndex(cmd []byte) uint64 {
+	return binaryEnc.Uint64(cmd[headerSize:])
+}
+
+func parseLeaseHolderID(cmd []byte) uint64 {
+	return binaryEnc.Uint64(cmd[headerSize:])
+}
+
+func getSetLeaseHolderCmd(leaseHolderID uint64) []byte {
+	cmd := make([]byte, headerSize+8)
+	binaryEnc.PutUint16(cmd, leaseHolderIDTag)
+	binaryEnc.PutUint64(cmd[headerSize:], leaseHolderID)
+	return cmd
+}
+
+func getSetTruncatedIndexCmd(index uint64) []byte {
+	cmd := make([]byte, headerSize+8)
+	binaryEnc.PutUint16(cmd, truncatedIndexTag)
+	binaryEnc.PutUint64(cmd[headerSize:], index)
+	return cmd
+}
+
+func isSetLeaseHolderUpdate(cmd []byte) bool {
+	return tagMatch(cmd, leaseHolderIDTag)
+}
+
+func isSetTruncatedIndexUpdate(cmd []byte) bool {
+	return tagMatch(cmd, truncatedIndexTag)
+}
+
+func isUserUpdate(cmd []byte) bool {
+	if len(cmd) < headerSize+8 {
+		return false
+	}
+	return parseCmdTag(cmd) == userEntryTag
+}
+
+func tagMatch(cmd []byte, expectedTag uint16) bool {
+	if len(cmd) != headerSize+8 {
+		return false
+	}
+	return parseCmdTag(cmd) == expectedTag
+}
+
+type stateMachine struct {
+	shardID        uint64
+	replicaID      uint64
+	Index          uint64
+	LeaseHolderID  uint64
+	TruncatedIndex uint64
+	LeaseHistory   map[uint64]uint64 // log index -> truncate index
+}
+
+var _ (sm.IStateMachine) = (*stateMachine)(nil)
+
+// making this a IConcurrentStateMachine for now, IStateMachine need to be updated
+// to provide raft entry index to its Update() method
+func newStateMachine(shardID uint64, replicaID uint64) sm.IStateMachine {
+	return &stateMachine{
+		shardID:      shardID,
+		replicaID:    replicaID,
+		LeaseHistory: make(map[uint64]uint64),
+	}
+}
+
+func (s *stateMachine) setLeaseHolderID(index uint64, cmd []byte) {
+	if !isSetLeaseHolderUpdate(cmd) {
+		panic("not a setLeaseHolder update")
+	}
+	s.LeaseHolderID = parseLeaseHolderID(cmd)
+	s.LeaseHistory[index] = s.LeaseHolderID
+}
+
+func (s *stateMachine) truncateLeaseHistory(index uint64) {
+	_, index = s.getLeaseHistory(index)
+	for key := range s.LeaseHistory {
+		if key < index {
+			delete(s.LeaseHistory, key)
+		}
+	}
+}
+
+func (s *stateMachine) getLeaseHistory(index uint64) (uint64, uint64) {
+	max := uint64(0)
+	lease := uint64(0)
+	for key, val := range s.LeaseHistory {
+		if key >= index {
+			continue
+		}
+		if key > max {
+			max = key
+			lease = val
+		}
+	}
+	return lease, max
+}
+
+func (s *stateMachine) setTruncatedIndex(cmd []byte) bool {
+	if !isSetTruncatedIndexUpdate(cmd) {
+		panic("not a setTruncatedIndex update")
+	}
+	index := parseTruncatedIndex(cmd)
+	if index > s.TruncatedIndex {
+		s.TruncatedIndex = index
+		s.truncateLeaseHistory(index)
+		return true
+	}
+	return false
+}
+
+// handleUserUpdate returns an empty sm.Result on success or it returns a
+// sm.Result value with the Value field set to the current lease holder ID
+// to indicate rejection by mismatched lease holder ID.
+func (s *stateMachine) handleUserUpdate(index uint64, cmd []byte) sm.Result {
+	if !isUserUpdate(cmd) {
+		panic("not user update")
+	}
+	if s.LeaseHolderID != parseLeaseHolderID(cmd) {
+		data := make([]byte, 8)
+		binaryEnc.PutUint64(data, s.LeaseHolderID)
+		return sm.Result{Data: data}
+	}
+	return sm.Result{Value: index}
+}
+
+func (s *stateMachine) Close() error {
+	return nil
+}
+
+func (s *stateMachine) Update(e sm.Entry) (sm.Result, error) {
+	cmd := e.Cmd
+	s.Index = e.Index
+	if isSetLeaseHolderUpdate(cmd) {
+		s.setLeaseHolderID(e.Index, cmd)
+		return sm.Result{}, nil
+	} else if isSetTruncatedIndexUpdate(cmd) {
+		if s.setTruncatedIndex(cmd) {
+			return sm.Result{}, nil
+		} else {
+			return sm.Result{Value: s.TruncatedIndex}, nil
+		}
+	} else if isUserUpdate(cmd) {
+		return s.handleUserUpdate(e.Index, cmd), nil
+	}
+	panic("corrupted entry")
+}
+
+func (s *stateMachine) Lookup(query interface{}) (interface{}, error) {
+	if v, ok := query.(uint16); ok {
+		if v == indexTag {
+			return s.Index, nil
+		} else if v == leaseHolderIDTag {
+			return s.LeaseHolderID, nil
+		} else if v == truncatedIndexTag {
+			return s.TruncatedIndex, nil
+		} else {
+			panic("unknown lookup command type")
+		}
+	}
+	if v, ok := query.(leaseHistoryQuery); ok {
+		lease, _ := s.getLeaseHistory(v.index)
+		return lease, nil
+	}
+	panic("unknown lookup command type")
+}
+
+func (s *stateMachine) SaveSnapshot(w io.Writer,
+	_ sm.ISnapshotFileCollection, _ <-chan struct{}) error {
+	enc := gob.NewEncoder(w)
+	return enc.Encode(s)
+}
+
+func (s *stateMachine) RecoverFromSnapshot(r io.Reader,
+	_ []sm.SnapshotFile, _ <-chan struct{}) error {
+	dec := gob.NewDecoder(r)
+	return dec.Decode(s)
+}
diff --git a/pkg/logservice/service.go b/pkg/logservice/service.go
new file mode 100644
index 000000000..900df86c6
--- /dev/null
+++ b/pkg/logservice/service.go
@@ -0,0 +1,270 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"context"
+	"net"
+	"sync"
+	"time"
+
+	"github.com/cockroachdb/errors"
+	"github.com/lni/dragonboat/v4/logger"
+	"github.com/lni/goutils/netutil"
+	"github.com/lni/goutils/syncutil"
+
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+var (
+	plog = logger.GetLogger("LogService")
+)
+
+type Lsn = uint64
+
+type LogRecord = pb.LogRecord
+
+type Service struct {
+	cfg         Config
+	store       *logStore
+	stopper     *syncutil.Stopper
+	connStopper *syncutil.Stopper
+}
+
+func NewService(cfg Config) (*Service, error) {
+	if err := cfg.Validate(); err != nil {
+		return nil, err
+	}
+	cfg.Fill()
+
+	store, err := newLogStore(cfg)
+	if err != nil {
+		plog.Errorf("failed to create log store %v", err)
+		return nil, err
+	}
+	service := &Service{
+		cfg:         cfg,
+		store:       store,
+		stopper:     syncutil.NewStopper(),
+		connStopper: syncutil.NewStopper(),
+	}
+	// TODO: before making the service available to the outside world, restore all
+	// replicas already known to the local store
+	if err := service.startServer(); err != nil {
+		plog.Errorf("failed to start the server %v", err)
+		if err := store.Close(); err != nil {
+			plog.Errorf("failed to close the store, %v", err)
+		}
+		return nil, err
+	}
+	return service, nil
+}
+
+func (s *Service) Close() error {
+	s.stopper.Stop()
+	s.connStopper.Stop()
+	return s.store.Close()
+}
+
+func (s *Service) ID() string {
+	return s.store.ID()
+}
+
+func (s *Service) startServer() error {
+	listener, err := netutil.NewStoppableListener(s.cfg.ServiceListenAddress,
+		nil, s.stopper.ShouldStop())
+	if err != nil {
+		return err
+	}
+	s.connStopper.RunWorker(func() {
+		// sync.WaitGroup's doc mentions that
+		// "Note that calls with a positive delta that occur when the counter is
+		//  zero must happen before a Wait."
+		// It is unclear that whether the stdlib is going complain in future
+		// releases when Wait() is called when the counter is zero and Add() with
+		// positive delta has never been called.
+		<-s.connStopper.ShouldStop()
+	})
+	s.stopper.RunWorker(func() {
+		for {
+			conn, err := listener.Accept()
+			if err != nil {
+				if err == netutil.ErrListenerStopped {
+					return
+				}
+				panic(err)
+			}
+			var once sync.Once
+			closeFn := func() {
+				once.Do(func() {
+					if err := conn.Close(); err != nil {
+						plog.Errorf("failed to close the connection, %v", err)
+					}
+				})
+			}
+			s.connStopper.RunWorker(func() {
+				<-s.stopper.ShouldStop()
+				closeFn()
+			})
+			s.connStopper.RunWorker(func() {
+				s.serve(conn)
+				closeFn()
+			})
+		}
+	})
+	return nil
+}
+
+func (s *Service) serve(conn net.Conn) {
+	magicNum := make([]byte, len(magicNumber))
+	reqBuf := make([]byte, reqBufSize)
+	recvBuf := make([]byte, recvBufSize)
+
+	for {
+		err := readMagicNumber(conn, magicNum)
+		if err != nil {
+			if errors.Is(err, errPoisonReceived) {
+				if err := sendPoisonAck(conn, poisonNumber[:]); err != nil {
+					plog.Errorf("failed to send poison ack, %v", err)
+				}
+				return
+			}
+			if errors.Is(err, ErrBadMessage) {
+				return
+			}
+			operr, ok := err.(net.Error)
+			if ok && operr.Timeout() {
+				continue
+			} else {
+				return
+			}
+		}
+		req, payload, err := readRequest(conn, reqBuf, recvBuf)
+		if err != nil {
+			plog.Errorf("failed to read request, %v", err)
+			return
+		}
+		// with error already encoded into the resp
+		resp, records := s.handle(req, payload)
+		var recs []byte
+		if len(records.Records) > 0 {
+			data := MustMarshal(&records)
+			resp.PayloadSize = uint64(len(data))
+			recs = data
+		}
+		if err := writeResponse(conn, resp, recs, recvBuf); err != nil {
+			plog.Errorf("failed to write response, %v", err)
+			return
+		}
+	}
+}
+
+func (s *Service) handle(req pb.Request,
+	payload []byte) (pb.Response, pb.LogRecordResponse) {
+	switch req.Method {
+	case pb.MethodType_CREATE:
+		panic("not implemented")
+	case pb.MethodType_DESTROY:
+		panic("not implemented")
+	case pb.MethodType_APPEND:
+		return s.handleAppend(req, payload), pb.LogRecordResponse{}
+	case pb.MethodType_READ:
+		return s.handleRead(req)
+	case pb.MethodType_TRUNCATE:
+		return s.handleTruncate(req), pb.LogRecordResponse{}
+	case pb.MethodType_GET_TRUNCATE:
+		return s.handleGetTruncatedIndex(req), pb.LogRecordResponse{}
+	case pb.MethodType_CONNECT:
+		return s.handleConnect(req), pb.LogRecordResponse{}
+	case pb.MethodType_CONNECT_RO:
+		return s.handleConnectRO(req), pb.LogRecordResponse{}
+	default:
+		panic("unknown method type")
+	}
+}
+
+func getResponse(req pb.Request) pb.Response {
+	return pb.Response{Method: req.Method}
+}
+
+func (s *Service) handleConnect(req pb.Request) pb.Response {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.Timeout))
+	defer cancel()
+	resp := getResponse(req)
+	if err := s.store.GetOrExtendDNLease(ctx, req.ShardID, req.DNID); err != nil {
+		resp.ErrorCode, resp.ErrorMessage = toErrorCode(err)
+	}
+	return resp
+}
+
+func (s *Service) handleConnectRO(req pb.Request) pb.Response {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.Timeout))
+	defer cancel()
+	resp := getResponse(req)
+	// we only check whether the specified shard is available
+	if _, err := s.store.GetTruncatedIndex(ctx, req.ShardID); err != nil {
+		resp.ErrorCode, resp.ErrorMessage = toErrorCode(err)
+	}
+	return resp
+}
+
+func (s *Service) handleAppend(req pb.Request, payload []byte) pb.Response {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.Timeout))
+	defer cancel()
+	resp := getResponse(req)
+	lsn, err := s.store.Append(ctx, req.ShardID, payload)
+	if err != nil {
+		resp.ErrorCode, resp.ErrorMessage = toErrorCode(err)
+	} else {
+		resp.Index = lsn
+	}
+	return resp
+}
+
+func (s *Service) handleRead(req pb.Request) (pb.Response, pb.LogRecordResponse) {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.Timeout))
+	defer cancel()
+	resp := getResponse(req)
+	records, lsn, err := s.store.QueryLog(ctx, req.ShardID, req.Index, req.MaxSize)
+	if err != nil {
+		resp.ErrorCode, resp.ErrorMessage = toErrorCode(err)
+	} else {
+		resp.LastIndex = lsn
+	}
+	return resp, pb.LogRecordResponse{Records: records}
+}
+
+func (s *Service) handleTruncate(req pb.Request) pb.Response {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.Timeout))
+	defer cancel()
+	resp := getResponse(req)
+	if err := s.store.TruncateLog(ctx, req.ShardID, req.Index); err != nil {
+		resp.ErrorCode, resp.ErrorMessage = toErrorCode(err)
+	}
+	return resp
+}
+
+func (s *Service) handleGetTruncatedIndex(req pb.Request) pb.Response {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.Timeout))
+	defer cancel()
+	resp := getResponse(req)
+	index, err := s.store.GetTruncatedIndex(ctx, req.ShardID)
+	if err != nil {
+		resp.ErrorCode, resp.ErrorMessage = toErrorCode(err)
+	} else {
+		resp.Index = index
+	}
+	return resp
+}
diff --git a/pkg/logservice/shardinfo.go b/pkg/logservice/shardinfo.go
new file mode 100644
index 000000000..1dd0316e5
--- /dev/null
+++ b/pkg/logservice/shardinfo.go
@@ -0,0 +1,63 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"github.com/matrixorigin/matrixone/pkg/common/moerr"
+)
+
+type ReplicaInfo struct {
+	UUID           string
+	ServiceAddress string
+}
+
+type ShardInfo struct {
+	ShardID  uint64
+	Replicas map[uint64]ReplicaInfo
+	Epoch    uint64
+	LeaderID uint64
+	Term     uint64
+}
+
+func (s *Service) GetShardInfo(shardID uint64) (ShardInfo, bool) {
+	r, ok := s.store.nh.GetNodeHostRegistry()
+	if !ok {
+		panic(moerr.NewError(moerr.INVALID_STATE, "gossip registry not enabled"))
+	}
+	shard, ok := r.GetShardInfo(shardID)
+	if !ok {
+		return ShardInfo{}, false
+	}
+	result := ShardInfo{
+		ShardID:  shard.ShardID,
+		Epoch:    shard.ConfigChangeIndex,
+		LeaderID: shard.LeaderID,
+		Term:     shard.Term,
+		Replicas: make(map[uint64]ReplicaInfo),
+	}
+	for nodeID, uuid := range shard.Nodes {
+		data, ok := r.GetMeta(uuid)
+		if !ok {
+			return ShardInfo{}, false
+		}
+		var md logStoreMeta
+		md.unmarshal(data)
+		result.Replicas[nodeID] = ReplicaInfo{
+			UUID:           uuid,
+			ServiceAddress: md.serviceAddress,
+		}
+	}
+	return result, true
+}
diff --git a/pkg/logservice/store.go b/pkg/logservice/store.go
new file mode 100644
index 000000000..4699e5a84
--- /dev/null
+++ b/pkg/logservice/store.go
@@ -0,0 +1,517 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"context"
+	"sync"
+	"time"
+
+	"github.com/cockroachdb/errors"
+	"github.com/lni/dragonboat/v4"
+	cli "github.com/lni/dragonboat/v4/client"
+	"github.com/lni/dragonboat/v4/config"
+	"github.com/lni/dragonboat/v4/raftpb"
+	sm "github.com/lni/dragonboat/v4/statemachine"
+	"github.com/lni/goutils/syncutil"
+
+	"github.com/matrixorigin/matrixone/pkg/common/moerr"
+	"github.com/matrixorigin/matrixone/pkg/hakeeper"
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+var (
+	ErrInvalidTruncateIndex = moerr.NewError(moerr.INVALID_INPUT, "invalid input")
+	ErrNotLeaseHolder       = moerr.NewError(moerr.INVALID_STATE, "not lease holder")
+	ErrOutOfRange           = moerr.NewError(moerr.INVALID_INPUT, "query out of range")
+	ErrInvalidShardID       = moerr.NewError(moerr.INVALID_INPUT, "invalid shard ID")
+)
+
+type logStoreMeta struct {
+	serviceAddress string
+}
+
+func (l *logStoreMeta) marshal() []byte {
+	return []byte(l.serviceAddress)
+}
+
+func (l *logStoreMeta) unmarshal(data []byte) {
+	l.serviceAddress = string(data)
+}
+
+func getNodeHostConfig(cfg Config) config.NodeHostConfig {
+	meta := logStoreMeta{
+		serviceAddress: cfg.ServiceAddress,
+	}
+	return config.NodeHostConfig{
+		DeploymentID:        cfg.DeploymentID,
+		NodeHostID:          cfg.NodeHostID,
+		NodeHostDir:         cfg.DataDir,
+		RTTMillisecond:      cfg.RTTMillisecond,
+		AddressByNodeHostID: true,
+		RaftAddress:         cfg.RaftAddress,
+		ListenAddress:       cfg.RaftListenAddress,
+		Expert: config.ExpertConfig{
+			FS: cfg.FS,
+			// FIXME: dragonboat need to be updated to make this field a first class
+			// citizen
+			TestGossipProbeInterval: 50 * time.Millisecond,
+		},
+		Gossip: config.GossipConfig{
+			BindAddress:      cfg.GossipListenAddress,
+			AdvertiseAddress: cfg.GossipAddress,
+			Seed:             cfg.GossipSeedAddresses,
+			Meta:             meta.marshal(),
+		},
+	}
+}
+
+func getRaftConfig(shardID uint64, replicaID uint64) config.Config {
+	return config.Config{
+		ShardID:             shardID,
+		ReplicaID:           replicaID,
+		CheckQuorum:         true,
+		PreVote:             true,
+		ElectionRTT:         10,
+		HeartbeatRTT:        1,
+		OrderedConfigChange: true,
+	}
+}
+
+type logStore struct {
+	cfg               Config
+	nh                *dragonboat.NodeHost
+	haKeeperReplicaID uint64
+	stopper           *syncutil.Stopper
+
+	mu struct {
+		sync.Mutex
+		truncateCh      chan struct{}
+		pendingTruncate map[uint64]struct{}
+	}
+}
+
+func newLogStore(cfg Config) (*logStore, error) {
+	nh, err := dragonboat.NewNodeHost(getNodeHostConfig(cfg))
+	if err != nil {
+		return nil, err
+	}
+	ls := &logStore{
+		cfg:     cfg,
+		nh:      nh,
+		stopper: syncutil.NewStopper(),
+	}
+	ls.mu.truncateCh = make(chan struct{})
+	ls.mu.pendingTruncate = make(map[uint64]struct{})
+	ls.stopper.RunWorker(func() {
+		ls.truncationWorker()
+	})
+	return ls, nil
+}
+
+func (l *logStore) Close() error {
+	l.stopper.Stop()
+	if l.nh != nil {
+		l.nh.Close()
+	}
+	return nil
+}
+
+func (l *logStore) ID() string {
+	return l.nh.ID()
+}
+
+func (l *logStore) StartHAKeeperReplica(replicaID uint64,
+	initialReplicas map[uint64]dragonboat.Target) error {
+	l.haKeeperReplicaID = replicaID
+	raftConfig := getRaftConfig(hakeeper.DefaultHAKeeperShardID, replicaID)
+	// TODO: add another API for joining
+	if err := l.nh.StartReplica(initialReplicas,
+		false, hakeeper.NewStateMachine, raftConfig); err != nil {
+		return err
+	}
+	l.stopper.RunWorker(func() {
+		l.ticker()
+	})
+	return nil
+}
+
+func (l *logStore) StartReplica(shardID uint64, replicaID uint64,
+	initialReplicas map[uint64]dragonboat.Target) error {
+	if shardID == hakeeper.DefaultHAKeeperShardID {
+		return ErrInvalidShardID
+	}
+	raftConfig := getRaftConfig(shardID, replicaID)
+	// TODO: add another API for joining
+	return l.nh.StartReplica(initialReplicas, false, newStateMachine, raftConfig)
+}
+
+func (l *logStore) propose(ctx context.Context,
+	session *cli.Session, cmd []byte) (sm.Result, error) {
+	count := 0
+	for {
+		count++
+		result, err := l.nh.SyncPropose(ctx, session, cmd)
+		if err != nil {
+			if errors.Is(err, dragonboat.ErrShardNotReady) {
+				time.Sleep(time.Duration(l.nh.NodeHostConfig().RTTMillisecond) * time.Millisecond)
+				continue
+			}
+			if errors.Is(err, dragonboat.ErrTimeoutTooSmall) && count > 1 {
+				return sm.Result{}, dragonboat.ErrTimeout
+			}
+			return sm.Result{}, err
+		}
+		return result, nil
+	}
+}
+
+func (l *logStore) read(ctx context.Context,
+	shardID uint64, query interface{}) (interface{}, error) {
+	count := 0
+	for {
+		count++
+		result, err := l.nh.SyncRead(ctx, shardID, query)
+		if err != nil {
+			if errors.Is(err, dragonboat.ErrShardNotReady) {
+				time.Sleep(time.Duration(l.nh.NodeHostConfig().RTTMillisecond) * time.Millisecond)
+				continue
+			}
+			if errors.Is(err, dragonboat.ErrTimeoutTooSmall) && count > 1 {
+				return nil, dragonboat.ErrTimeout
+			}
+			return nil, err
+		}
+		return result, nil
+	}
+}
+
+func (l *logStore) GetOrExtendDNLease(ctx context.Context,
+	shardID uint64, dnID uint64) error {
+	session := l.nh.GetNoOPSession(shardID)
+	cmd := getSetLeaseHolderCmd(dnID)
+	_, err := l.propose(ctx, session, cmd)
+	return err
+}
+
+func (l *logStore) TruncateLog(ctx context.Context,
+	shardID uint64, index Lsn) error {
+	session := l.nh.GetNoOPSession(shardID)
+	cmd := getSetTruncatedIndexCmd(index)
+	result, err := l.propose(ctx, session, cmd)
+	if err != nil {
+		plog.Errorf("propose truncate log cmd failed, %v", err)
+		return err
+	}
+	if result.Value > 0 {
+		plog.Errorf("shardID %d already truncated to index %d", shardID, result.Value)
+		return errors.Wrapf(ErrInvalidTruncateIndex, "already truncated to %d", result.Value)
+	}
+	l.mu.Lock()
+	l.mu.pendingTruncate[shardID] = struct{}{}
+	l.mu.Unlock()
+	l.mu.truncateCh <- struct{}{}
+	return nil
+}
+
+func (l *logStore) Append(ctx context.Context,
+	shardID uint64, cmd []byte) (Lsn, error) {
+	if !isUserUpdate(cmd) {
+		panic(moerr.NewError(moerr.INVALID_INPUT, "not user update"))
+	}
+	session := l.nh.GetNoOPSession(shardID)
+	result, err := l.propose(ctx, session, cmd)
+	if err != nil {
+		plog.Errorf("propose failed, %v", err)
+		return 0, err
+	}
+	if len(result.Data) > 0 {
+		plog.Errorf("not current lease holder (%d)", binaryEnc.Uint64(result.Data))
+		return 0, errors.Wrapf(ErrNotLeaseHolder,
+			"current lease holder ID %d", binaryEnc.Uint64(result.Data))
+	}
+	if result.Value == 0 {
+		panic(moerr.NewError(moerr.INVALID_STATE, "unexpected Lsn value"))
+	}
+	return result.Value, nil
+}
+
+func (l *logStore) GetTruncatedIndex(ctx context.Context,
+	shardID uint64) (uint64, error) {
+	v, err := l.read(ctx, shardID, truncatedIndexTag)
+	if err != nil {
+		return 0, err
+	}
+	return v.(uint64), nil
+}
+
+func (l *logStore) AddLogStoreHeartbeat(ctx context.Context,
+	hb pb.LogStoreHeartbeat) error {
+	data := MustMarshal(&hb)
+	cmd := hakeeper.GetLogStoreHeartbeatCmd(data)
+	session := l.nh.GetNoOPSession(hakeeper.DefaultHAKeeperShardID)
+	if _, err := l.propose(ctx, session, cmd); err != nil {
+		plog.Errorf("propose failed, %v", err)
+		return err
+	}
+	return nil
+}
+
+func (l *logStore) AddDNStoreHeartbeat(ctx context.Context,
+	hb pb.DNStoreHeartbeat) error {
+	data := MustMarshal(&hb)
+	cmd := hakeeper.GetDNStoreHeartbeatCmd(data)
+	session := l.nh.GetNoOPSession(hakeeper.DefaultHAKeeperShardID)
+	if _, err := l.propose(ctx, session, cmd); err != nil {
+		plog.Errorf("propose failed, %v", err)
+		return err
+	}
+	return nil
+}
+
+func (l *logStore) getLeaseHolderID(ctx context.Context,
+	shardID uint64, entries []raftpb.Entry) (uint64, error) {
+	if len(entries) == 0 {
+		panic("empty entries")
+	}
+	// first entry is a update lease cmd
+	e := entries[0]
+	if isSetLeaseHolderUpdate(e.Cmd) {
+		return parseLeaseHolderID(e.Cmd), nil
+	}
+	v, err := l.read(ctx, shardID, leaseHistoryQuery{index: e.Index})
+	if err != nil {
+		plog.Errorf("failed to read, %v", err)
+		return 0, err
+	}
+	return v.(uint64), nil
+}
+
+func (l *logStore) decodeCmd(e raftpb.Entry) []byte {
+	if e.Type == raftpb.ApplicationEntry {
+		panic(moerr.NewError(moerr.INVALID_STATE, "unexpected entry type"))
+	}
+	if e.Type == raftpb.EncodedEntry {
+		if e.Cmd[0] != 0 {
+			panic(moerr.NewError(moerr.INVALID_STATE, "unexpected cmd header"))
+		}
+		return e.Cmd[1:]
+	}
+	panic(moerr.NewError(moerr.INVALID_STATE, "invalid cmd"))
+}
+
+func isRaftInternalEntry(e raftpb.Entry) bool {
+	if len(e.Cmd) == 0 {
+		return true
+	}
+	return e.Type == raftpb.ConfigChangeEntry || e.Type == raftpb.MetadataEntry
+}
+
+func (l *logStore) filterEntries(ctx context.Context,
+	shardID uint64, entries []raftpb.Entry) ([]LogRecord, error) {
+	if len(entries) == 0 {
+		return []LogRecord{}, nil
+	}
+	leaseHolderID, err := l.getLeaseHolderID(ctx, shardID, entries)
+	if err != nil {
+		return nil, err
+	}
+	result := make([]LogRecord, 0)
+	for _, e := range entries {
+		if isRaftInternalEntry(e) {
+			// raft internal stuff
+			continue
+		}
+		cmd := l.decodeCmd(e)
+		if isSetLeaseHolderUpdate(cmd) {
+			leaseHolderID = parseLeaseHolderID(cmd)
+			continue
+		}
+		if isUserUpdate(cmd) {
+			if parseLeaseHolderID(cmd) != leaseHolderID {
+				// lease not match, skip
+				continue
+			}
+			result = append(result, LogRecord{Data: cmd, Index: e.Index})
+		}
+	}
+	return result, nil
+}
+
+func getNextIndex(entries []raftpb.Entry, firstIndex Lsn, lastIndex Lsn) Lsn {
+	if len(entries) == 0 {
+		return firstIndex
+	}
+	lastResultIndex := entries[len(entries)-1].Index
+	if lastResultIndex+1 < lastIndex {
+		return lastResultIndex + 1
+	}
+	return firstIndex
+}
+
+func (l *logStore) QueryLog(ctx context.Context, shardID uint64,
+	firstIndex Lsn, maxSize uint64) ([]LogRecord, Lsn, error) {
+	v, err := l.read(ctx, shardID, indexTag)
+	if err != nil {
+		return nil, 0, err
+	}
+	lastIndex := v.(uint64)
+	// FIXME: check whether lastIndex >= firstIndex
+	rs, err := l.nh.QueryRaftLog(shardID, firstIndex, lastIndex+1, maxSize)
+	if err != nil {
+		plog.Errorf("QueryRaftLog failed, %v", err)
+		return nil, 0, err
+	}
+	select {
+	case v := <-rs.CompletedC:
+		if v.Completed() {
+			entries, logRange := v.RaftLogs()
+			next := getNextIndex(entries, firstIndex, logRange.LastIndex)
+			results, err := l.filterEntries(ctx, shardID, entries)
+			if err != nil {
+				plog.Errorf("filterEntries failed, %v", err)
+				return nil, 0, err
+			}
+			return results, next, nil
+		} else if v.RequestOutOfRange() {
+			plog.Errorf("OutOfRange query found")
+			return nil, 0, ErrOutOfRange
+		}
+		panic(moerr.NewError(moerr.INVALID_STATE, "unexpected rs state"))
+	case <-ctx.Done():
+		return nil, 0, ctx.Err()
+	}
+}
+
+func (l *logStore) ticker() {
+	ticker := time.NewTicker(hakeeper.TickDuration)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-ticker.C:
+			if err := l.hakeeperTick(); err != nil {
+				panic(err)
+			}
+		case <-l.stopper.ShouldStop():
+			return
+		}
+	}
+}
+
+func (l *logStore) truncationWorker() {
+	for {
+		select {
+		case <-l.stopper.ShouldStop():
+			return
+		case <-l.mu.truncateCh:
+			if err := l.truncateLog(); err != nil {
+				panic(err)
+			}
+		}
+	}
+}
+
+// TODO: add test for this
+// FIXME: ignore temp errors
+func (l *logStore) hakeeperTick() error {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
+	leaderID, ok, err := l.nh.GetLeaderID(hakeeper.DefaultHAKeeperShardID)
+	if err != nil {
+		plog.Errorf("failed to get HAKeeper Leader ID, %v", err)
+		return err
+	}
+	if ok && leaderID == l.haKeeperReplicaID {
+		cmd := hakeeper.GetTickCmd()
+		session := l.nh.GetNoOPSession(hakeeper.DefaultHAKeeperShardID)
+		if _, err := l.propose(ctx, session, cmd); err != nil {
+			plog.Errorf("propose failed, %v", err)
+			return err
+		}
+	}
+	return nil
+}
+
+// TODO: add tests for this
+func (l *logStore) truncateLog() error {
+	l.mu.Lock()
+	pendings := l.mu.pendingTruncate
+	l.mu.pendingTruncate = make(map[uint64]struct{})
+	l.mu.Unlock()
+
+	for shardID := range pendings {
+		select {
+		case <-l.stopper.ShouldStop():
+			return nil
+		default:
+		}
+
+		if err := func() error {
+			ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
+			defer cancel()
+			index, err := l.GetTruncatedIndex(ctx, shardID)
+			if err != nil {
+				plog.Errorf("GetTruncatedIndex failed, %v", err)
+				// FIXME: check error type, see whether it is a tmp one
+				return err
+			}
+			// the first 4 entries for a 3-replica raft group are tiny anyway
+			if index > 1 {
+				opts := dragonboat.SnapshotOption{
+					OverrideCompactionOverhead: true,
+					CompactionIndex:            index - 1,
+				}
+				if _, err := l.nh.SyncRequestSnapshot(ctx, shardID, opts); err != nil {
+					plog.Errorf("SyncRequestSnapshot failed, %v", err)
+					// FIXME: check error type, see whether it is a tmp one
+					return err
+				}
+			}
+			return nil
+		}(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (l *logStore) getHeartbeatMessage() pb.LogStoreHeartbeat {
+	m := pb.LogStoreHeartbeat{
+		UUID:           l.cfg.NodeHostID,
+		RaftAddress:    l.cfg.RaftAddress,
+		ServiceAddress: l.cfg.ServiceAddress,
+		GossipAddress:  l.cfg.GossipAddress,
+		Shards:         make([]pb.LogShardInfo, 0),
+	}
+	opts := dragonboat.NodeHostInfoOption{
+		SkipLogInfo: true,
+	}
+	nhi := l.nh.GetNodeHostInfo(opts)
+	for _, ci := range nhi.ShardInfoList {
+		shardInfo := pb.LogShardInfo{
+			ShardID:  ci.ShardID,
+			Replicas: ci.Nodes,
+			Epoch:    ci.ConfigChangeIndex,
+			LeaderID: ci.LeaderID,
+			Term:     ci.Term,
+		}
+		if shardInfo.Replicas == nil {
+			shardInfo.Replicas = make(map[uint64]dragonboat.Target)
+		}
+		m.Shards = append(m.Shards, shardInfo)
+	}
+	return m
+}
diff --git a/pkg/logservice/transport.go b/pkg/logservice/transport.go
new file mode 100644
index 000000000..44ed870af
--- /dev/null
+++ b/pkg/logservice/transport.go
@@ -0,0 +1,321 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"bytes"
+	"context"
+	"io"
+	"net"
+	"time"
+
+	"github.com/matrixorigin/matrixone/pkg/common/moerr"
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+var (
+	ErrInvalidBuffer   = moerr.NewError(moerr.INVALID_STATE, "invalid buffer size")
+	ErrBadMessage      = moerr.NewError(moerr.INVALID_INPUT, "invalid message")
+	errPoisonReceived  = moerr.NewError(moerr.INVALID_STATE, "poison received")
+	ErrDeadlineNotSet  = moerr.NewError(moerr.INVALID_INPUT, "deadline not set")
+	ErrInvalidDeadline = moerr.NewError(moerr.INVALID_INPUT, "invalid deadline")
+
+	magicNumber          = [2]byte{0x2A, 0xBD}
+	poisonNumber         = [2]byte{0x2B, 0xBE}
+	requestSendDuration  = 2 * time.Second
+	requestReadDuration  = 2 * time.Second
+	responseSendDuration = 2 * time.Second
+	responseReadDuration = 2 * time.Second
+	writeDuration        = 10 * time.Second
+	readDuration         = 10 * time.Second
+	keepAlivePeriod      = 5 * time.Second
+
+	reqBufSize  = 2 * 1024
+	recvBufSize = 512 * 1024
+)
+
+func sendPoison(conn net.Conn, poison []byte) error {
+	tt := time.Now().Add(requestSendDuration)
+	if err := conn.SetWriteDeadline(tt); err != nil {
+		return err
+	}
+	if _, err := conn.Write(poison); err != nil {
+		return err
+	}
+	return nil
+}
+
+func sendPoisonAck(conn net.Conn, poisonAck []byte) error {
+	return sendPoison(conn, poisonAck)
+}
+
+func waitPoisonAck(conn net.Conn) error {
+	ack := make([]byte, len(poisonNumber))
+	tt := time.Now().Add(keepAlivePeriod)
+	if err := conn.SetReadDeadline(tt); err != nil {
+		return err
+	}
+	if _, err := io.ReadFull(conn, ack); err != nil {
+		return err
+	}
+	return nil
+}
+
+func readSize(conn net.Conn, buf []byte) (int, error) {
+	if len(buf) < 4 {
+		buf = make([]byte, 4)
+	}
+	szbuf := buf[:4]
+	tt := time.Now().Add(requestReadDuration)
+	if err := conn.SetWriteDeadline(tt); err != nil {
+		return 0, err
+	}
+	if _, err := io.ReadFull(conn, szbuf); err != nil {
+		return 0, err
+	}
+	return int(binaryEnc.Uint32(szbuf)), nil
+}
+
+// FIXME: add data corruption check
+func writeRequest(conn net.Conn,
+	req pb.Request, buf []byte, payload []byte) error {
+	if len(buf) < req.Size()+4+len(magicNumber[:]) {
+		buf = make([]byte, req.Size()+4+len(magicNumber[:]))
+	}
+	n, err := req.MarshalTo(buf[4+len(magicNumber[:]):])
+	if err != nil {
+		panic(err)
+	}
+
+	copy(buf, magicNumber[:])
+	binaryEnc.PutUint32(buf[len(magicNumber[:]):], uint32(n))
+	buf = buf[:n+4+len(magicNumber[:])]
+
+	tt := time.Now().Add(requestSendDuration)
+	if err := conn.SetWriteDeadline(tt); err != nil {
+		return err
+	}
+	if _, err := conn.Write(buf); err != nil {
+		return err
+	}
+	if len(payload) > 0 {
+		tt = time.Now().Add(writeDuration)
+		if err := conn.SetWriteDeadline(tt); err != nil {
+			return err
+		}
+		if _, err := conn.Write(payload); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func readRequest(conn net.Conn,
+	buf []byte, payload []byte) (pb.Request, []byte, error) {
+	size, err := readSize(conn, buf)
+	if err != nil {
+		return pb.Request{}, nil, err
+	}
+	if len(buf) < size {
+		buf = make([]byte, size)
+	}
+	buf = buf[:size]
+	if _, err := io.ReadFull(conn, buf); err != nil {
+		return pb.Request{}, nil, err
+	}
+	var req pb.Request
+	MustUnmarshal(&req, buf)
+	if req.PayloadSize > 0 {
+		if uint64(len(payload)) < req.PayloadSize {
+			payload = make([]byte, req.PayloadSize)
+		}
+		payload = payload[:req.PayloadSize]
+		tt := time.Now().Add(readDuration)
+		if err := conn.SetWriteDeadline(tt); err != nil {
+			return pb.Request{}, nil, err
+		}
+		if _, err := io.ReadFull(conn, payload); err != nil {
+			return pb.Request{}, nil, err
+		}
+	}
+	return req, payload, nil
+}
+
+func writeResponse(conn net.Conn, resp pb.Response,
+	records []byte, buf []byte) error {
+	if len(buf) < resp.Size()+4 {
+		buf = make([]byte, resp.Size()+4)
+	}
+	if resp.PayloadSize != uint64(len(records)) {
+		panic("Payload size not set")
+	}
+
+	n, err := resp.MarshalTo(buf[4:])
+	if err != nil {
+		panic(err)
+	}
+	buf = buf[:n+4]
+	binaryEnc.PutUint32(buf, uint32(n))
+
+	tt := time.Now().Add(responseSendDuration)
+	if err := conn.SetWriteDeadline(tt); err != nil {
+		return err
+	}
+	if _, err := conn.Write(buf); err != nil {
+		return err
+	}
+
+	if len(records) > 0 {
+		sent := 0
+		bufSize := int(recvBufSize)
+		for sent < len(records) {
+			if sent+bufSize > len(records) {
+				bufSize = len(records) - sent
+			}
+			tt = time.Now().Add(writeDuration)
+			if err := conn.SetWriteDeadline(tt); err != nil {
+				return err
+			}
+			if _, err := conn.Write(records[sent : sent+bufSize]); err != nil {
+				return err
+			}
+			sent += bufSize
+		}
+	}
+
+	return nil
+}
+
+func readResponse(conn net.Conn,
+	buf []byte) (pb.Response, pb.LogRecordResponse, error) {
+	size, err := readSize(conn, buf)
+	if err != nil {
+		return pb.Response{}, pb.LogRecordResponse{}, err
+	}
+
+	if len(buf) < size {
+		buf = make([]byte, size)
+	}
+	buf = buf[:size]
+
+	tt := time.Now().Add(responseReadDuration)
+	if err := conn.SetReadDeadline(tt); err != nil {
+		return pb.Response{}, pb.LogRecordResponse{}, err
+	}
+	if _, err := io.ReadFull(conn, buf); err != nil {
+		return pb.Response{}, pb.LogRecordResponse{}, err
+	}
+	var resp pb.Response
+	MustUnmarshal(&resp, buf)
+	size = int(resp.PayloadSize)
+	if size > len(buf) {
+		buf = make([]byte, size)
+	} else {
+		buf = buf[:size]
+	}
+
+	received := uint64(0)
+	var recvBuf []byte
+	if size < recvBufSize {
+		recvBuf = buf[:size]
+	} else {
+		recvBuf = buf[:recvBufSize]
+	}
+	toRead := size
+	for toRead > 0 {
+		tt = time.Now().Add(readDuration)
+		if err := conn.SetReadDeadline(tt); err != nil {
+			return pb.Response{}, pb.LogRecordResponse{}, err
+		}
+		if _, err := io.ReadFull(conn, recvBuf); err != nil {
+			return pb.Response{}, pb.LogRecordResponse{}, err
+		}
+		toRead -= len(recvBuf)
+		received += uint64(len(recvBuf))
+		if toRead < recvBufSize {
+			recvBuf = buf[received : received+uint64(toRead)]
+		} else {
+			recvBuf = buf[received : received+uint64(recvBufSize)]
+		}
+	}
+
+	var logs pb.LogRecordResponse
+	MustUnmarshal(&logs, buf)
+
+	return resp, logs, nil
+}
+
+func readMagicNumber(conn net.Conn, buf []byte) error {
+	tt := time.Now().Add(requestReadDuration)
+	if err := conn.SetReadDeadline(tt); err != nil {
+		return err
+	}
+	if len(buf) < len(magicNumber[:]) {
+		buf = make([]byte, len(magicNumber[:]))
+	}
+	buf = buf[:len(magicNumber[:])]
+	if _, err := io.ReadFull(conn, buf); err != nil {
+		return err
+	}
+	if bytes.Equal(buf, poisonNumber[:]) {
+		return errPoisonReceived
+	}
+	if !bytes.Equal(buf, magicNumber[:]) {
+		return ErrBadMessage
+	}
+	return nil
+}
+
+func getConnection(ctx context.Context, target string) (net.Conn, error) {
+	timeout, err := getTimeoutFromContext(ctx)
+	if err != nil {
+		return nil, err
+	}
+	conn, err := net.DialTimeout("tcp", target, timeout)
+	if err != nil {
+		return nil, err
+	}
+	tcpconn, ok := conn.(*net.TCPConn)
+	if ok {
+		if err := setTCPConn(tcpconn); err != nil {
+			return nil, err
+		}
+	} else {
+		panic("unexpected connection type")
+	}
+	return conn, nil
+}
+
+func setTCPConn(conn *net.TCPConn) error {
+	if err := conn.SetLinger(0); err != nil {
+		return err
+	}
+	if err := conn.SetKeepAlive(true); err != nil {
+		return err
+	}
+	return conn.SetKeepAlivePeriod(keepAlivePeriod)
+}
+
+func getTimeoutFromContext(ctx context.Context) (time.Duration, error) {
+	d, ok := ctx.Deadline()
+	if !ok {
+		return 0, ErrDeadlineNotSet
+	}
+	now := time.Now()
+	if now.After(d) {
+		return 0, ErrInvalidDeadline
+	}
+	return d.Sub(now), nil
+}
diff --git a/pkg/logservice/utils.go b/pkg/logservice/utils.go
new file mode 100644
index 000000000..ff96f5703
--- /dev/null
+++ b/pkg/logservice/utils.go
@@ -0,0 +1,37 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+type Marshaler interface {
+	Marshal() ([]byte, error)
+}
+
+type Unmarshaler interface {
+	Unmarshal([]byte) error
+}
+
+func MustMarshal(m Marshaler) []byte {
+	data, err := m.Marshal()
+	if err != nil {
+		panic(err)
+	}
+	return data
+}
+
+func MustUnmarshal(m Unmarshaler, data []byte) {
+	if err := m.Unmarshal(data); err != nil {
+		panic(err)
+	}
+}
diff --git a/pkg/pb/logservice/logservice.pb.go b/pkg/pb/logservice/logservice.pb.go
new file mode 100644
index 000000000..68637af35
--- /dev/null
+++ b/pkg/pb/logservice/logservice.pb.go
@@ -0,0 +1,3278 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: logservice.proto
+
+package logservice
+
+import (
+	fmt "fmt"
+	_ "github.com/gogo/protobuf/gogoproto"
+	proto "github.com/gogo/protobuf/proto"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+type MethodType int32
+
+const (
+	MethodType_CREATE       MethodType = 0
+	MethodType_DESTROY      MethodType = 1
+	MethodType_APPEND       MethodType = 2
+	MethodType_READ         MethodType = 3
+	MethodType_TRUNCATE     MethodType = 4
+	MethodType_GET_TRUNCATE MethodType = 5
+	MethodType_CONNECT      MethodType = 6
+	MethodType_CONNECT_RO   MethodType = 7
+)
+
+var MethodType_name = map[int32]string{
+	0: "CREATE",
+	1: "DESTROY",
+	2: "APPEND",
+	3: "READ",
+	4: "TRUNCATE",
+	5: "GET_TRUNCATE",
+	6: "CONNECT",
+	7: "CONNECT_RO",
+}
+
+var MethodType_value = map[string]int32{
+	"CREATE":       0,
+	"DESTROY":      1,
+	"APPEND":       2,
+	"READ":         3,
+	"TRUNCATE":     4,
+	"GET_TRUNCATE": 5,
+	"CONNECT":      6,
+	"CONNECT_RO":   7,
+}
+
+func (x MethodType) String() string {
+	return proto.EnumName(MethodType_name, int32(x))
+}
+
+func (MethodType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{0}
+}
+
+type ErrorCode int32
+
+const (
+	ErrorCode_NoError               ErrorCode = 0
+	ErrorCode_Timeout               ErrorCode = 1
+	ErrorCode_Canceled              ErrorCode = 2
+	ErrorCode_InvalidShard          ErrorCode = 3
+	ErrorCode_InvalidTimeout        ErrorCode = 4
+	ErrorCode_InvalidPayload        ErrorCode = 5
+	ErrorCode_InvalidPayloadSize    ErrorCode = 6
+	ErrorCode_Rejected              ErrorCode = 7
+	ErrorCode_ShardNotReady         ErrorCode = 8
+	ErrorCode_SystemClosed          ErrorCode = 9
+	ErrorCode_IndexAlreadyTruncated ErrorCode = 100
+	ErrorCode_OutOfRange            ErrorCode = 101
+	ErrorCode_NotLeaseHolder        ErrorCode = 102
+	ErrorCode_OtherSystemError      ErrorCode = 1000
+)
+
+var ErrorCode_name = map[int32]string{
+	0:    "NoError",
+	1:    "Timeout",
+	2:    "Canceled",
+	3:    "InvalidShard",
+	4:    "InvalidTimeout",
+	5:    "InvalidPayload",
+	6:    "InvalidPayloadSize",
+	7:    "Rejected",
+	8:    "ShardNotReady",
+	9:    "SystemClosed",
+	100:  "IndexAlreadyTruncated",
+	101:  "OutOfRange",
+	102:  "NotLeaseHolder",
+	1000: "OtherSystemError",
+}
+
+var ErrorCode_value = map[string]int32{
+	"NoError":               0,
+	"Timeout":               1,
+	"Canceled":              2,
+	"InvalidShard":          3,
+	"InvalidTimeout":        4,
+	"InvalidPayload":        5,
+	"InvalidPayloadSize":    6,
+	"Rejected":              7,
+	"ShardNotReady":         8,
+	"SystemClosed":          9,
+	"IndexAlreadyTruncated": 100,
+	"OutOfRange":            101,
+	"NotLeaseHolder":        102,
+	"OtherSystemError":      1000,
+}
+
+func (x ErrorCode) String() string {
+	return proto.EnumName(ErrorCode_name, int32(x))
+}
+
+func (ErrorCode) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{1}
+}
+
+type ShardState_State int32
+
+const (
+	ShardState_REGISTERED ShardState_State = 0
+	ShardState_LAUNCHING  ShardState_State = 1
+	ShardState_LAUNCHED   ShardState_State = 2
+	ShardState_ABORTED    ShardState_State = 3
+)
+
+var ShardState_State_name = map[int32]string{
+	0: "REGISTERED",
+	1: "LAUNCHING",
+	2: "LAUNCHED",
+	3: "ABORTED",
+}
+
+var ShardState_State_value = map[string]int32{
+	"REGISTERED": 0,
+	"LAUNCHING":  1,
+	"LAUNCHED":   2,
+	"ABORTED":    3,
+}
+
+func (x ShardState_State) String() string {
+	return proto.EnumName(ShardState_State_name, int32(x))
+}
+
+func (ShardState_State) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{0, 0}
+}
+
+// ShardState represents the state of a registered Shard.
+type ShardState struct {
+	Name        string           `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
+	ShardID     uint64           `protobuf:"varint,2,opt,name=ShardID,proto3" json:"ShardID,omitempty"`
+	LaunchState ShardState_State `protobuf:"varint,3,opt,name=LaunchState,proto3,enum=logservice.ShardState_State" json:"LaunchState,omitempty"`
+	// InitialReplicas is a map of ReplicaID to LogStore UUIDs, it describes the
+	// initial replicas assigned for the given shard.
+	InitialReplicas      map[uint64]string `protobuf:"bytes,4,rep,name=InitialReplicas,proto3" json:"InitialReplicas,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	XXX_NoUnkeyedLiteral struct{}          `json:"-"`
+	XXX_unrecognized     []byte            `json:"-"`
+	XXX_sizecache        int32             `json:"-"`
+}
+
+func (m *ShardState) Reset()         { *m = ShardState{} }
+func (m *ShardState) String() string { return proto.CompactTextString(m) }
+func (*ShardState) ProtoMessage()    {}
+func (*ShardState) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{0}
+}
+func (m *ShardState) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ShardState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_ShardState.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *ShardState) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ShardState.Merge(m, src)
+}
+func (m *ShardState) XXX_Size() int {
+	return m.Size()
+}
+func (m *ShardState) XXX_DiscardUnknown() {
+	xxx_messageInfo_ShardState.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ShardState proto.InternalMessageInfo
+
+func (m *ShardState) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *ShardState) GetShardID() uint64 {
+	if m != nil {
+		return m.ShardID
+	}
+	return 0
+}
+
+func (m *ShardState) GetLaunchState() ShardState_State {
+	if m != nil {
+		return m.LaunchState
+	}
+	return ShardState_REGISTERED
+}
+
+func (m *ShardState) GetInitialReplicas() map[uint64]string {
+	if m != nil {
+		return m.InitialReplicas
+	}
+	return nil
+}
+
+// LogShardInfo contains information of a launched shard.
+type LogShardInfo struct {
+	ShardID uint64 `protobuf:"varint,1,opt,name=ShardID,proto3" json:"ShardID,omitempty"`
+	// Replicas is a map of ReplicaID to LogStore UUID, it describe the member
+	// replicas of the shard at the given Epoch.
+	Replicas map[uint64]string `protobuf:"bytes,2,rep,name=Replicas,proto3" json:"Replicas,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	// Epoch is the epoch value of the Shard, member replicas of the Shard can
+	// change across epochs.
+	Epoch uint64 `protobuf:"varint,3,opt,name=Epoch,proto3" json:"Epoch,omitempty"`
+	// LeaderID is the ReplicaID of the leader replica at the given term. When
+	// LeaderID is 0, it means there is no leader or the leader is unknown.
+	LeaderID uint64 `protobuf:"varint,4,opt,name=LeaderID,proto3" json:"LeaderID,omitempty"`
+	// Term is the Raft term value.
+	Term                 uint64   `protobuf:"varint,5,opt,name=Term,proto3" json:"Term,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *LogShardInfo) Reset()         { *m = LogShardInfo{} }
+func (m *LogShardInfo) String() string { return proto.CompactTextString(m) }
+func (*LogShardInfo) ProtoMessage()    {}
+func (*LogShardInfo) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{1}
+}
+func (m *LogShardInfo) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *LogShardInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_LogShardInfo.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *LogShardInfo) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_LogShardInfo.Merge(m, src)
+}
+func (m *LogShardInfo) XXX_Size() int {
+	return m.Size()
+}
+func (m *LogShardInfo) XXX_DiscardUnknown() {
+	xxx_messageInfo_LogShardInfo.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LogShardInfo proto.InternalMessageInfo
+
+func (m *LogShardInfo) GetShardID() uint64 {
+	if m != nil {
+		return m.ShardID
+	}
+	return 0
+}
+
+func (m *LogShardInfo) GetReplicas() map[uint64]string {
+	if m != nil {
+		return m.Replicas
+	}
+	return nil
+}
+
+func (m *LogShardInfo) GetEpoch() uint64 {
+	if m != nil {
+		return m.Epoch
+	}
+	return 0
+}
+
+func (m *LogShardInfo) GetLeaderID() uint64 {
+	if m != nil {
+		return m.LeaderID
+	}
+	return 0
+}
+
+func (m *LogShardInfo) GetTerm() uint64 {
+	if m != nil {
+		return m.Term
+	}
+	return 0
+}
+
+// LogStoreHeartbeat is the periodic message sent to the HAKeeper by Log Stores.
+type LogStoreHeartbeat struct {
+	// UUID is the uuid of the Log Store.
+	UUID           string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"`
+	RaftAddress    string `protobuf:"bytes,2,opt,name=RaftAddress,proto3" json:"RaftAddress,omitempty"`
+	ServiceAddress string `protobuf:"bytes,3,opt,name=ServiceAddress,proto3" json:"ServiceAddress,omitempty"`
+	GossipAddress  string `protobuf:"bytes,4,opt,name=GossipAddress,proto3" json:"GossipAddress,omitempty"`
+	// Shards is a list of LogShardInfo instances collected on the specified
+	// LogStore. Details in Shards are based on the local knowledge of each
+	// replica running on the current LogStore, it may not be accurate or
+	// update to date due to various reasons.
+	Shards               []LogShardInfo `protobuf:"bytes,5,rep,name=Shards,proto3" json:"Shards"`
+	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
+	XXX_unrecognized     []byte         `json:"-"`
+	XXX_sizecache        int32          `json:"-"`
+}
+
+func (m *LogStoreHeartbeat) Reset()         { *m = LogStoreHeartbeat{} }
+func (m *LogStoreHeartbeat) String() string { return proto.CompactTextString(m) }
+func (*LogStoreHeartbeat) ProtoMessage()    {}
+func (*LogStoreHeartbeat) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{2}
+}
+func (m *LogStoreHeartbeat) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *LogStoreHeartbeat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_LogStoreHeartbeat.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *LogStoreHeartbeat) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_LogStoreHeartbeat.Merge(m, src)
+}
+func (m *LogStoreHeartbeat) XXX_Size() int {
+	return m.Size()
+}
+func (m *LogStoreHeartbeat) XXX_DiscardUnknown() {
+	xxx_messageInfo_LogStoreHeartbeat.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LogStoreHeartbeat proto.InternalMessageInfo
+
+func (m *LogStoreHeartbeat) GetUUID() string {
+	if m != nil {
+		return m.UUID
+	}
+	return ""
+}
+
+func (m *LogStoreHeartbeat) GetRaftAddress() string {
+	if m != nil {
+		return m.RaftAddress
+	}
+	return ""
+}
+
+func (m *LogStoreHeartbeat) GetServiceAddress() string {
+	if m != nil {
+		return m.ServiceAddress
+	}
+	return ""
+}
+
+func (m *LogStoreHeartbeat) GetGossipAddress() string {
+	if m != nil {
+		return m.GossipAddress
+	}
+	return ""
+}
+
+func (m *LogStoreHeartbeat) GetShards() []LogShardInfo {
+	if m != nil {
+		return m.Shards
+	}
+	return nil
+}
+
+// DNShardInfo contains information of a launched DN shard.
+type DNShardInfo struct {
+	// ShardID uniquely identifies a DN shard. Each DN shard manages a Primary
+	// Key range or hashed Primary Key value range.
+	ShardID uint64 `protobuf:"varint,1,opt,name=ShardID,proto3" json:"ShardID,omitempty"`
+	// ReplicaID uniquely identifies a DN shard instance. After repairing a
+	// failed DN shard, a new DN shard instance is created with a new ReplicaID
+	// value.
+	ReplicaID            uint64   `protobuf:"varint,2,opt,name=ReplicaID,proto3" json:"ReplicaID,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *DNShardInfo) Reset()         { *m = DNShardInfo{} }
+func (m *DNShardInfo) String() string { return proto.CompactTextString(m) }
+func (*DNShardInfo) ProtoMessage()    {}
+func (*DNShardInfo) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{3}
+}
+func (m *DNShardInfo) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *DNShardInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_DNShardInfo.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *DNShardInfo) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_DNShardInfo.Merge(m, src)
+}
+func (m *DNShardInfo) XXX_Size() int {
+	return m.Size()
+}
+func (m *DNShardInfo) XXX_DiscardUnknown() {
+	xxx_messageInfo_DNShardInfo.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DNShardInfo proto.InternalMessageInfo
+
+func (m *DNShardInfo) GetShardID() uint64 {
+	if m != nil {
+		return m.ShardID
+	}
+	return 0
+}
+
+func (m *DNShardInfo) GetReplicaID() uint64 {
+	if m != nil {
+		return m.ReplicaID
+	}
+	return 0
+}
+
+// DNStoreHeartbeat is the periodic message sent to the HAKeeper by DN stores.
+type DNStoreHeartbeat struct {
+	// UUID is the uuid of the DN Store.
+	UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"`
+	// Shards is a list of DNShardInfo instances collected on the specified
+	// DN store.
+	Shards               []DNShardInfo `protobuf:"bytes,2,rep,name=Shards,proto3" json:"Shards"`
+	XXX_NoUnkeyedLiteral struct{}      `json:"-"`
+	XXX_unrecognized     []byte        `json:"-"`
+	XXX_sizecache        int32         `json:"-"`
+}
+
+func (m *DNStoreHeartbeat) Reset()         { *m = DNStoreHeartbeat{} }
+func (m *DNStoreHeartbeat) String() string { return proto.CompactTextString(m) }
+func (*DNStoreHeartbeat) ProtoMessage()    {}
+func (*DNStoreHeartbeat) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{4}
+}
+func (m *DNStoreHeartbeat) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *DNStoreHeartbeat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_DNStoreHeartbeat.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *DNStoreHeartbeat) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_DNStoreHeartbeat.Merge(m, src)
+}
+func (m *DNStoreHeartbeat) XXX_Size() int {
+	return m.Size()
+}
+func (m *DNStoreHeartbeat) XXX_DiscardUnknown() {
+	xxx_messageInfo_DNStoreHeartbeat.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DNStoreHeartbeat proto.InternalMessageInfo
+
+func (m *DNStoreHeartbeat) GetUUID() string {
+	if m != nil {
+		return m.UUID
+	}
+	return ""
+}
+
+func (m *DNStoreHeartbeat) GetShards() []DNShardInfo {
+	if m != nil {
+		return m.Shards
+	}
+	return nil
+}
+
+type LogRecord struct {
+	Index                uint64   `protobuf:"varint,1,opt,name=Index,proto3" json:"Index,omitempty"`
+	Data                 []byte   `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *LogRecord) Reset()         { *m = LogRecord{} }
+func (m *LogRecord) String() string { return proto.CompactTextString(m) }
+func (*LogRecord) ProtoMessage()    {}
+func (*LogRecord) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{5}
+}
+func (m *LogRecord) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *LogRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_LogRecord.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *LogRecord) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_LogRecord.Merge(m, src)
+}
+func (m *LogRecord) XXX_Size() int {
+	return m.Size()
+}
+func (m *LogRecord) XXX_DiscardUnknown() {
+	xxx_messageInfo_LogRecord.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LogRecord proto.InternalMessageInfo
+
+func (m *LogRecord) GetIndex() uint64 {
+	if m != nil {
+		return m.Index
+	}
+	return 0
+}
+
+func (m *LogRecord) GetData() []byte {
+	if m != nil {
+		return m.Data
+	}
+	return nil
+}
+
+type Request struct {
+	Method               MethodType `protobuf:"varint,1,opt,name=Method,proto3,enum=logservice.MethodType" json:"Method,omitempty"`
+	Name                 string     `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
+	ShardID              uint64     `protobuf:"varint,3,opt,name=ShardID,proto3" json:"ShardID,omitempty"`
+	Index                uint64     `protobuf:"varint,4,opt,name=Index,proto3" json:"Index,omitempty"`
+	MaxSize              uint64     `protobuf:"varint,5,opt,name=MaxSize,proto3" json:"MaxSize,omitempty"`
+	Timeout              int64      `protobuf:"varint,6,opt,name=Timeout,proto3" json:"Timeout,omitempty"`
+	DNShardID            uint64     `protobuf:"varint,7,opt,name=DNShardID,proto3" json:"DNShardID,omitempty"`
+	DNID                 uint64     `protobuf:"varint,8,opt,name=DNID,proto3" json:"DNID,omitempty"`
+	PayloadSize          uint64     `protobuf:"varint,9,opt,name=PayloadSize,proto3" json:"PayloadSize,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
+}
+
+func (m *Request) Reset()         { *m = Request{} }
+func (m *Request) String() string { return proto.CompactTextString(m) }
+func (*Request) ProtoMessage()    {}
+func (*Request) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{6}
+}
+func (m *Request) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Request.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *Request) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Request.Merge(m, src)
+}
+func (m *Request) XXX_Size() int {
+	return m.Size()
+}
+func (m *Request) XXX_DiscardUnknown() {
+	xxx_messageInfo_Request.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Request proto.InternalMessageInfo
+
+func (m *Request) GetMethod() MethodType {
+	if m != nil {
+		return m.Method
+	}
+	return MethodType_CREATE
+}
+
+func (m *Request) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *Request) GetShardID() uint64 {
+	if m != nil {
+		return m.ShardID
+	}
+	return 0
+}
+
+func (m *Request) GetIndex() uint64 {
+	if m != nil {
+		return m.Index
+	}
+	return 0
+}
+
+func (m *Request) GetMaxSize() uint64 {
+	if m != nil {
+		return m.MaxSize
+	}
+	return 0
+}
+
+func (m *Request) GetTimeout() int64 {
+	if m != nil {
+		return m.Timeout
+	}
+	return 0
+}
+
+func (m *Request) GetDNShardID() uint64 {
+	if m != nil {
+		return m.DNShardID
+	}
+	return 0
+}
+
+func (m *Request) GetDNID() uint64 {
+	if m != nil {
+		return m.DNID
+	}
+	return 0
+}
+
+func (m *Request) GetPayloadSize() uint64 {
+	if m != nil {
+		return m.PayloadSize
+	}
+	return 0
+}
+
+type Response struct {
+	Method               MethodType `protobuf:"varint,1,opt,name=Method,proto3,enum=logservice.MethodType" json:"Method,omitempty"`
+	ErrorCode            ErrorCode  `protobuf:"varint,2,opt,name=ErrorCode,proto3,enum=logservice.ErrorCode" json:"ErrorCode,omitempty"`
+	ErrorMessage         string     `protobuf:"bytes,3,opt,name=ErrorMessage,proto3" json:"ErrorMessage,omitempty"`
+	ShardID              uint64     `protobuf:"varint,4,opt,name=ShardID,proto3" json:"ShardID,omitempty"`
+	Index                uint64     `protobuf:"varint,5,opt,name=Index,proto3" json:"Index,omitempty"`
+	LastIndex            uint64     `protobuf:"varint,6,opt,name=LastIndex,proto3" json:"LastIndex,omitempty"`
+	PayloadSize          uint64     `protobuf:"varint,7,opt,name=PayloadSize,proto3" json:"PayloadSize,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
+}
+
+func (m *Response) Reset()         { *m = Response{} }
+func (m *Response) String() string { return proto.CompactTextString(m) }
+func (*Response) ProtoMessage()    {}
+func (*Response) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{7}
+}
+func (m *Response) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Response.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *Response) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Response.Merge(m, src)
+}
+func (m *Response) XXX_Size() int {
+	return m.Size()
+}
+func (m *Response) XXX_DiscardUnknown() {
+	xxx_messageInfo_Response.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Response proto.InternalMessageInfo
+
+func (m *Response) GetMethod() MethodType {
+	if m != nil {
+		return m.Method
+	}
+	return MethodType_CREATE
+}
+
+func (m *Response) GetErrorCode() ErrorCode {
+	if m != nil {
+		return m.ErrorCode
+	}
+	return ErrorCode_NoError
+}
+
+func (m *Response) GetErrorMessage() string {
+	if m != nil {
+		return m.ErrorMessage
+	}
+	return ""
+}
+
+func (m *Response) GetShardID() uint64 {
+	if m != nil {
+		return m.ShardID
+	}
+	return 0
+}
+
+func (m *Response) GetIndex() uint64 {
+	if m != nil {
+		return m.Index
+	}
+	return 0
+}
+
+func (m *Response) GetLastIndex() uint64 {
+	if m != nil {
+		return m.LastIndex
+	}
+	return 0
+}
+
+func (m *Response) GetPayloadSize() uint64 {
+	if m != nil {
+		return m.PayloadSize
+	}
+	return 0
+}
+
+type LogRecordResponse struct {
+	Records              []LogRecord `protobuf:"bytes,1,rep,name=Records,proto3" json:"Records"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *LogRecordResponse) Reset()         { *m = LogRecordResponse{} }
+func (m *LogRecordResponse) String() string { return proto.CompactTextString(m) }
+func (*LogRecordResponse) ProtoMessage()    {}
+func (*LogRecordResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_fd1040c5381ab5a7, []int{8}
+}
+func (m *LogRecordResponse) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *LogRecordResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_LogRecordResponse.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *LogRecordResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_LogRecordResponse.Merge(m, src)
+}
+func (m *LogRecordResponse) XXX_Size() int {
+	return m.Size()
+}
+func (m *LogRecordResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_LogRecordResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LogRecordResponse proto.InternalMessageInfo
+
+func (m *LogRecordResponse) GetRecords() []LogRecord {
+	if m != nil {
+		return m.Records
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterEnum("logservice.MethodType", MethodType_name, MethodType_value)
+	proto.RegisterEnum("logservice.ErrorCode", ErrorCode_name, ErrorCode_value)
+	proto.RegisterEnum("logservice.ShardState_State", ShardState_State_name, ShardState_State_value)
+	proto.RegisterType((*ShardState)(nil), "logservice.ShardState")
+	proto.RegisterMapType((map[uint64]string)(nil), "logservice.ShardState.InitialReplicasEntry")
+	proto.RegisterType((*LogShardInfo)(nil), "logservice.LogShardInfo")
+	proto.RegisterMapType((map[uint64]string)(nil), "logservice.LogShardInfo.ReplicasEntry")
+	proto.RegisterType((*LogStoreHeartbeat)(nil), "logservice.LogStoreHeartbeat")
+	proto.RegisterType((*DNShardInfo)(nil), "logservice.DNShardInfo")
+	proto.RegisterType((*DNStoreHeartbeat)(nil), "logservice.DNStoreHeartbeat")
+	proto.RegisterType((*LogRecord)(nil), "logservice.LogRecord")
+	proto.RegisterType((*Request)(nil), "logservice.Request")
+	proto.RegisterType((*Response)(nil), "logservice.Response")
+	proto.RegisterType((*LogRecordResponse)(nil), "logservice.LogRecordResponse")
+}
+
+func init() { proto.RegisterFile("logservice.proto", fileDescriptor_fd1040c5381ab5a7) }
+
+var fileDescriptor_fd1040c5381ab5a7 = []byte{
+	// 982 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x95, 0xcf, 0x6f, 0xe3, 0x44,
+	0x14, 0xc7, 0x6b, 0xc7, 0xf9, 0xe1, 0xd7, 0x34, 0xcc, 0x8e, 0xb6, 0x8b, 0xa9, 0xaa, 0x12, 0x45,
+	0x68, 0x55, 0x2d, 0x22, 0x2b, 0x75, 0x55, 0x84, 0x40, 0x42, 0x4a, 0x63, 0xab, 0x0d, 0x4a, 0x9d,
+	0x6a, 0xe2, 0x1e, 0x38, 0xa0, 0xd5, 0x34, 0x9e, 0x26, 0x01, 0xc7, 0x13, 0xec, 0x49, 0xd5, 0x70,
+	0x45, 0x5c, 0xf9, 0xbb, 0xf6, 0x06, 0x47, 0x4e, 0x08, 0xf5, 0xc4, 0x8d, 0x7f, 0x01, 0xcd, 0x8c,
+	0xe3, 0x38, 0x61, 0x2b, 0x2d, 0x7b, 0x89, 0xe6, 0x7d, 0xe7, 0xbd, 0x37, 0xef, 0x7d, 0xe6, 0x79,
+	0x02, 0x28, 0xe2, 0xe3, 0x94, 0x25, 0x77, 0xd3, 0x11, 0x6b, 0xcf, 0x13, 0x2e, 0x38, 0x86, 0xb5,
+	0x72, 0xf0, 0xd9, 0x78, 0x2a, 0x26, 0x8b, 0x9b, 0xf6, 0x88, 0xcf, 0x5e, 0x8e, 0xf9, 0x98, 0xbf,
+	0x54, 0x2e, 0x37, 0x8b, 0x5b, 0x65, 0x29, 0x43, 0xad, 0x74, 0x68, 0xeb, 0x0f, 0x13, 0x60, 0x38,
+	0xa1, 0x49, 0x38, 0x14, 0x54, 0x30, 0x8c, 0xc1, 0xf2, 0xe9, 0x8c, 0x39, 0x46, 0xd3, 0x38, 0xb6,
+	0x89, 0x5a, 0x63, 0x07, 0xaa, 0xca, 0xa3, 0xe7, 0x3a, 0x66, 0xd3, 0x38, 0xb6, 0xc8, 0xca, 0xc4,
+	0x5f, 0xc3, 0x6e, 0x9f, 0x2e, 0xe2, 0xd1, 0x44, 0x05, 0x3b, 0xa5, 0xa6, 0x71, 0xdc, 0x38, 0x39,
+	0x6c, 0x17, 0xea, 0x5b, 0xa7, 0x6e, 0xab, 0x5f, 0x52, 0x0c, 0xc0, 0xd7, 0xf0, 0x41, 0x2f, 0x9e,
+	0x8a, 0x29, 0x8d, 0x08, 0x9b, 0x47, 0xd3, 0x11, 0x4d, 0x1d, 0xab, 0x59, 0x3a, 0xde, 0x3d, 0xf9,
+	0xf4, 0x91, 0x1c, 0x5b, 0xde, 0x5e, 0x2c, 0x92, 0x25, 0xd9, 0xce, 0x71, 0x70, 0x06, 0x4f, 0xdf,
+	0xe6, 0x88, 0x11, 0x94, 0x7e, 0x60, 0x4b, 0xd5, 0x9b, 0x45, 0xe4, 0x12, 0x3f, 0x85, 0xf2, 0x1d,
+	0x8d, 0x16, 0x4c, 0x35, 0x66, 0x13, 0x6d, 0x7c, 0x69, 0x7e, 0x61, 0xb4, 0x3a, 0x50, 0xd6, 0x35,
+	0x36, 0x00, 0x88, 0x77, 0xde, 0x1b, 0x06, 0x1e, 0xf1, 0x5c, 0xb4, 0x83, 0xf7, 0xc0, 0xee, 0x77,
+	0xae, 0xfd, 0xee, 0x45, 0xcf, 0x3f, 0x47, 0x06, 0xae, 0x43, 0x4d, 0x9b, 0x9e, 0x8b, 0x4c, 0xbc,
+	0x0b, 0xd5, 0xce, 0xd9, 0x80, 0x04, 0x9e, 0x8b, 0x4a, 0xad, 0x7f, 0x0c, 0xa8, 0xf7, 0xf9, 0x58,
+	0xc3, 0x8a, 0x6f, 0x79, 0x11, 0xa4, 0xb1, 0x09, 0xf2, 0x0c, 0x6a, 0x39, 0x01, 0x53, 0x11, 0x78,
+	0x5e, 0x24, 0x50, 0xcc, 0xd2, 0xde, 0x6c, 0x3e, 0x8f, 0x93, 0xbd, 0x78, 0x73, 0x3e, 0x9a, 0xa8,
+	0x6b, 0xb0, 0x88, 0x36, 0xf0, 0x01, 0xd4, 0xfa, 0x8c, 0x86, 0x2c, 0xe9, 0xb9, 0x8e, 0xa5, 0x36,
+	0x72, 0x5b, 0x5e, 0x76, 0xc0, 0x92, 0x99, 0x53, 0x56, 0xba, 0x5a, 0x1f, 0x7c, 0x05, 0x7b, 0xef,
+	0x0f, 0xed, 0x37, 0x03, 0x9e, 0xc8, 0x5a, 0x05, 0x4f, 0xd8, 0x05, 0xa3, 0x89, 0xb8, 0x61, 0x54,
+	0xc8, 0x63, 0xae, 0xaf, 0xb3, 0x9e, 0x6d, 0xa2, 0xd6, 0xb8, 0x09, 0xbb, 0x84, 0xde, 0x8a, 0x4e,
+	0x18, 0x26, 0x2c, 0x4d, 0xb3, 0x4c, 0x45, 0x09, 0x3f, 0x87, 0xc6, 0x50, 0xb7, 0xbf, 0x72, 0x2a,
+	0x29, 0xa7, 0x2d, 0x15, 0x7f, 0x02, 0x7b, 0xe7, 0x3c, 0x4d, 0xa7, 0xf3, 0x95, 0x9b, 0xa5, 0xdc,
+	0x36, 0x45, 0xfc, 0x39, 0x54, 0x14, 0xc1, 0xd4, 0x29, 0x2b, 0xbc, 0xce, 0x63, 0x78, 0xcf, 0xac,
+	0x37, 0x7f, 0x7e, 0xbc, 0x43, 0x32, 0xef, 0x96, 0x07, 0xbb, 0xae, 0xff, 0x2e, 0x37, 0x78, 0x08,
+	0x76, 0xc6, 0x2d, 0xff, 0x4c, 0xd6, 0x42, 0xeb, 0x3b, 0x40, 0xae, 0xff, 0x0e, 0x58, 0x4e, 0xf3,
+	0x32, 0xf5, 0x14, 0x7c, 0x58, 0x2c, 0xb3, 0x50, 0xc8, 0x56, 0x95, 0xa7, 0x60, 0xf7, 0xf9, 0x98,
+	0xb0, 0x11, 0x4f, 0x42, 0x79, 0x3d, 0xbd, 0x38, 0x64, 0xf7, 0x59, 0x85, 0xda, 0x90, 0xa7, 0xb9,
+	0x54, 0x50, 0x55, 0x5a, 0x9d, 0xa8, 0x75, 0xeb, 0x17, 0x13, 0xaa, 0x84, 0xfd, 0xb8, 0x60, 0xa9,
+	0xc0, 0x6d, 0xa8, 0x5c, 0x32, 0x31, 0xe1, 0xa1, 0x0a, 0x6b, 0x9c, 0x3c, 0x2b, 0x9e, 0xac, 0x77,
+	0x82, 0xe5, 0x9c, 0x91, 0xcc, 0x2b, 0x7f, 0x28, 0xcc, 0xb7, 0x3f, 0x14, 0xa5, 0x4d, 0x3a, 0x79,
+	0x4d, 0x56, 0xb1, 0x26, 0x07, 0xaa, 0x97, 0xf4, 0x7e, 0x38, 0xfd, 0x89, 0x65, 0x23, 0xb8, 0x32,
+	0xe5, 0x4e, 0x30, 0x9d, 0x31, 0xbe, 0x10, 0x4e, 0xa5, 0x69, 0x1c, 0x97, 0xc8, 0xca, 0x94, 0x9c,
+	0x57, 0x1c, 0x5c, 0xa7, 0xaa, 0x39, 0xe7, 0x82, 0xea, 0xd2, 0xef, 0xb9, 0x4e, 0x4d, 0x4f, 0xb4,
+	0x5c, 0xcb, 0x51, 0xbb, 0xa2, 0xcb, 0x88, 0xd3, 0x50, 0x9d, 0x64, 0xab, 0xad, 0xa2, 0xd4, 0xfa,
+	0xd9, 0x94, 0x9f, 0x5f, 0x3a, 0xe7, 0x71, 0xca, 0xfe, 0x37, 0x88, 0x57, 0x60, 0x7b, 0x49, 0xc2,
+	0x93, 0x2e, 0x0f, 0x35, 0x8d, 0xc6, 0xc9, 0x7e, 0x31, 0x24, 0xdf, 0x24, 0x6b, 0x3f, 0xdc, 0x82,
+	0xba, 0x32, 0x2e, 0x59, 0x9a, 0xd2, 0x31, 0xcb, 0x46, 0x7b, 0x43, 0x2b, 0xd2, 0xb4, 0x1e, 0xa1,
+	0x59, 0x2e, 0xd2, 0x3c, 0x04, 0xbb, 0x4f, 0x53, 0xa1, 0x77, 0x2a, 0x9a, 0x4c, 0x2e, 0x6c, 0x53,
+	0xa8, 0xfe, 0x97, 0xc2, 0x37, 0xea, 0xdb, 0xd5, 0x43, 0x94, 0xd3, 0x38, 0x95, 0x13, 0x22, 0x95,
+	0xd4, 0x31, 0xd4, 0x44, 0xee, 0x6f, 0x7d, 0x38, 0x7a, 0x37, 0x9b, 0xc7, 0x95, 0xef, 0x8b, 0x7b,
+	0x80, 0x35, 0x2a, 0x0c, 0x50, 0xe9, 0x12, 0xaf, 0x13, 0x78, 0x68, 0x47, 0xbe, 0x90, 0xae, 0x37,
+	0x0c, 0xc8, 0xe0, 0x5b, 0x64, 0xc8, 0x8d, 0xce, 0xd5, 0x95, 0xe7, 0xcb, 0xa7, 0xb3, 0x06, 0x16,
+	0xf1, 0x3a, 0x2e, 0x2a, 0xc9, 0x27, 0x35, 0x20, 0xd7, 0x7e, 0x57, 0x06, 0x58, 0x18, 0x41, 0xfd,
+	0xdc, 0x0b, 0x5e, 0xe7, 0x4a, 0x59, 0xa6, 0xe8, 0x0e, 0x7c, 0xdf, 0xeb, 0x06, 0xa8, 0x22, 0x9f,
+	0xe7, 0xcc, 0x78, 0x4d, 0x06, 0xa8, 0xfa, 0xe2, 0x57, 0xb3, 0x70, 0x1f, 0xd2, 0xd5, 0xe7, 0xca,
+	0xd4, 0x47, 0x67, 0x53, 0xa4, 0xdf, 0xed, 0x2e, 0x8d, 0x47, 0x2c, 0x62, 0x21, 0x32, 0xe5, 0x21,
+	0xbd, 0xf8, 0x8e, 0x46, 0xd3, 0x50, 0x31, 0x46, 0x25, 0x8c, 0xa1, 0x91, 0x29, 0xab, 0x18, 0xab,
+	0xa0, 0x65, 0xdc, 0x50, 0x19, 0x3f, 0x03, 0xbc, 0xa9, 0x49, 0x96, 0xa8, 0x22, 0xf3, 0x13, 0xf6,
+	0x3d, 0x1b, 0x09, 0x16, 0xa2, 0x2a, 0x7e, 0x02, 0x7b, 0x2a, 0xb1, 0xcf, 0x05, 0x61, 0x34, 0x5c,
+	0xa2, 0x9a, 0x3c, 0x72, 0xb8, 0x4c, 0x05, 0x9b, 0x75, 0x23, 0x9e, 0xb2, 0x10, 0xd9, 0xf8, 0x23,
+	0xd8, 0x57, 0x77, 0xd5, 0x89, 0x12, 0xe9, 0x13, 0x24, 0x8b, 0x78, 0x44, 0x65, 0x7c, 0x28, 0xbb,
+	0x1c, 0x2c, 0xc4, 0xe0, 0x96, 0xd0, 0x78, 0xcc, 0x90, 0xfc, 0x9b, 0x6e, 0xf8, 0x5c, 0xf4, 0x19,
+	0x4d, 0xd9, 0x05, 0x8f, 0x42, 0x96, 0xa0, 0x5b, 0xbc, 0x0f, 0x68, 0x20, 0x26, 0x2c, 0xd1, 0x59,
+	0x75, 0xd3, 0x7f, 0x57, 0xcf, 0xea, 0x6f, 0x1e, 0x8e, 0x8c, 0xdf, 0x1f, 0x8e, 0x8c, 0xbf, 0x1e,
+	0x8e, 0x8c, 0x9b, 0x8a, 0xfa, 0xd7, 0x7f, 0xf5, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9c, 0x85,
+	0xf2, 0x9a, 0x44, 0x08, 0x00, 0x00,
+}
+
+func (m *ShardState) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ShardState) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ShardState) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.InitialReplicas) > 0 {
+		for k := range m.InitialReplicas {
+			v := m.InitialReplicas[k]
+			baseI := i
+			i -= len(v)
+			copy(dAtA[i:], v)
+			i = encodeVarintLogservice(dAtA, i, uint64(len(v)))
+			i--
+			dAtA[i] = 0x12
+			i = encodeVarintLogservice(dAtA, i, uint64(k))
+			i--
+			dAtA[i] = 0x8
+			i = encodeVarintLogservice(dAtA, i, uint64(baseI-i))
+			i--
+			dAtA[i] = 0x22
+		}
+	}
+	if m.LaunchState != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.LaunchState))
+		i--
+		dAtA[i] = 0x18
+	}
+	if m.ShardID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.ShardID))
+		i--
+		dAtA[i] = 0x10
+	}
+	if len(m.Name) > 0 {
+		i -= len(m.Name)
+		copy(dAtA[i:], m.Name)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.Name)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *LogShardInfo) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *LogShardInfo) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *LogShardInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if m.Term != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Term))
+		i--
+		dAtA[i] = 0x28
+	}
+	if m.LeaderID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.LeaderID))
+		i--
+		dAtA[i] = 0x20
+	}
+	if m.Epoch != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Epoch))
+		i--
+		dAtA[i] = 0x18
+	}
+	if len(m.Replicas) > 0 {
+		for k := range m.Replicas {
+			v := m.Replicas[k]
+			baseI := i
+			i -= len(v)
+			copy(dAtA[i:], v)
+			i = encodeVarintLogservice(dAtA, i, uint64(len(v)))
+			i--
+			dAtA[i] = 0x12
+			i = encodeVarintLogservice(dAtA, i, uint64(k))
+			i--
+			dAtA[i] = 0x8
+			i = encodeVarintLogservice(dAtA, i, uint64(baseI-i))
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	if m.ShardID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.ShardID))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *LogStoreHeartbeat) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *LogStoreHeartbeat) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *LogStoreHeartbeat) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.Shards) > 0 {
+		for iNdEx := len(m.Shards) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Shards[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintLogservice(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x2a
+		}
+	}
+	if len(m.GossipAddress) > 0 {
+		i -= len(m.GossipAddress)
+		copy(dAtA[i:], m.GossipAddress)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.GossipAddress)))
+		i--
+		dAtA[i] = 0x22
+	}
+	if len(m.ServiceAddress) > 0 {
+		i -= len(m.ServiceAddress)
+		copy(dAtA[i:], m.ServiceAddress)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.ServiceAddress)))
+		i--
+		dAtA[i] = 0x1a
+	}
+	if len(m.RaftAddress) > 0 {
+		i -= len(m.RaftAddress)
+		copy(dAtA[i:], m.RaftAddress)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.RaftAddress)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.UUID) > 0 {
+		i -= len(m.UUID)
+		copy(dAtA[i:], m.UUID)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.UUID)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *DNShardInfo) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *DNShardInfo) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *DNShardInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if m.ReplicaID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.ReplicaID))
+		i--
+		dAtA[i] = 0x10
+	}
+	if m.ShardID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.ShardID))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *DNStoreHeartbeat) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *DNStoreHeartbeat) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *DNStoreHeartbeat) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.Shards) > 0 {
+		for iNdEx := len(m.Shards) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Shards[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintLogservice(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	if len(m.UUID) > 0 {
+		i -= len(m.UUID)
+		copy(dAtA[i:], m.UUID)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.UUID)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *LogRecord) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *LogRecord) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *LogRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.Data) > 0 {
+		i -= len(m.Data)
+		copy(dAtA[i:], m.Data)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.Data)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if m.Index != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Index))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *Request) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *Request) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Request) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if m.PayloadSize != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.PayloadSize))
+		i--
+		dAtA[i] = 0x48
+	}
+	if m.DNID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.DNID))
+		i--
+		dAtA[i] = 0x40
+	}
+	if m.DNShardID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.DNShardID))
+		i--
+		dAtA[i] = 0x38
+	}
+	if m.Timeout != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Timeout))
+		i--
+		dAtA[i] = 0x30
+	}
+	if m.MaxSize != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.MaxSize))
+		i--
+		dAtA[i] = 0x28
+	}
+	if m.Index != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Index))
+		i--
+		dAtA[i] = 0x20
+	}
+	if m.ShardID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.ShardID))
+		i--
+		dAtA[i] = 0x18
+	}
+	if len(m.Name) > 0 {
+		i -= len(m.Name)
+		copy(dAtA[i:], m.Name)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.Name)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if m.Method != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Method))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *Response) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *Response) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Response) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if m.PayloadSize != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.PayloadSize))
+		i--
+		dAtA[i] = 0x38
+	}
+	if m.LastIndex != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.LastIndex))
+		i--
+		dAtA[i] = 0x30
+	}
+	if m.Index != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Index))
+		i--
+		dAtA[i] = 0x28
+	}
+	if m.ShardID != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.ShardID))
+		i--
+		dAtA[i] = 0x20
+	}
+	if len(m.ErrorMessage) > 0 {
+		i -= len(m.ErrorMessage)
+		copy(dAtA[i:], m.ErrorMessage)
+		i = encodeVarintLogservice(dAtA, i, uint64(len(m.ErrorMessage)))
+		i--
+		dAtA[i] = 0x1a
+	}
+	if m.ErrorCode != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.ErrorCode))
+		i--
+		dAtA[i] = 0x10
+	}
+	if m.Method != 0 {
+		i = encodeVarintLogservice(dAtA, i, uint64(m.Method))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *LogRecordResponse) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *LogRecordResponse) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *LogRecordResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.Records) > 0 {
+		for iNdEx := len(m.Records) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Records[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintLogservice(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0xa
+		}
+	}
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintLogservice(dAtA []byte, offset int, v uint64) int {
+	offset -= sovLogservice(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *ShardState) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Name)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	if m.ShardID != 0 {
+		n += 1 + sovLogservice(uint64(m.ShardID))
+	}
+	if m.LaunchState != 0 {
+		n += 1 + sovLogservice(uint64(m.LaunchState))
+	}
+	if len(m.InitialReplicas) > 0 {
+		for k, v := range m.InitialReplicas {
+			_ = k
+			_ = v
+			mapEntrySize := 1 + sovLogservice(uint64(k)) + 1 + len(v) + sovLogservice(uint64(len(v)))
+			n += mapEntrySize + 1 + sovLogservice(uint64(mapEntrySize))
+		}
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *LogShardInfo) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.ShardID != 0 {
+		n += 1 + sovLogservice(uint64(m.ShardID))
+	}
+	if len(m.Replicas) > 0 {
+		for k, v := range m.Replicas {
+			_ = k
+			_ = v
+			mapEntrySize := 1 + sovLogservice(uint64(k)) + 1 + len(v) + sovLogservice(uint64(len(v)))
+			n += mapEntrySize + 1 + sovLogservice(uint64(mapEntrySize))
+		}
+	}
+	if m.Epoch != 0 {
+		n += 1 + sovLogservice(uint64(m.Epoch))
+	}
+	if m.LeaderID != 0 {
+		n += 1 + sovLogservice(uint64(m.LeaderID))
+	}
+	if m.Term != 0 {
+		n += 1 + sovLogservice(uint64(m.Term))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *LogStoreHeartbeat) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.UUID)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	l = len(m.RaftAddress)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	l = len(m.ServiceAddress)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	l = len(m.GossipAddress)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	if len(m.Shards) > 0 {
+		for _, e := range m.Shards {
+			l = e.Size()
+			n += 1 + l + sovLogservice(uint64(l))
+		}
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *DNShardInfo) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.ShardID != 0 {
+		n += 1 + sovLogservice(uint64(m.ShardID))
+	}
+	if m.ReplicaID != 0 {
+		n += 1 + sovLogservice(uint64(m.ReplicaID))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *DNStoreHeartbeat) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.UUID)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	if len(m.Shards) > 0 {
+		for _, e := range m.Shards {
+			l = e.Size()
+			n += 1 + l + sovLogservice(uint64(l))
+		}
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *LogRecord) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Index != 0 {
+		n += 1 + sovLogservice(uint64(m.Index))
+	}
+	l = len(m.Data)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *Request) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Method != 0 {
+		n += 1 + sovLogservice(uint64(m.Method))
+	}
+	l = len(m.Name)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	if m.ShardID != 0 {
+		n += 1 + sovLogservice(uint64(m.ShardID))
+	}
+	if m.Index != 0 {
+		n += 1 + sovLogservice(uint64(m.Index))
+	}
+	if m.MaxSize != 0 {
+		n += 1 + sovLogservice(uint64(m.MaxSize))
+	}
+	if m.Timeout != 0 {
+		n += 1 + sovLogservice(uint64(m.Timeout))
+	}
+	if m.DNShardID != 0 {
+		n += 1 + sovLogservice(uint64(m.DNShardID))
+	}
+	if m.DNID != 0 {
+		n += 1 + sovLogservice(uint64(m.DNID))
+	}
+	if m.PayloadSize != 0 {
+		n += 1 + sovLogservice(uint64(m.PayloadSize))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *Response) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Method != 0 {
+		n += 1 + sovLogservice(uint64(m.Method))
+	}
+	if m.ErrorCode != 0 {
+		n += 1 + sovLogservice(uint64(m.ErrorCode))
+	}
+	l = len(m.ErrorMessage)
+	if l > 0 {
+		n += 1 + l + sovLogservice(uint64(l))
+	}
+	if m.ShardID != 0 {
+		n += 1 + sovLogservice(uint64(m.ShardID))
+	}
+	if m.Index != 0 {
+		n += 1 + sovLogservice(uint64(m.Index))
+	}
+	if m.LastIndex != 0 {
+		n += 1 + sovLogservice(uint64(m.LastIndex))
+	}
+	if m.PayloadSize != 0 {
+		n += 1 + sovLogservice(uint64(m.PayloadSize))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *LogRecordResponse) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if len(m.Records) > 0 {
+		for _, e := range m.Records {
+			l = e.Size()
+			n += 1 + l + sovLogservice(uint64(l))
+		}
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func sovLogservice(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozLogservice(x uint64) (n int) {
+	return sovLogservice(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *ShardState) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ShardState: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ShardState: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Name = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ShardID", wireType)
+			}
+			m.ShardID = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ShardID |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 3:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field LaunchState", wireType)
+			}
+			m.LaunchState = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.LaunchState |= ShardState_State(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field InitialReplicas", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.InitialReplicas == nil {
+				m.InitialReplicas = make(map[uint64]string)
+			}
+			var mapkey uint64
+			var mapvalue string
+			for iNdEx < postIndex {
+				entryPreIndex := iNdEx
+				var wire uint64
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return ErrIntOverflowLogservice
+					}
+					if iNdEx >= l {
+						return io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					wire |= uint64(b&0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				fieldNum := int32(wire >> 3)
+				if fieldNum == 1 {
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowLogservice
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						mapkey |= uint64(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+				} else if fieldNum == 2 {
+					var stringLenmapvalue uint64
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowLogservice
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						stringLenmapvalue |= uint64(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+					intStringLenmapvalue := int(stringLenmapvalue)
+					if intStringLenmapvalue < 0 {
+						return ErrInvalidLengthLogservice
+					}
+					postStringIndexmapvalue := iNdEx + intStringLenmapvalue
+					if postStringIndexmapvalue < 0 {
+						return ErrInvalidLengthLogservice
+					}
+					if postStringIndexmapvalue > l {
+						return io.ErrUnexpectedEOF
+					}
+					mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue])
+					iNdEx = postStringIndexmapvalue
+				} else {
+					iNdEx = entryPreIndex
+					skippy, err := skipLogservice(dAtA[iNdEx:])
+					if err != nil {
+						return err
+					}
+					if (skippy < 0) || (iNdEx+skippy) < 0 {
+						return ErrInvalidLengthLogservice
+					}
+					if (iNdEx + skippy) > postIndex {
+						return io.ErrUnexpectedEOF
+					}
+					iNdEx += skippy
+				}
+			}
+			m.InitialReplicas[mapkey] = mapvalue
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *LogShardInfo) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: LogShardInfo: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: LogShardInfo: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ShardID", wireType)
+			}
+			m.ShardID = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ShardID |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Replicas", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Replicas == nil {
+				m.Replicas = make(map[uint64]string)
+			}
+			var mapkey uint64
+			var mapvalue string
+			for iNdEx < postIndex {
+				entryPreIndex := iNdEx
+				var wire uint64
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return ErrIntOverflowLogservice
+					}
+					if iNdEx >= l {
+						return io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					wire |= uint64(b&0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				fieldNum := int32(wire >> 3)
+				if fieldNum == 1 {
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowLogservice
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						mapkey |= uint64(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+				} else if fieldNum == 2 {
+					var stringLenmapvalue uint64
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowLogservice
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						stringLenmapvalue |= uint64(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+					intStringLenmapvalue := int(stringLenmapvalue)
+					if intStringLenmapvalue < 0 {
+						return ErrInvalidLengthLogservice
+					}
+					postStringIndexmapvalue := iNdEx + intStringLenmapvalue
+					if postStringIndexmapvalue < 0 {
+						return ErrInvalidLengthLogservice
+					}
+					if postStringIndexmapvalue > l {
+						return io.ErrUnexpectedEOF
+					}
+					mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue])
+					iNdEx = postStringIndexmapvalue
+				} else {
+					iNdEx = entryPreIndex
+					skippy, err := skipLogservice(dAtA[iNdEx:])
+					if err != nil {
+						return err
+					}
+					if (skippy < 0) || (iNdEx+skippy) < 0 {
+						return ErrInvalidLengthLogservice
+					}
+					if (iNdEx + skippy) > postIndex {
+						return io.ErrUnexpectedEOF
+					}
+					iNdEx += skippy
+				}
+			}
+			m.Replicas[mapkey] = mapvalue
+			iNdEx = postIndex
+		case 3:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Epoch", wireType)
+			}
+			m.Epoch = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Epoch |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 4:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field LeaderID", wireType)
+			}
+			m.LeaderID = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.LeaderID |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 5:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Term", wireType)
+			}
+			m.Term = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Term |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *LogStoreHeartbeat) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: LogStoreHeartbeat: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: LogStoreHeartbeat: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.UUID = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field RaftAddress", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.RaftAddress = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ServiceAddress", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.ServiceAddress = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field GossipAddress", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.GossipAddress = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 5:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Shards", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Shards = append(m.Shards, LogShardInfo{})
+			if err := m.Shards[len(m.Shards)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *DNShardInfo) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: DNShardInfo: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: DNShardInfo: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ShardID", wireType)
+			}
+			m.ShardID = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ShardID |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ReplicaID", wireType)
+			}
+			m.ReplicaID = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ReplicaID |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *DNStoreHeartbeat) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: DNStoreHeartbeat: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: DNStoreHeartbeat: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.UUID = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Shards", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Shards = append(m.Shards, DNShardInfo{})
+			if err := m.Shards[len(m.Shards)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *LogRecord) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: LogRecord: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: LogRecord: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Index", wireType)
+			}
+			m.Index = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Index |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...)
+			if m.Data == nil {
+				m.Data = []byte{}
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *Request) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: Request: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Request: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Method", wireType)
+			}
+			m.Method = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Method |= MethodType(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Name = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ShardID", wireType)
+			}
+			m.ShardID = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ShardID |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 4:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Index", wireType)
+			}
+			m.Index = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Index |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 5:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field MaxSize", wireType)
+			}
+			m.MaxSize = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.MaxSize |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 6:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Timeout", wireType)
+			}
+			m.Timeout = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Timeout |= int64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 7:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field DNShardID", wireType)
+			}
+			m.DNShardID = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.DNShardID |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 8:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field DNID", wireType)
+			}
+			m.DNID = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.DNID |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 9:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field PayloadSize", wireType)
+			}
+			m.PayloadSize = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.PayloadSize |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *Response) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: Response: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Response: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Method", wireType)
+			}
+			m.Method = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Method |= MethodType(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ErrorCode", wireType)
+			}
+			m.ErrorCode = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ErrorCode |= ErrorCode(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ErrorMessage", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.ErrorMessage = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 4:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ShardID", wireType)
+			}
+			m.ShardID = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ShardID |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 5:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Index", wireType)
+			}
+			m.Index = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Index |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 6:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field LastIndex", wireType)
+			}
+			m.LastIndex = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.LastIndex |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 7:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field PayloadSize", wireType)
+			}
+			m.PayloadSize = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.PayloadSize |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *LogRecordResponse) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: LogRecordResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: LogRecordResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Records", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Records = append(m.Records, LogRecord{})
+			if err := m.Records[len(m.Records)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipLogservice(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthLogservice
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipLogservice(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowLogservice
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowLogservice
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthLogservice
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupLogservice
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthLogservice
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthLogservice        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowLogservice          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupLogservice = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/pkg/pb/plan/plan.pb.go b/pkg/pb/plan/plan.pb.go
index 4fa59cab7..ab1723d87 100644
--- a/pkg/pb/plan/plan.pb.go
+++ b/pkg/pb/plan/plan.pb.go
@@ -6785,6 +6785,8 @@ func (m *Node) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 			i--
 			dAtA[i] = 0x5a
 		}
+		i--
+		dAtA[i] = 0x6a
 	}
 	if len(m.GroupingSet) > 0 {
 		for iNdEx := len(m.GroupingSet) - 1; iNdEx >= 0; iNdEx-- {
diff --git a/proto/logservice.proto b/proto/logservice.proto
new file mode 100644
index 000000000..eb228825c
--- /dev/null
+++ b/proto/logservice.proto
@@ -0,0 +1,151 @@
+// Copyright 2022 MatrixOrigin.
+//
+// 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,
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+package logservice;
+
+import "github.com/gogo/protobuf/gogoproto/gogo.proto";
+
+// ShardState represents the state of a registered Shard. 
+message ShardState {
+  enum State {
+    REGISTERED = 0;
+    LAUNCHING  = 1;
+    LAUNCHED   = 2;
+    ABORTED    = 3;
+  }
+  string Name = 1;
+  uint64 ShardID = 2;
+  State LaunchState = 3;
+  // InitialReplicas is a map of ReplicaID to LogStore UUIDs, it describes the
+  // initial replicas assigned for the given shard. 
+  map<uint64, string> InitialReplicas = 4;
+};
+
+// LogShardInfo contains information of a launched shard.  
+message LogShardInfo {
+  uint64 ShardID               = 1;
+  // Replicas is a map of ReplicaID to LogStore UUID, it describe the member
+  // replicas of the shard at the given Epoch. 
+  map<uint64, string> Replicas = 2;
+  // Epoch is the epoch value of the Shard, member replicas of the Shard can
+  // change across epochs. 
+  uint64 Epoch                 = 3;
+  // LeaderID is the ReplicaID of the leader replica at the given term. When
+  // LeaderID is 0, it means there is no leader or the leader is unknown.
+  uint64 LeaderID              = 4;
+  // Term is the Raft term value. 
+  uint64 Term                  = 5;
+
+  // TODO: per shard stats like CPU/memory/network usage can be added here
+};
+
+// LogStoreHeartbeat is the periodic message sent to the HAKeeper by Log Stores.
+message LogStoreHeartbeat {
+  // UUID is the uuid of the Log Store.
+  string UUID = 1;
+  
+  string RaftAddress    = 2;
+  string ServiceAddress = 3;
+  string GossipAddress  = 4;
+
+  // Shards is a list of LogShardInfo instances collected on the specified
+  // LogStore. Details in Shards are based on the local knowledge of each
+  // replica running on the current LogStore, it may not be accurate or
+  // update to date due to various reasons. 
+  repeated LogShardInfo Shards = 5 [(gogoproto.nullable) = false];
+};
+
+// DNShardInfo contains information of a launched DN shard. 
+message DNShardInfo {
+  // ShardID uniquely identifies a DN shard. Each DN shard manages a Primary
+  // Key range or hashed Primary Key value range. 
+  uint64 ShardID                 = 1;
+  // ReplicaID uniquely identifies a DN shard instance. After repairing a
+  // failed DN shard, a new DN shard instance is created with a new ReplicaID
+  // value.
+  uint64 ReplicaID               = 2;
+
+  // TODO: per shard stats like CPU/memory/network usage can be added here
+};
+
+// DNStoreHeartbeat is the periodic message sent to the HAKeeper by DN stores.
+message DNStoreHeartbeat {
+  // UUID is the uuid of the DN Store.
+  string UUID = 1;
+  // Shards is a list of DNShardInfo instances collected on the specified
+  // DN store. 
+  repeated DNShardInfo Shards = 2 [(gogoproto.nullable) = false];
+};
+
+enum MethodType {
+  CREATE       = 0;
+  DESTROY      = 1;
+  APPEND       = 2;
+  READ         = 3;
+  TRUNCATE     = 4;
+  GET_TRUNCATE = 5;
+  CONNECT      = 6;
+  CONNECT_RO   = 7;
+};
+
+message LogRecord {
+  uint64 Index = 1;
+  bytes Data   = 2;
+};
+
+message Request {
+  MethodType Method   = 1;
+  string Name         = 2;
+  uint64 ShardID      = 3;
+  uint64 Index        = 4;
+  uint64 MaxSize      = 5;
+  int64 Timeout       = 6;
+
+  uint64 DNShardID    = 7;
+  uint64 DNID         = 8;
+  uint64 PayloadSize  = 9;
+};
+
+enum ErrorCode {
+  NoError               = 0;
+  Timeout               = 1;
+  Canceled              = 2;
+  InvalidShard          = 3;
+  InvalidTimeout        = 4;
+  InvalidPayload        = 5;
+  InvalidPayloadSize    = 6;
+  Rejected              = 7;
+  ShardNotReady         = 8;
+  SystemClosed          = 9;
+
+  IndexAlreadyTruncated = 100;
+  OutOfRange            = 101;
+  NotLeaseHolder        = 102;
+
+  OtherSystemError      = 1000;
+};
+
+message Response {
+  MethodType Method   = 1;
+  ErrorCode ErrorCode = 2;
+  string ErrorMessage = 3;
+  uint64 ShardID      = 4;
+  uint64 Index        = 5;
+  uint64 LastIndex    = 6;
+  uint64 PayloadSize  = 7;
+};
+
+message LogRecordResponse {
+  repeated LogRecord Records = 1 [(gogoproto.nullable) = false];
+};


Test patch:
diff --git a/pkg/hakeeper/rsm_test.go b/pkg/hakeeper/rsm_test.go
new file mode 100644
index 000000000..1b2b64e32
--- /dev/null
+++ b/pkg/hakeeper/rsm_test.go
@@ -0,0 +1,211 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package hakeeper
+
+import (
+	"bytes"
+	"testing"
+
+	sm "github.com/lni/dragonboat/v4/statemachine"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+func TestAssignID(t *testing.T) {
+	tsm := NewStateMachine(0, 1).(*stateMachine)
+	assert.Equal(t, uint64(0), tsm.NextID)
+	assert.Equal(t, uint64(1), tsm.assignID())
+	assert.Equal(t, uint64(1), tsm.NextID)
+}
+
+func TestCreateLogShardCmd(t *testing.T) {
+	cmd := getCreateLogShardCmd("test")
+	name, ok := isCreateLogShardCmd(cmd)
+	assert.True(t, ok)
+	assert.Equal(t, "test", name)
+}
+
+func TestHAKeeperStateMachineCanBeCreated(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil {
+			t.Fatalf("failed to panic")
+		}
+	}()
+	tsm := NewStateMachine(0, 1).(*stateMachine)
+	assert.Equal(t, uint64(1), tsm.replicaID)
+	NewStateMachine(1, 1)
+}
+
+func TestHAKeeperStateMachineSnapshot(t *testing.T) {
+	tsm1 := NewStateMachine(0, 1).(*stateMachine)
+	tsm2 := NewStateMachine(0, 2).(*stateMachine)
+	tsm1.NextID = 12345
+	tsm1.LogShards["test1"] = 23456
+	tsm1.LogShards["test2"] = 34567
+
+	buf := bytes.NewBuffer(nil)
+	assert.Nil(t, tsm1.SaveSnapshot(buf, nil, nil))
+	assert.Nil(t, tsm2.RecoverFromSnapshot(buf, nil, nil))
+	assert.Equal(t, tsm1.NextID, tsm2.NextID)
+	assert.Equal(t, tsm1.LogShards, tsm2.LogShards)
+	assert.True(t, tsm1.replicaID != tsm2.replicaID)
+}
+
+func TestHAKeeperLogShardCanBeCreated(t *testing.T) {
+	cmd := getCreateLogShardCmd("test1")
+	tsm1 := NewStateMachine(0, 1).(*stateMachine)
+	tsm1.NextID = 100
+
+	result, err := tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.Nil(t, err)
+	assert.Equal(t, sm.Result{Value: 101}, result)
+	assert.Equal(t, uint64(101), tsm1.NextID)
+
+	tsm1.NextID = 200
+	result, err = tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.Nil(t, err)
+	data := make([]byte, 8)
+	binaryEnc.PutUint64(data, 101)
+	assert.Equal(t, sm.Result{Data: data}, result)
+}
+
+func TestHAKeeperQueryLogShardID(t *testing.T) {
+	cmd := getCreateLogShardCmd("test1")
+	tsm1 := NewStateMachine(0, 1).(*stateMachine)
+	tsm1.NextID = 100
+	result, err := tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.Nil(t, err)
+	assert.Equal(t, sm.Result{Value: 101}, result)
+
+	q1 := &logShardIDQuery{name: "test1"}
+	r, err := tsm1.Lookup(q1)
+	assert.NoError(t, err)
+	r1, ok := r.(*logShardIDQueryResult)
+	assert.True(t, ok)
+	assert.True(t, r1.found)
+	assert.Equal(t, uint64(101), r1.id)
+
+	q2 := &logShardIDQuery{name: "test2"}
+	r, err = tsm1.Lookup(q2)
+	assert.NoError(t, err)
+	r2, ok := r.(*logShardIDQueryResult)
+	assert.True(t, ok)
+	assert.False(t, r2.found)
+}
+
+func TestHAKeeperCanBeClosed(t *testing.T) {
+	tsm1 := NewStateMachine(0, 1).(*stateMachine)
+	assert.Nil(t, tsm1.Close())
+}
+
+func TestHAKeeperTick(t *testing.T) {
+	tsm1 := NewStateMachine(0, 1).(*stateMachine)
+	assert.Equal(t, uint64(0), tsm1.Tick)
+	cmd := GetTickCmd()
+	_, err := tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+	_, err = tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+	assert.Equal(t, uint64(2), tsm1.Tick)
+}
+
+func TestHandleLogHeartbeat(t *testing.T) {
+	tsm1 := NewStateMachine(0, 1).(*stateMachine)
+	cmd := GetTickCmd()
+	_, err := tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+	_, err = tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+	_, err = tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+
+	hb := logservice.LogStoreHeartbeat{
+		UUID:           "uuid1",
+		RaftAddress:    "localhost:9090",
+		ServiceAddress: "localhost:9091",
+		GossipAddress:  "localhost:9092",
+		Shards: []logservice.LogShardInfo{
+			{
+				ShardID: 100,
+				Replicas: map[uint64]string{
+					200: "localhost:8000",
+					300: "localhost:9000",
+				},
+				Epoch:    200,
+				LeaderID: 200,
+				Term:     10,
+			},
+			{
+				ShardID: 101,
+				Replicas: map[uint64]string{
+					201: "localhost:8000",
+					301: "localhost:9000",
+				},
+				Epoch:    202,
+				LeaderID: 201,
+				Term:     30,
+			},
+		},
+	}
+	data, err := hb.Marshal()
+	require.NoError(t, err)
+	cmd = GetLogStoreHeartbeatCmd(data)
+	_, err = tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+	s := tsm1.LogState
+	assert.Equal(t, 1, len(s.Stores))
+	lsinfo, ok := s.Stores[hb.UUID]
+	require.True(t, ok)
+	assert.Equal(t, uint64(3), lsinfo.Tick)
+	assert.Equal(t, hb.RaftAddress, lsinfo.RaftAddress)
+	assert.Equal(t, hb.ServiceAddress, lsinfo.ServiceAddress)
+	assert.Equal(t, hb.GossipAddress, lsinfo.GossipAddress)
+	assert.Equal(t, 2, len(lsinfo.Shards))
+	assert.Equal(t, hb.Shards, lsinfo.Shards)
+}
+
+func TestHandleDNHeartbeat(t *testing.T) {
+	tsm1 := NewStateMachine(0, 1).(*stateMachine)
+	cmd := GetTickCmd()
+	_, err := tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+	_, err = tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+	_, err = tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+
+	hb := logservice.DNStoreHeartbeat{
+		UUID: "uuid1",
+		Shards: []logservice.DNShardInfo{
+			{ShardID: 1, ReplicaID: 1},
+			{ShardID: 2, ReplicaID: 1},
+			{ShardID: 3, ReplicaID: 1},
+		},
+	}
+	data, err := hb.Marshal()
+	require.NoError(t, err)
+	cmd = GetDNStoreHeartbeatCmd(data)
+	_, err = tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+	s := tsm1.DNState
+	assert.Equal(t, 1, len(s.Stores))
+	dninfo, ok := s.Stores[hb.UUID]
+	assert.True(t, ok)
+	assert.Equal(t, uint64(3), dninfo.Tick)
+	require.Equal(t, 3, len(dninfo.Shards))
+	assert.Equal(t, hb.Shards, dninfo.Shards)
+}
diff --git a/pkg/hakeeper/state_test.go b/pkg/hakeeper/state_test.go
new file mode 100644
index 000000000..7a0a9b1a3
--- /dev/null
+++ b/pkg/hakeeper/state_test.go
@@ -0,0 +1,168 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package hakeeper
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+func TestDNStateUpdate(t *testing.T) {
+	s := NewDNState()
+	hb := logservice.DNStoreHeartbeat{
+		UUID: "uuid1",
+		Shards: []logservice.DNShardInfo{
+			{ShardID: 1, ReplicaID: 1},
+			{ShardID: 2, ReplicaID: 1},
+			{ShardID: 3, ReplicaID: 1},
+		},
+	}
+	s.Update(hb, 1)
+	assert.Equal(t, 1, len(s.Stores))
+	dninfo, ok := s.Stores[hb.UUID]
+	assert.True(t, ok)
+	assert.Equal(t, uint64(1), dninfo.Tick)
+	require.Equal(t, 3, len(dninfo.Shards))
+	assert.Equal(t, hb.Shards, dninfo.Shards)
+
+	hb = logservice.DNStoreHeartbeat{
+		UUID: "uuid2",
+		Shards: []logservice.DNShardInfo{
+			{ShardID: 100, ReplicaID: 1},
+		},
+	}
+	s.Update(hb, 2)
+
+	hb = logservice.DNStoreHeartbeat{
+		UUID: "uuid1",
+		Shards: []logservice.DNShardInfo{
+			{ShardID: 1, ReplicaID: 1},
+			{ShardID: 3, ReplicaID: 1},
+			{ShardID: 4, ReplicaID: 1},
+			{ShardID: 100, ReplicaID: 1},
+		},
+	}
+	s.Update(hb, 2)
+	assert.Equal(t, 2, len(s.Stores))
+	dninfo, ok = s.Stores[hb.UUID]
+	assert.True(t, ok)
+	assert.Equal(t, uint64(2), dninfo.Tick)
+	require.Equal(t, 4, len(dninfo.Shards))
+	assert.Equal(t, hb.Shards, dninfo.Shards)
+}
+
+func TestUpdateLogStateStore(t *testing.T) {
+	s := NewLogState()
+	hb := logservice.LogStoreHeartbeat{
+		UUID:           "uuid1",
+		RaftAddress:    "localhost:9090",
+		ServiceAddress: "localhost:9091",
+		GossipAddress:  "localhost:9092",
+		Shards: []logservice.LogShardInfo{
+			{
+				ShardID: 100,
+				Replicas: map[uint64]string{
+					200: "localhost:8000",
+					300: "localhost:9000",
+				},
+				Epoch:    200,
+				LeaderID: 200,
+				Term:     10,
+			},
+			{
+				ShardID: 101,
+				Replicas: map[uint64]string{
+					201: "localhost:8000",
+					301: "localhost:9000",
+				},
+				Epoch:    202,
+				LeaderID: 201,
+				Term:     30,
+			},
+		},
+	}
+	s.Update(hb, 3)
+
+	assert.Equal(t, 1, len(s.Stores))
+	lsinfo, ok := s.Stores[hb.UUID]
+	require.True(t, ok)
+	assert.Equal(t, uint64(3), lsinfo.Tick)
+	assert.Equal(t, hb.RaftAddress, lsinfo.RaftAddress)
+	assert.Equal(t, hb.ServiceAddress, lsinfo.ServiceAddress)
+	assert.Equal(t, hb.GossipAddress, lsinfo.GossipAddress)
+	assert.Equal(t, 2, len(lsinfo.Shards))
+	assert.Equal(t, hb.Shards, lsinfo.Shards)
+
+	require.Equal(t, 2, len(s.Shards))
+	shard1, ok := s.Shards[100]
+	assert.True(t, ok)
+	assert.Equal(t, hb.Shards[0], shard1)
+	shard2, ok := s.Shards[101]
+	assert.True(t, ok)
+	assert.Equal(t, hb.Shards[1], shard2)
+
+	hb2 := logservice.LogStoreHeartbeat{
+		UUID:           "uuid1",
+		RaftAddress:    "localhost:9090",
+		ServiceAddress: "localhost:9091",
+		GossipAddress:  "localhost:9092",
+		Shards: []logservice.LogShardInfo{
+			{
+				ShardID: 100,
+				Replicas: map[uint64]string{
+					200: "localhost:8000",
+					300: "localhost:9000",
+					400: "localhost:10000",
+				},
+				Epoch:    201,
+				LeaderID: 400,
+				Term:     20,
+			},
+			{
+				ShardID: 101,
+				Replicas: map[uint64]string{
+					201: "localhost:8000",
+				},
+				Epoch:    200,
+				LeaderID: NoLeader,
+				Term:     100,
+			},
+		},
+	}
+	s.Update(hb2, 4)
+
+	assert.Equal(t, 1, len(s.Stores))
+	lsinfo, ok = s.Stores[hb.UUID]
+	require.True(t, ok)
+	assert.Equal(t, uint64(4), lsinfo.Tick)
+	assert.Equal(t, hb2.RaftAddress, lsinfo.RaftAddress)
+	assert.Equal(t, hb2.ServiceAddress, lsinfo.ServiceAddress)
+	assert.Equal(t, hb2.GossipAddress, lsinfo.GossipAddress)
+	assert.Equal(t, 2, len(lsinfo.Shards))
+	assert.Equal(t, hb2.Shards, lsinfo.Shards)
+
+	require.Equal(t, 2, len(s.Shards))
+	shard1, ok = s.Shards[100]
+	assert.True(t, ok)
+	assert.Equal(t, hb2.Shards[0], shard1)
+	shard2, ok = s.Shards[101]
+	assert.True(t, ok)
+	// shard2 didn't change to hb2.Shard[1]
+	assert.Equal(t, hb.Shards[1], shard2)
+}
diff --git a/pkg/logservice/client_test.go b/pkg/logservice/client_test.go
new file mode 100644
index 000000000..77b582b71
--- /dev/null
+++ b/pkg/logservice/client_test.go
@@ -0,0 +1,159 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"context"
+	"math"
+	"math/rand"
+	"testing"
+	"time"
+
+	"github.com/lni/goutils/leaktest"
+	"github.com/lni/vfs"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+func runClientTest(t *testing.T,
+	readOnly bool, fn func(*testing.T, LogServiceClientConfig, Client)) {
+	defer leaktest.AfterTest(t)()
+	cfg := getServiceTestConfig()
+	defer vfs.ReportLeakedFD(cfg.FS, t)
+	service, err := NewService(cfg)
+	require.NoError(t, err)
+	defer func() {
+		assert.NoError(t, service.Close())
+	}()
+
+	init := make(map[uint64]string)
+	init[2] = service.ID()
+	assert.NoError(t, service.store.StartReplica(1, 2, init))
+
+	scfg := LogServiceClientConfig{
+		ReadOnly:         readOnly,
+		ShardID:          1,
+		ReplicaID:        2,
+		ServiceAddresses: []string{testServiceAddress},
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
+	c, err := CreateClient(ctx, "shard1", scfg)
+	require.NoError(t, err)
+	defer func() {
+		assert.NoError(t, c.Close())
+	}()
+
+	fn(t, scfg, c)
+}
+
+func TestClientCanBeCreated(t *testing.T) {
+	fn := func(t *testing.T, cfg LogServiceClientConfig, c Client) {
+	}
+	runClientTest(t, false, fn)
+	runClientTest(t, true, fn)
+}
+
+func TestClientAppend(t *testing.T) {
+	fn := func(t *testing.T, cfg LogServiceClientConfig, c Client) {
+		cmd := make([]byte, 16+headerSize+8)
+		cmd = getAppendCmd(cmd, cfg.ReplicaID)
+		rand.Read(cmd[headerSize+8:])
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+		lsn, err := c.Append(ctx, pb.LogRecord{Data: cmd})
+		require.NoError(t, err)
+		assert.Equal(t, uint64(4), lsn)
+
+		lsn, err = c.Append(ctx, pb.LogRecord{Data: cmd})
+		require.NoError(t, err)
+		assert.Equal(t, uint64(5), lsn)
+
+		cmd = getAppendCmd(cmd, cfg.ReplicaID+1)
+		_, err = c.Append(ctx, pb.LogRecord{Data: cmd})
+		assert.Equal(t, ErrNotLeaseHolder, err)
+	}
+	runClientTest(t, false, fn)
+}
+
+func TestClientRead(t *testing.T) {
+	fn := func(t *testing.T, cfg LogServiceClientConfig, c Client) {
+		cmd := make([]byte, 16+headerSize+8)
+		cmd = getAppendCmd(cmd, cfg.ReplicaID)
+		rand.Read(cmd[headerSize+8:])
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+		lsn, err := c.Append(ctx, pb.LogRecord{Data: cmd})
+		require.NoError(t, err)
+		assert.Equal(t, uint64(4), lsn)
+
+		cmd2 := make([]byte, 16+headerSize+8)
+		cmd2 = getAppendCmd(cmd2, cfg.ReplicaID)
+		rand.Read(cmd2[headerSize+8:])
+		lsn, err = c.Append(ctx, pb.LogRecord{Data: cmd2})
+		require.NoError(t, err)
+		assert.Equal(t, uint64(5), lsn)
+
+		// FIXME: returned records should contain correct Index value
+		recs, lsn, err := c.Read(ctx, 4, math.MaxUint64)
+		require.NoError(t, err)
+		assert.Equal(t, uint64(4), lsn)
+		assert.Equal(t, 2, len(recs))
+		assert.Equal(t, cmd, recs[0].Data)
+		assert.Equal(t, cmd2, recs[1].Data)
+
+		_, _, err = c.Read(ctx, 6, math.MaxUint64)
+		assert.Equal(t, ErrOutOfRange, err)
+	}
+	runClientTest(t, false, fn)
+}
+
+func TestClientTruncate(t *testing.T) {
+	fn := func(t *testing.T, cfg LogServiceClientConfig, c Client) {
+		cmd := make([]byte, 16+headerSize+8)
+		cmd = getAppendCmd(cmd, cfg.ReplicaID)
+		rand.Read(cmd[headerSize+8:])
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+		lsn, err := c.Append(ctx, pb.LogRecord{Data: cmd})
+		require.NoError(t, err)
+		assert.Equal(t, uint64(4), lsn)
+
+		require.NoError(t, c.Truncate(ctx, 4))
+		lsn, err = c.GetTruncatedIndex(ctx)
+		assert.NoError(t, err)
+		assert.Equal(t, Lsn(4), lsn)
+
+		assert.Equal(t, ErrInvalidTruncateIndex, c.Truncate(ctx, 3))
+	}
+	runClientTest(t, false, fn)
+}
+
+func TestReadOnlyClientRejectWriteRequests(t *testing.T) {
+	fn := func(t *testing.T, cfg LogServiceClientConfig, c Client) {
+		cmd := make([]byte, 16+headerSize+8)
+		cmd = getAppendCmd(cmd, cfg.ReplicaID)
+		rand.Read(cmd[headerSize+8:])
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+		_, err := c.Append(ctx, pb.LogRecord{Data: cmd})
+		require.Equal(t, ErrIncompatibleClient, err)
+		require.Equal(t, ErrIncompatibleClient, c.Truncate(ctx, 4))
+	}
+	runClientTest(t, true, fn)
+}
diff --git a/pkg/logservice/config_test.go b/pkg/logservice/config_test.go
new file mode 100644
index 000000000..79625e817
--- /dev/null
+++ b/pkg/logservice/config_test.go
@@ -0,0 +1,92 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"testing"
+
+	"github.com/cockroachdb/errors"
+	"github.com/stretchr/testify/assert"
+)
+
+func getTestConfig() Config {
+	return Config{
+		DeploymentID:         100,
+		DataDir:              "/mydata/dir",
+		ServiceAddress:       "localhost:9000",
+		ServiceListenAddress: "localhost:9000",
+		RaftAddress:          "localhost:9001",
+		RaftListenAddress:    "localhost:9001",
+		GossipAddress:        "localhost:9002",
+		GossipListenAddress:  "localhost:9002",
+		GossipSeedAddresses:  []string{"localhost:9002"},
+	}
+}
+
+func TestConfigCanBeValidated(t *testing.T) {
+	c := getTestConfig()
+	assert.Nil(t, c.Validate())
+
+	c1 := c
+	c1.DeploymentID = 0
+	assert.True(t, errors.Is(c1.Validate(), ErrInvalidConfig))
+
+	c2 := c
+	c2.ServiceAddress = ""
+	assert.True(t, errors.Is(c2.Validate(), ErrInvalidConfig))
+
+	c3 := c
+	c3.RaftAddress = ""
+	assert.True(t, errors.Is(c3.Validate(), ErrInvalidConfig))
+
+	c4 := c
+	c4.GossipAddress = ""
+	assert.True(t, errors.Is(c4.Validate(), ErrInvalidConfig))
+
+	c5 := c
+	c5.GossipSeedAddresses = []string{}
+	assert.True(t, errors.Is(c5.Validate(), ErrInvalidConfig))
+}
+
+func TestFillConfig(t *testing.T) {
+	c := Config{}
+	c.Fill()
+
+	assert.Equal(t, uint64(0), c.DeploymentID)
+	assert.Equal(t, defaultDataDir, c.DataDir)
+	assert.Equal(t, defaultServiceAddress, c.ServiceAddress)
+	assert.Equal(t, defaultServiceAddress, c.ServiceListenAddress)
+	assert.Equal(t, defaultRaftAddress, c.RaftAddress)
+	assert.Equal(t, defaultRaftAddress, c.RaftListenAddress)
+	assert.Equal(t, defaultGossipAddress, c.GossipAddress)
+	assert.Equal(t, defaultGossipAddress, c.GossipListenAddress)
+	assert.Equal(t, 0, len(c.GossipSeedAddresses))
+}
+
+func TestListenAddressCanBeFilled(t *testing.T) {
+	cfg := Config{
+		DeploymentID:        1,
+		RTTMillisecond:      5,
+		DataDir:             "data-1",
+		ServiceAddress:      "127.0.0.1:9002",
+		RaftAddress:         "127.0.0.1:9000",
+		GossipAddress:       "127.0.0.1:9001",
+		GossipSeedAddresses: []string{"127.0.0.1:9011"},
+	}
+	cfg.Fill()
+	assert.Equal(t, cfg.ServiceAddress, cfg.ServiceListenAddress)
+	assert.Equal(t, cfg.RaftAddress, cfg.RaftListenAddress)
+	assert.Equal(t, cfg.GossipAddress, cfg.GossipListenAddress)
+}
diff --git a/pkg/logservice/errors_test.go b/pkg/logservice/errors_test.go
new file mode 100644
index 000000000..8ed7ecd12
--- /dev/null
+++ b/pkg/logservice/errors_test.go
@@ -0,0 +1,46 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+func TestErrorConversion(t *testing.T) {
+	mappings := getErrorToCodeMapping()
+	for _, rec := range mappings {
+		if rec.reverse {
+			code, msg := toErrorCode(rec.err)
+			resp := pb.Response{
+				ErrorCode:    code,
+				ErrorMessage: msg,
+			}
+			err := toError(resp)
+			assert.Equal(t, err, rec.err)
+		}
+	}
+}
+
+func TestUnknownErrorIsHandled(t *testing.T) {
+	err := errors.New("test error")
+	code, str := toErrorCode(err)
+	assert.Equal(t, pb.ErrorCode_OtherSystemError, code)
+	assert.Equal(t, err.Error(), str)
+}
diff --git a/pkg/logservice/rsm_test.go b/pkg/logservice/rsm_test.go
new file mode 100644
index 000000000..84743fd61
--- /dev/null
+++ b/pkg/logservice/rsm_test.go
@@ -0,0 +1,246 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"bytes"
+	"testing"
+
+	sm "github.com/lni/dragonboat/v4/statemachine"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetLeaseHistory(t *testing.T) {
+	tsm := newStateMachine(1, 2).(*stateMachine)
+	tsm.LeaseHistory[100] = 1000
+	tsm.LeaseHistory[200] = 2000
+	tsm.LeaseHistory[300] = 3000
+	lease, index := tsm.getLeaseHistory(150)
+	assert.Equal(t, uint64(1000), lease)
+	assert.Equal(t, uint64(100), index)
+
+	lease, index = tsm.getLeaseHistory(200)
+	assert.Equal(t, uint64(1000), lease)
+	assert.Equal(t, uint64(100), index)
+
+	lease, index = tsm.getLeaseHistory(100)
+	assert.Equal(t, uint64(0), lease)
+	assert.Equal(t, uint64(0), index)
+
+	lease, index = tsm.getLeaseHistory(400)
+	assert.Equal(t, uint64(3000), lease)
+	assert.Equal(t, uint64(300), index)
+}
+
+func TestTruncateLeaseHistory(t *testing.T) {
+	getSM := func() *stateMachine {
+		tsm := newStateMachine(1, 2).(*stateMachine)
+		tsm.LeaseHistory[100] = 1000
+		tsm.LeaseHistory[200] = 2000
+		tsm.LeaseHistory[300] = 3000
+		return tsm
+	}
+
+	tsm := getSM()
+	tsm.truncateLeaseHistory(105)
+	assert.Equal(t, 3, len(tsm.LeaseHistory))
+	tsm.truncateLeaseHistory(200)
+	assert.Equal(t, 3, len(tsm.LeaseHistory))
+	tsm.truncateLeaseHistory(201)
+	assert.Equal(t, 2, len(tsm.LeaseHistory))
+	_, ok1 := tsm.LeaseHistory[200]
+	_, ok2 := tsm.LeaseHistory[300]
+	assert.True(t, ok1)
+	assert.True(t, ok2)
+
+	tsm = getSM()
+	tsm.truncateLeaseHistory(300)
+	assert.Equal(t, 2, len(tsm.LeaseHistory))
+	_, ok := tsm.LeaseHistory[100]
+	assert.False(t, ok)
+
+	tsm = getSM()
+	tsm.truncateLeaseHistory(301)
+	assert.Equal(t, 1, len(tsm.LeaseHistory))
+	_, ok = tsm.LeaseHistory[300]
+	assert.True(t, ok)
+}
+
+func TestGetSetLeaseHolderCmd(t *testing.T) {
+	cmd := getSetLeaseHolderCmd(100)
+	assert.True(t, isSetLeaseHolderUpdate(cmd))
+	cmd2 := getSetTruncatedIndexCmd(200)
+	assert.False(t, isSetLeaseHolderUpdate(cmd2))
+}
+
+func TestGetSetTruncatedIndexCmd(t *testing.T) {
+	cmd := getSetTruncatedIndexCmd(1234)
+	assert.True(t, isSetTruncatedIndexUpdate(cmd))
+	cmd2 := getSetLeaseHolderCmd(1234)
+	assert.False(t, isSetTruncatedIndexUpdate(cmd2))
+}
+
+func TestIsUserUpdate(t *testing.T) {
+	cmd := make([]byte, headerSize+8+1)
+	binaryEnc.PutUint16(cmd, userEntryTag)
+	assert.True(t, isUserUpdate(cmd))
+	cmd2 := getSetLeaseHolderCmd(1234)
+	cmd3 := getSetTruncatedIndexCmd(200)
+	assert.False(t, isUserUpdate(cmd2))
+	assert.False(t, isUserUpdate(cmd3))
+}
+
+func TestNewStateMachine(t *testing.T) {
+	tsm := newStateMachine(100, 200).(*stateMachine)
+	assert.Equal(t, uint64(100), tsm.shardID)
+	assert.Equal(t, uint64(200), tsm.replicaID)
+}
+
+func TestStateMachineCanBeClosed(t *testing.T) {
+	tsm := newStateMachine(100, 200)
+	assert.Nil(t, tsm.Close())
+}
+
+func TestDNLeaseHolderCanBeUpdated(t *testing.T) {
+	cmd := getSetLeaseHolderCmd(500)
+	tsm := newStateMachine(1, 2).(*stateMachine)
+	assert.Equal(t, uint64(0), tsm.LeaseHolderID)
+	e := sm.Entry{Cmd: cmd, Index: 100}
+	result, err := tsm.Update(e)
+	assert.Equal(t, sm.Result{}, result)
+	assert.Nil(t, err)
+	assert.Equal(t, uint64(500), tsm.LeaseHolderID)
+	assert.Equal(t, 1, len(tsm.LeaseHistory))
+	v, ok := tsm.LeaseHistory[100]
+	assert.True(t, ok)
+	assert.Equal(t, uint64(500), v)
+
+	cmd = getSetLeaseHolderCmd(1000)
+	e = sm.Entry{Cmd: cmd, Index: 200}
+	result, err = tsm.Update(e)
+	assert.Equal(t, sm.Result{}, result)
+	assert.Nil(t, err)
+	assert.Equal(t, uint64(1000), tsm.LeaseHolderID)
+	assert.Equal(t, 2, len(tsm.LeaseHistory))
+	v, ok = tsm.LeaseHistory[200]
+	assert.True(t, ok)
+	assert.Equal(t, uint64(1000), v)
+
+	cmd = getSetTruncatedIndexCmd(110)
+	e = sm.Entry{Cmd: cmd}
+	result, err = tsm.Update(e)
+	assert.Equal(t, sm.Result{}, result)
+	assert.Nil(t, err)
+	// first lease history record won't be truncated
+	assert.Equal(t, 2, len(tsm.LeaseHistory))
+}
+
+func TestTruncatedIndexCanBeUpdated(t *testing.T) {
+	cmd := getSetTruncatedIndexCmd(200)
+	tsm := newStateMachine(1, 2).(*stateMachine)
+	e := sm.Entry{Cmd: cmd}
+	result, err := tsm.Update(e)
+	assert.Equal(t, sm.Result{}, result)
+	assert.Nil(t, err)
+
+	cmd2 := getSetTruncatedIndexCmd(220)
+	e2 := sm.Entry{Cmd: cmd2}
+	result, err = tsm.Update(e2)
+	assert.Equal(t, sm.Result{}, result)
+	assert.Nil(t, err)
+
+	cmd3 := getSetTruncatedIndexCmd(100)
+	e3 := sm.Entry{Cmd: cmd3}
+	result, err = tsm.Update(e3)
+	assert.Equal(t, sm.Result{Value: 220}, result)
+	assert.Nil(t, err)
+}
+
+func TestStateMachineUserUpdate(t *testing.T) {
+	cmd := make([]byte, headerSize+8+1)
+	binaryEnc.PutUint16(cmd, userEntryTag)
+	binaryEnc.PutUint64(cmd[headerSize:], uint64(1234))
+
+	tsm := newStateMachine(1, 2).(*stateMachine)
+	tsm.LeaseHolderID = 1234
+	e := sm.Entry{Index: 100, Cmd: cmd}
+	result, err := tsm.Update(e)
+	assert.Nil(t, err)
+	assert.Equal(t, e.Index, result.Value)
+	assert.Nil(t, result.Data)
+
+	tsm.LeaseHolderID = 2345
+	e = sm.Entry{Cmd: cmd}
+	result, err = tsm.Update(e)
+	assert.Nil(t, err)
+	assert.Equal(t, uint64(0), result.Value)
+	assert.NotNil(t, result.Data)
+	assert.Equal(t, tsm.LeaseHolderID, binaryEnc.Uint64(result.Data))
+}
+
+func TestStateMachineSnapshot(t *testing.T) {
+	buf := bytes.NewBuffer(nil)
+	tsm := newStateMachine(1, 2).(*stateMachine)
+	tsm.LeaseHolderID = 123456
+	tsm.TruncatedIndex = 456789
+	assert.Nil(t, tsm.SaveSnapshot(buf, nil, nil))
+
+	tsm2 := newStateMachine(3, 4).(*stateMachine)
+	assert.Nil(t, tsm2.RecoverFromSnapshot(buf, nil, nil))
+	assert.Equal(t, tsm.LeaseHolderID, tsm2.LeaseHolderID)
+	assert.Equal(t, tsm.TruncatedIndex, tsm2.TruncatedIndex)
+	assert.Equal(t, uint64(3), tsm2.shardID)
+	assert.Equal(t, uint64(4), tsm2.replicaID)
+}
+
+func TestStateMachineLookup(t *testing.T) {
+	tsm := newStateMachine(1, 2).(*stateMachine)
+	tsm.Index = 1234
+	tsm.LeaseHolderID = 123456
+	tsm.TruncatedIndex = 456789
+	v, err := tsm.Lookup(leaseHolderIDTag)
+	assert.Nil(t, err)
+	assert.Equal(t, tsm.LeaseHolderID, v.(uint64))
+
+	v2, err := tsm.Lookup(truncatedIndexTag)
+	assert.Nil(t, err)
+	assert.Equal(t, tsm.TruncatedIndex, v2.(uint64))
+
+	v3, err := tsm.Lookup(indexTag)
+	assert.Nil(t, err)
+	assert.Equal(t, tsm.Index, v3.(uint64))
+}
+
+func TestStateMachineLookupPanicOnUnexpectedInputValue(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil {
+			t.Fatalf("failed to panic")
+		}
+	}()
+	tsm := newStateMachine(1, 2).(*stateMachine)
+	_, err := tsm.Lookup(uint16(1234))
+	assert.NoError(t, err)
+}
+
+func TestStateMachineLookupPanicOnUnexpectedInputType(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil {
+			t.Fatalf("failed to panic")
+		}
+	}()
+	tsm := newStateMachine(1, 2).(*stateMachine)
+	_, err := tsm.Lookup(uint64(1234))
+	assert.NoError(t, err)
+}
diff --git a/pkg/logservice/service_test.go b/pkg/logservice/service_test.go
new file mode 100644
index 000000000..a982bb792
--- /dev/null
+++ b/pkg/logservice/service_test.go
@@ -0,0 +1,372 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"testing"
+	"time"
+
+	"github.com/lni/dragonboat/v4"
+	"github.com/lni/goutils/leaktest"
+	"github.com/lni/vfs"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+const (
+	testServiceAddress = "localhost:9000"
+)
+
+func getServiceTestConfig() Config {
+	return Config{
+		RTTMillisecond:       10,
+		GossipSeedAddresses:  []string{"127.0.0.1:9000"},
+		DeploymentID:         1,
+		FS:                   vfs.NewStrictMem(),
+		ServiceListenAddress: testServiceAddress,
+		ServiceAddress:       testServiceAddress,
+	}
+}
+
+func runServiceTest(t *testing.T, fn func(*testing.T, *Service)) {
+	defer leaktest.AfterTest(t)()
+	cfg := getServiceTestConfig()
+	defer vfs.ReportLeakedFD(cfg.FS, t)
+	service, err := NewService(cfg)
+	require.NoError(t, err)
+	peers := make(map[uint64]dragonboat.Target)
+	peers[1] = service.ID()
+	require.NoError(t, service.store.StartReplica(1, 1, peers))
+	defer func() {
+		assert.NoError(t, service.Close())
+	}()
+	fn(t, service)
+}
+
+func TestNewService(t *testing.T) {
+	defer leaktest.AfterTest(t)()
+	cfg := getServiceTestConfig()
+	defer vfs.ReportLeakedFD(cfg.FS, t)
+	service, err := NewService(cfg)
+	require.NoError(t, err)
+	assert.NoError(t, service.Close())
+}
+
+func TestServiceConnect(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		req := pb.Request{
+			Method:  pb.MethodType_CONNECT,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+			DNID:    100,
+		}
+		resp := s.handleConnect(req)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+	}
+	runServiceTest(t, fn)
+}
+
+func TestServiceConnectTimeout(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		req := pb.Request{
+			Method:  pb.MethodType_CONNECT,
+			ShardID: 1,
+			Timeout: 50 * int64(time.Millisecond),
+			DNID:    100,
+		}
+		resp := s.handleConnect(req)
+		assert.Equal(t, pb.ErrorCode_Timeout, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+	}
+	runServiceTest(t, fn)
+}
+
+func TestServiceConnectRO(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		req := pb.Request{
+			Method:  pb.MethodType_CONNECT_RO,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+			DNID:    100,
+		}
+		resp := s.handleConnect(req)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+	}
+	runServiceTest(t, fn)
+}
+
+func getTestAppendCmd(id uint64, data []byte) []byte {
+	cmd := make([]byte, len(data)+headerSize+8)
+	binaryEnc.PutUint16(cmd, userEntryTag)
+	binaryEnc.PutUint64(cmd[headerSize:], id)
+	copy(cmd[headerSize+8:], data)
+	return cmd
+}
+
+func TestServiceHandleAppend(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		req := pb.Request{
+			Method:  pb.MethodType_CONNECT_RO,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+			DNID:    100,
+		}
+		resp := s.handleConnect(req)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+
+		data := make([]byte, 8)
+		cmd := getTestAppendCmd(req.DNID, data)
+		req = pb.Request{
+			Method:  pb.MethodType_APPEND,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+		}
+		resp = s.handleAppend(req, cmd)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+		assert.Equal(t, uint64(4), resp.Index)
+	}
+	runServiceTest(t, fn)
+}
+
+func TestServiceHandleAppendWhenNotBeingTheLeaseHolder(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		req := pb.Request{
+			Method:  pb.MethodType_CONNECT_RO,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+			DNID:    100,
+		}
+		resp := s.handleConnect(req)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+
+		data := make([]byte, 8)
+		cmd := getTestAppendCmd(req.DNID+1, data)
+		req = pb.Request{
+			Method:  pb.MethodType_APPEND,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+		}
+		resp = s.handleAppend(req, cmd)
+		assert.Equal(t, pb.ErrorCode_NotLeaseHolder, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+		assert.Equal(t, uint64(0), resp.Index)
+	}
+	runServiceTest(t, fn)
+}
+
+func TestServiceHandleRead(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		req := pb.Request{
+			Method:  pb.MethodType_CONNECT_RO,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+			DNID:    100,
+		}
+		resp := s.handleConnect(req)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+
+		data := make([]byte, 8)
+		cmd := getTestAppendCmd(req.DNID, data)
+		req = pb.Request{
+			Method:  pb.MethodType_APPEND,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+		}
+		resp = s.handleAppend(req, cmd)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+		assert.Equal(t, uint64(4), resp.Index)
+
+		req = pb.Request{
+			Method:  pb.MethodType_READ,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+			Index:   1,
+			MaxSize: 1024 * 32,
+		}
+		resp, records := s.handleRead(req)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+		assert.Equal(t, uint64(1), resp.LastIndex)
+		require.Equal(t, 1, len(records.Records))
+		assert.Equal(t, cmd, records.Records[0].Data)
+	}
+	runServiceTest(t, fn)
+}
+
+func TestServiceTruncate(t *testing.T) {
+	fn := func(t *testing.T, s *Service) {
+		req := pb.Request{
+			Method:  pb.MethodType_CONNECT_RO,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+			DNID:    100,
+		}
+		resp := s.handleConnect(req)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+
+		data := make([]byte, 8)
+		cmd := getTestAppendCmd(req.DNID, data)
+		req = pb.Request{
+			Method:  pb.MethodType_APPEND,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+		}
+		resp = s.handleAppend(req, cmd)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+		assert.Equal(t, uint64(4), resp.Index)
+
+		req = pb.Request{
+			Method:  pb.MethodType_TRUNCATE,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+			Index:   4,
+		}
+		resp = s.handleTruncate(req)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+		assert.Equal(t, uint64(0), resp.Index)
+
+		req = pb.Request{
+			Method:  pb.MethodType_GET_TRUNCATE,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+		}
+		resp = s.handleGetTruncatedIndex(req)
+		assert.Equal(t, pb.ErrorCode_NoError, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+		assert.Equal(t, uint64(4), resp.Index)
+
+		req = pb.Request{
+			Method:  pb.MethodType_TRUNCATE,
+			ShardID: 1,
+			Timeout: int64(time.Second),
+			Index:   3,
+		}
+		resp = s.handleTruncate(req)
+		assert.Equal(t, pb.ErrorCode_IndexAlreadyTruncated, resp.ErrorCode)
+		assert.Equal(t, "", resp.ErrorMessage)
+	}
+	runServiceTest(t, fn)
+}
+
+func TestShardInfoCanBeQueried(t *testing.T) {
+	defer leaktest.AfterTest(t)()
+	cfg1 := Config{
+		FS:                  vfs.NewStrictMem(),
+		DeploymentID:        1,
+		RTTMillisecond:      5,
+		DataDir:             "data-1",
+		ServiceAddress:      "127.0.0.1:9002",
+		RaftAddress:         "127.0.0.1:9000",
+		GossipAddress:       "127.0.0.1:9001",
+		GossipSeedAddresses: []string{"127.0.0.1:9011"},
+	}
+	cfg2 := Config{
+		FS:                  vfs.NewStrictMem(),
+		DeploymentID:        1,
+		RTTMillisecond:      5,
+		DataDir:             "data-2",
+		ServiceAddress:      "127.0.0.1:9012",
+		RaftAddress:         "127.0.0.1:9010",
+		GossipAddress:       "127.0.0.1:9011",
+		GossipSeedAddresses: []string{"127.0.0.1:9001"},
+	}
+
+	service1, err := NewService(cfg1)
+	require.NoError(t, err)
+	defer func() {
+		assert.NoError(t, service1.Close())
+	}()
+	peers1 := make(map[uint64]dragonboat.Target)
+	peers1[1] = service1.ID()
+	assert.NoError(t, service1.store.StartReplica(1, 1, peers1))
+
+	service2, err := NewService(cfg2)
+	require.NoError(t, err)
+	defer func() {
+		assert.NoError(t, service2.Close())
+	}()
+	peers2 := make(map[uint64]dragonboat.Target)
+	peers2[1] = service2.ID()
+	assert.NoError(t, service2.store.StartReplica(2, 1, peers2))
+
+	nhID1 := service1.ID()
+	nhID2 := service2.ID()
+
+	done := false
+	for i := 0; i < 3000; i++ {
+		si1, ok := service1.GetShardInfo(1)
+		if !ok || si1.LeaderID != 1 {
+			time.Sleep(time.Millisecond)
+			continue
+		}
+		assert.Equal(t, 1, len(si1.Replicas))
+		require.Equal(t, uint64(1), si1.ShardID)
+		ri, ok := si1.Replicas[1]
+		assert.True(t, ok)
+		assert.Equal(t, nhID1, ri.UUID)
+		assert.Equal(t, cfg1.ServiceAddress, ri.ServiceAddress)
+
+		si2, ok := service1.GetShardInfo(2)
+		if !ok || si2.LeaderID != 1 {
+			time.Sleep(time.Millisecond)
+			continue
+		}
+		assert.Equal(t, 1, len(si2.Replicas))
+		require.Equal(t, uint64(2), si2.ShardID)
+		ri, ok = si2.Replicas[1]
+		assert.True(t, ok)
+		assert.Equal(t, nhID2, ri.UUID)
+		assert.Equal(t, cfg2.ServiceAddress, ri.ServiceAddress)
+
+		si1, ok = service2.GetShardInfo(1)
+		if !ok || si1.LeaderID != 1 {
+			time.Sleep(time.Millisecond)
+			continue
+		}
+		assert.Equal(t, 1, len(si1.Replicas))
+		require.Equal(t, uint64(1), si1.ShardID)
+		ri, ok = si1.Replicas[1]
+		assert.True(t, ok)
+		assert.Equal(t, nhID1, ri.UUID)
+		assert.Equal(t, cfg1.ServiceAddress, ri.ServiceAddress)
+
+		si2, ok = service2.GetShardInfo(2)
+		if !ok || si2.LeaderID != 1 {
+			time.Sleep(time.Millisecond)
+			continue
+		}
+		assert.Equal(t, 1, len(si2.Replicas))
+		require.Equal(t, uint64(2), si2.ShardID)
+		ri, ok = si2.Replicas[1]
+		assert.True(t, ok)
+		assert.Equal(t, nhID2, ri.UUID)
+		assert.Equal(t, cfg2.ServiceAddress, ri.ServiceAddress)
+
+		done = true
+	}
+	assert.True(t, done)
+}
diff --git a/pkg/logservice/store_test.go b/pkg/logservice/store_test.go
new file mode 100644
index 000000000..466688400
--- /dev/null
+++ b/pkg/logservice/store_test.go
@@ -0,0 +1,286 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"context"
+	"math"
+	"testing"
+	"time"
+
+	"github.com/cockroachdb/errors"
+	"github.com/lni/dragonboat/v4"
+	"github.com/lni/goutils/leaktest"
+	"github.com/lni/vfs"
+	"github.com/stretchr/testify/assert"
+
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+var (
+	testIOTimeout = 5 * time.Second
+)
+
+func TestNodeHostConfig(t *testing.T) {
+	cfg := Config{
+		DeploymentID: 1234,
+		DataDir:      "lalala",
+	}
+	nhConfig := getNodeHostConfig(cfg)
+	assert.Equal(t, cfg.DeploymentID, nhConfig.DeploymentID)
+	assert.Equal(t, cfg.DataDir, nhConfig.NodeHostDir)
+	assert.True(t, nhConfig.AddressByNodeHostID)
+}
+
+func TestRaftConfig(t *testing.T) {
+	cfg := getRaftConfig(1, 1)
+	assert.True(t, cfg.CheckQuorum)
+	assert.True(t, cfg.OrderedConfigChange)
+}
+
+func getStoreTestConfig() Config {
+	cfg := Config{
+		RTTMillisecond:      10,
+		GossipSeedAddresses: []string{"127.0.0.1:9000"},
+		DeploymentID:        1,
+		FS:                  vfs.NewStrictMem(),
+	}
+	cfg.Fill()
+	return cfg
+}
+
+func TestStoreCanBeCreatedAndClosed(t *testing.T) {
+	defer leaktest.AfterTest(t)()
+	cfg := getStoreTestConfig()
+	defer vfs.ReportLeakedFD(cfg.FS, t)
+	store, err := newLogStore(cfg)
+	assert.NoError(t, err)
+	defer func() {
+		assert.NoError(t, store.Close())
+	}()
+}
+
+func getTestStore(cfg Config) (*logStore, error) {
+	store, err := newLogStore(cfg)
+	if err != nil {
+		return nil, err
+	}
+	peers := make(map[uint64]dragonboat.Target)
+	peers[2] = store.nh.ID()
+	if err := store.StartReplica(1, 2, peers); err != nil {
+		store.Close()
+		return nil, err
+	}
+	return store, nil
+}
+
+func TestHAKeeperCanBeStarted(t *testing.T) {
+	defer leaktest.AfterTest(t)()
+	cfg := getStoreTestConfig()
+	defer vfs.ReportLeakedFD(cfg.FS, t)
+	store, err := newLogStore(cfg)
+	assert.NoError(t, err)
+	peers := make(map[uint64]dragonboat.Target)
+	peers[2] = store.nh.ID()
+	assert.NoError(t, store.StartHAKeeperReplica(2, peers))
+	defer func() {
+		assert.NoError(t, store.Close())
+	}()
+}
+
+func TestStateMachineCanBeStarted(t *testing.T) {
+	defer leaktest.AfterTest(t)()
+	cfg := getStoreTestConfig()
+	defer vfs.ReportLeakedFD(cfg.FS, t)
+	store, err := getTestStore(cfg)
+	assert.NoError(t, err)
+	defer func() {
+		assert.NoError(t, store.Close())
+	}()
+}
+
+func runStoreTest(t *testing.T, fn func(*testing.T, *logStore)) {
+	defer leaktest.AfterTest(t)()
+	cfg := getStoreTestConfig()
+	defer vfs.ReportLeakedFD(cfg.FS, t)
+	store, err := getTestStore(cfg)
+	assert.NoError(t, err)
+	defer func() {
+		assert.NoError(t, store.Close())
+	}()
+	fn(t, store)
+}
+
+func getTestUserEntry() []byte {
+	cmd := make([]byte, 2+8+8)
+	binaryEnc.PutUint16(cmd, userEntryTag)
+	binaryEnc.PutUint64(cmd[headerSize:], 100)
+	binaryEnc.PutUint64(cmd[headerSize+8:], 1234567890)
+	return cmd
+}
+
+func TestGetOrExtendLease(t *testing.T) {
+	fn := func(t *testing.T, store *logStore) {
+		ctx, cancel := context.WithTimeout(context.Background(), testIOTimeout)
+		defer cancel()
+		assert.NoError(t, store.GetOrExtendDNLease(ctx, 1, 100))
+	}
+	runStoreTest(t, fn)
+}
+
+func TestAppendLog(t *testing.T) {
+	fn := func(t *testing.T, store *logStore) {
+		ctx, cancel := context.WithTimeout(context.Background(), testIOTimeout)
+		defer cancel()
+		assert.NoError(t, store.GetOrExtendDNLease(ctx, 1, 100))
+		cmd := getTestUserEntry()
+		lsn, err := store.Append(ctx, 1, cmd)
+		assert.NoError(t, err)
+		assert.Equal(t, uint64(4), lsn)
+	}
+	runStoreTest(t, fn)
+}
+
+func TestAppendLogIsRejectedForMismatchedLeaseHolderID(t *testing.T) {
+	fn := func(t *testing.T, store *logStore) {
+		ctx, cancel := context.WithTimeout(context.Background(), testIOTimeout)
+		defer cancel()
+		assert.NoError(t, store.GetOrExtendDNLease(ctx, 1, 100))
+		cmd := make([]byte, 2+8+8)
+		binaryEnc.PutUint16(cmd, userEntryTag)
+		binaryEnc.PutUint64(cmd[headerSize:], 101)
+		binaryEnc.PutUint64(cmd[headerSize+8:], 1234567890)
+		_, err := store.Append(ctx, 1, cmd)
+		assert.True(t, errors.Is(err, ErrNotLeaseHolder))
+	}
+	runStoreTest(t, fn)
+}
+
+func TestTruncateLog(t *testing.T) {
+	fn := func(t *testing.T, store *logStore) {
+		ctx, cancel := context.WithTimeout(context.Background(), testIOTimeout)
+		defer cancel()
+		assert.NoError(t, store.GetOrExtendDNLease(ctx, 1, 100))
+		cmd := getTestUserEntry()
+		_, err := store.Append(ctx, 1, cmd)
+		assert.NoError(t, err)
+		assert.NoError(t, store.TruncateLog(ctx, 1, 4))
+		err = store.TruncateLog(ctx, 1, 3)
+		assert.True(t, errors.Is(err, ErrInvalidTruncateIndex))
+	}
+	runStoreTest(t, fn)
+}
+
+func TestGetTruncatedIndex(t *testing.T) {
+	fn := func(t *testing.T, store *logStore) {
+		ctx, cancel := context.WithTimeout(context.Background(), testIOTimeout)
+		defer cancel()
+		index, err := store.GetTruncatedIndex(ctx, 1)
+		assert.Equal(t, uint64(0), index)
+		assert.NoError(t, err)
+		assert.NoError(t, store.GetOrExtendDNLease(ctx, 1, 100))
+		cmd := getTestUserEntry()
+		_, err = store.Append(ctx, 1, cmd)
+		assert.NoError(t, err)
+		assert.NoError(t, store.TruncateLog(ctx, 1, 4))
+		index, err = store.GetTruncatedIndex(ctx, 1)
+		assert.Equal(t, uint64(4), index)
+		assert.NoError(t, err)
+	}
+	runStoreTest(t, fn)
+}
+
+func TestQueryLog(t *testing.T) {
+	fn := func(t *testing.T, store *logStore) {
+		ctx, cancel := context.WithTimeout(context.Background(), testIOTimeout)
+		defer cancel()
+		assert.NoError(t, store.GetOrExtendDNLease(ctx, 1, 100))
+		cmd := getTestUserEntry()
+		_, err := store.Append(ctx, 1, cmd)
+		assert.NoError(t, err)
+		entries, lsn, err := store.QueryLog(ctx, 1, 4, math.MaxUint64)
+		assert.NoError(t, err)
+		assert.Equal(t, 1, len(entries))
+		assert.Equal(t, uint64(4), lsn)
+		assert.Equal(t, entries[0].Data, cmd)
+		// lease holder ID update cmd at entry index 3
+		entries, lsn, err = store.QueryLog(ctx, 1, 3, math.MaxUint64)
+		assert.NoError(t, err)
+		assert.Equal(t, 1, len(entries))
+		assert.Equal(t, uint64(3), lsn)
+		assert.Equal(t, entries[0].Data, cmd)
+		// size limited
+		_, err = store.Append(ctx, 1, cmd)
+		assert.NoError(t, err)
+		entries, lsn, err = store.QueryLog(ctx, 1, 4, 1)
+		assert.NoError(t, err)
+		assert.Equal(t, 1, len(entries))
+		assert.Equal(t, uint64(5), lsn)
+		assert.Equal(t, entries[0].Data, cmd)
+		// more log available
+		entries, lsn, err = store.QueryLog(ctx, 1, 5, 1)
+		assert.NoError(t, err)
+		assert.Equal(t, 1, len(entries))
+		assert.Equal(t, uint64(5), lsn)
+		assert.Equal(t, entries[0].Data, cmd)
+	}
+	runStoreTest(t, fn)
+}
+
+func TestHAKeeperTick(t *testing.T) {
+	fn := func(t *testing.T, store *logStore) {
+		peers := make(map[uint64]dragonboat.Target)
+		peers[1] = store.ID()
+		assert.NoError(t, store.StartHAKeeperReplica(1, peers))
+
+		assert.NoError(t, store.hakeeperTick())
+	}
+	runStoreTest(t, fn)
+}
+
+func TestGetHeartbeatMessage(t *testing.T) {
+	fn := func(t *testing.T, store *logStore) {
+		peers := make(map[uint64]dragonboat.Target)
+		peers[1] = store.ID()
+		assert.NoError(t, store.StartReplica(10, 1, peers))
+		assert.NoError(t, store.StartHAKeeperReplica(1, peers))
+
+		m := store.getHeartbeatMessage()
+		// hakeeper shard is included
+		assert.Equal(t, 3, len(m.Shards))
+	}
+	runStoreTest(t, fn)
+}
+
+func TestAddHeartbeat(t *testing.T) {
+	fn := func(t *testing.T, store *logStore) {
+		peers := make(map[uint64]dragonboat.Target)
+		peers[1] = store.ID()
+		assert.NoError(t, store.StartHAKeeperReplica(1, peers))
+
+		m := store.getHeartbeatMessage()
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+		assert.NoError(t, store.AddLogStoreHeartbeat(ctx, m))
+
+		dnMsg := pb.DNStoreHeartbeat{
+			UUID:   store.ID(),
+			Shards: make([]pb.DNShardInfo, 0),
+		}
+		dnMsg.Shards = append(dnMsg.Shards, pb.DNShardInfo{ShardID: 2, ReplicaID: 3})
+		assert.NoError(t, store.AddDNStoreHeartbeat(ctx, dnMsg))
+	}
+	runStoreTest(t, fn)
+}
diff --git a/pkg/logservice/transport_test.go b/pkg/logservice/transport_test.go
new file mode 100644
index 000000000..d51c8be7a
--- /dev/null
+++ b/pkg/logservice/transport_test.go
@@ -0,0 +1,263 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// 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.
+
+package logservice
+
+import (
+	"bytes"
+	"context"
+	"math/rand"
+	"net"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+
+	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+type testConn struct {
+	buf bytes.Buffer
+}
+
+var _ net.Conn = (*testConn)(nil)
+
+func newTestConn() *testConn {
+	return &testConn{}
+}
+
+func (t *testConn) Read(data []byte) (int, error) {
+	return t.buf.Read(data)
+}
+
+func (t *testConn) Write(data []byte) (int, error) {
+	return t.buf.Write(data)
+}
+
+func (t *testConn) Close() error {
+	return nil
+}
+
+func (t *testConn) LocalAddr() net.Addr {
+	panic("not implemented")
+}
+
+func (t *testConn) RemoteAddr() net.Addr {
+	panic("not implemented")
+}
+
+func (t *testConn) SetDeadline(tt time.Time) error {
+	return nil
+}
+
+func (t *testConn) SetReadDeadline(tt time.Time) error {
+	return nil
+}
+
+func (t *testConn) SetWriteDeadline(tt time.Time) error {
+	return nil
+}
+
+func TestSendPoisonAndAck(t *testing.T) {
+	conn := newTestConn()
+	assert.NoError(t, sendPoison(conn, poisonNumber[:]))
+	data := conn.buf.Bytes()
+	assert.Equal(t, poisonNumber[:], data)
+
+	conn = newTestConn()
+	assert.NoError(t, sendPoisonAck(conn, poisonNumber[:]))
+	data = conn.buf.Bytes()
+	assert.Equal(t, poisonNumber[:], data)
+}
+
+func TestReadSize(t *testing.T) {
+	conn := newTestConn()
+	szbuf := make([]byte, 4)
+	sz := uint32(12345)
+	binaryEnc.PutUint32(szbuf, sz)
+	_, err := conn.Write(szbuf)
+	assert.NoError(t, err)
+
+	result, err := readSize(conn, nil)
+	assert.Equal(t, int(sz), result)
+	assert.NoError(t, err)
+}
+
+func TestWriteRequest(t *testing.T) {
+	conn := newTestConn()
+	req := pb.Request{
+		Method:      pb.MethodType_APPEND,
+		Name:        "test",
+		DNID:        1234567890,
+		PayloadSize: 32,
+	}
+	payload := make([]byte, 32)
+	rand.Read(payload)
+	err := writeRequest(conn, req, nil, payload)
+	assert.NoError(t, err)
+
+	result := make([]byte, req.Size()+4+len(magicNumber[:]))
+	n, err := conn.Read(result)
+	assert.NoError(t, err)
+	assert.Equal(t, req.Size()+4+len(magicNumber[:]), n)
+
+	assert.Equal(t, magicNumber[:], result[:len(magicNumber[:])])
+	sz := binaryEnc.Uint32(result[len(magicNumber[:]):])
+	assert.Equal(t, int(sz), req.Size())
+
+	data := MustMarshal(&req)
+	assert.Equal(t, data, result[len(magicNumber[:])+4:])
+}
+
+func TestReadRequest(t *testing.T) {
+	conn := newTestConn()
+	req := pb.Request{
+		Method:      pb.MethodType_APPEND,
+		Name:        "test",
+		PayloadSize: 32,
+	}
+	payload := make([]byte, 32)
+	rand.Read(payload)
+	err := writeRequest(conn, req, nil, payload)
+	assert.NoError(t, err)
+
+	mn := make([]byte, len(magicNumber[:]))
+	_, err = conn.Read(mn)
+	assert.NoError(t, err)
+	assert.Equal(t, magicNumber[:], mn)
+
+	reqResult, payloadResult, err := readRequest(conn, nil, nil)
+	assert.NoError(t, err)
+	assert.Equal(t, req, reqResult)
+	assert.Equal(t, payload, payloadResult)
+}
+
+func TestWriteResponse(t *testing.T) {
+	testWriteResponse(t, recvBufSize)
+	testWriteResponse(t, recvBufSize-1)
+	testWriteResponse(t, recvBufSize+1)
+	testWriteResponse(t, recvBufSize*3)
+	testWriteResponse(t, recvBufSize*3-1)
+	testWriteResponse(t, recvBufSize*3+1)
+}
+
+func testWriteResponse(t *testing.T, sz int) {
+	conn := newTestConn()
+	resp := pb.Response{
+		Method:    pb.MethodType_APPEND,
+		ShardID:   1234567890,
+		LastIndex: 234567890,
+	}
+	assert.NoError(t, writeResponse(conn, resp, nil, nil))
+
+	szbuf := make([]byte, 4)
+	_, err := conn.Read(szbuf)
+	assert.NoError(t, err)
+	assert.Equal(t, resp.Size(), int(binaryEnc.Uint32(szbuf)))
+	data := make([]byte, binaryEnc.Uint32(szbuf))
+	_, err = conn.Read(data)
+	assert.NoError(t, err)
+	var respResult pb.Response
+	assert.NoError(t, respResult.Unmarshal(data))
+	assert.Equal(t, resp, respResult)
+
+	records := pb.LogRecordResponse{
+		Records: []pb.LogRecord{
+			{Data: make([]byte, sz)},
+		},
+	}
+	rand.Read(records.Records[0].Data)
+	conn = newTestConn()
+	recs := MustMarshal(&records)
+	resp.PayloadSize = uint64(len(recs))
+	assert.NoError(t, writeResponse(conn, resp, recs, nil))
+
+	ignored := make([]byte, 4+resp.Size())
+	_, err = conn.Read(ignored)
+	assert.NoError(t, err)
+
+	data = make([]byte, resp.PayloadSize)
+	_, err = conn.Read(data)
+	assert.NoError(t, err)
+	recordsResult := pb.LogRecordResponse{}
+	assert.NoError(t, recordsResult.Unmarshal(data))
+	assert.Equal(t, records, recordsResult)
+}
+
+func TestReadResponse(t *testing.T) {
+	testReadResponse(t, recvBufSize-1)
+	testReadResponse(t, recvBufSize+1)
+	testReadResponse(t, recvBufSize*3)
+	testReadResponse(t, recvBufSize*3-1)
+	testReadResponse(t, recvBufSize*3+1)
+}
+
+func testReadResponse(t *testing.T, sz int) {
+	conn := newTestConn()
+	resp := pb.Response{
+		Method:    pb.MethodType_APPEND,
+		ShardID:   1234567890,
+		LastIndex: 234567890,
+	}
+	assert.NoError(t, writeResponse(conn, resp, nil, nil))
+	respResult, recordsResult, err := readResponse(conn, nil)
+	assert.NoError(t, err)
+	assert.Equal(t, resp, respResult)
+	assert.Equal(t, pb.LogRecordResponse{}, recordsResult)
+
+	records := pb.LogRecordResponse{
+		Records: []pb.LogRecord{
+			{Data: make([]byte, sz)},
+		},
+	}
+	recs := MustMarshal(&records)
+	resp.PayloadSize = uint64(len(recs))
+	assert.NoError(t, writeResponse(conn, resp, recs, nil))
+	respResult, recordsResult, err = readResponse(conn, nil)
+	assert.NoError(t, err)
+	assert.Equal(t, resp, respResult)
+	assert.Equal(t, records, recordsResult)
+}
+
+func TestReadMagicNumber(t *testing.T) {
+	conn := newTestConn()
+	_, err := conn.Write([]byte{0, 0})
+	assert.NoError(t, err)
+	assert.Equal(t, ErrBadMessage, readMagicNumber(conn, nil))
+
+	conn = newTestConn()
+	_, err = conn.Write(magicNumber[:])
+	assert.NoError(t, err)
+	assert.NoError(t, readMagicNumber(conn, nil))
+
+	conn = newTestConn()
+	_, err = conn.Write(poisonNumber[:])
+	assert.NoError(t, err)
+	assert.Equal(t, errPoisonReceived, readMagicNumber(conn, nil))
+}
+
+func TestGetTimeoutFromContext(t *testing.T) {
+	_, err := getTimeoutFromContext(context.Background())
+	assert.Equal(t, ErrDeadlineNotSet, err)
+	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
+	defer cancel()
+	d, err := getTimeoutFromContext(ctx)
+	assert.NoError(t, err)
+	assert.True(t, d > 55*time.Second)
+	ctx1, cancel1 := context.WithTimeout(context.Background(), time.Microsecond)
+	defer cancel1()
+	time.Sleep(time.Millisecond)
+	_, err = getTimeoutFromContext(ctx1)
+	assert.Equal(t, ErrInvalidDeadline, err)
+}