驯服时间:如何在生产中运行XTDB
#database #docker #kubernetes #clojure

In the previous articles,我们探索了咬合性的概念,并讨论了如何开始使用XTDB(一个偶然的不变数据库)。现在,让我们深入研究部署XTDB并在生产中运行它的技术细节。这篇博客文章旨在提供宝贵的见解和考虑因素,以在此过程中牢记。

XTDB 1

在我们继续之前,重要的是要注意,本文共享的经验是基于使用XTDB版本1.21。
值得一提的是,您的经历可能会有所不同,尤其是随着XTDB 2.0和随后的版本的引入。

部署XTDB

在生产环境中部署XTDB提供了几种选择,每个选项都有其优点和注意事项:

  • 作为JVM应用程序的一部分运行
  • 单独运行,但在同一家服务器上与您的应用程序一起运行
  • 在独立节点或单独的容器上运行
  • 运行一个XTDB节点群

要实现弹性和可扩展设置,我建议运行一个XTDB节点群,每个节点都部署为由Kubernetes管理的单独的Docker容器。这可以轻松编排,自动缩放和简化XTDB群集的管理。

容器化XTDB

在本节中,我们将探索如何使用Kubernetes群集中的自定义配置的XTDB Application (Clojure Project)构建和运行Docker容器。但是,如果您不需要自定义构建并且可以简单地使用standalone Docker image,则可以跳过此部分。

优步

如果我们想在docker中运行XTDB,将其运行在容器中的最合适的方法是将我们的预配置XT应用程序编译为一个Uberjar -uberjarâ文件。

这可以通过在我们的Clojure项目中使用Uberdeps来完成:

    ; deps.edn
    :aliases
     {:uberdeps {:replace-deps {uberdeps/uberdeps {:mvn/version "1.1.0"}}
                 :replace-paths []
                 :main-opts ["-m" "uberdeps.uberjar"]}}

安装了它后,您可以将项目编译到一个Uberjar文件中:

clj -M:uberdeps

Dockerfile

我们希望我们的XTDB docker映像尽可能轻巧,因此最好的方法是拥有一个多阶段构建图像,该图像在所选的JVM图像上构建并运行Uberjar:

    # Build clojure uberjar
    FROM clojure:openjdk-17-tools-deps-alpine AS BUILD

    WORKDIR /xtdb
    COPY . /xtdb
    RUN apk add --no-cache libstdc++

    RUN clojure -M:uberdeps

    # Copy and run uberjar
    FROM openjdk:17-alpine3.14
    WORKDIR /usr/local/lib/xtdb

    RUN apk --no-cache add bash libstdc++

    COPY --from=BUILD /xtdb/resources /usr/local/lib/xtdb/resources
    COPY --from=BUILD /xtdb/target .

    ENV MALLOC_ARENA_MAX=2
    CMD ["java", "-cp", "xtdb.jar", "clojure.main", "-m", "xtdb.core"]

    EXPOSE 3000

优雅的关闭

在Kubernetes中运行XT的另一个要求是优雅地关闭,例如杀死容器或Kubernetes部署重新启动。为了确保这样做,我们必须通过添加SIGTERM信号处理程序来更改xtdb.clj文件:

    ;; Stop the system on SIGTERM
    (with-handler :term
      (log/info "Caught SIGTERM, quitting")
      (.close @xt-node)
      (log/info "All components shut down")
      (System/exit 0))

验证

由于我们与应用程序容器分开运行XTDB,因此我们可能需要确保对数据库的请求进行正确认证。

为了确保我们需要通过提供JWK(JSON Web令牌键集)作为XTDB节点的环境变量来更改启动XTDB的方式:

    ; core.clj
    (def config
      {:xtdb.http-server/server {:port 3000
                                 :jwks (System/getenv "XTDB_JWKS")} ...

下一步是从您的申请请求发送兼容的JWT令牌:

    {"Authorization", "Bearer #{your_jwt}"}

Kubernetes

当我们决定使用运行的XT节点作为Kubernetes群集中的Docker容器时,我们需要为此准备Kubernetes表现出来。

实现这一目标的最简单方法是创建一个kubernetes状态的多个XT容器:

    ---
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: xtdb
      labels:
        app.kubernetes.io/name: xtdb
    spec:
      serviceName: xtdb-headless
      replicas: 3
      selector:
        matchLabels:
          app.kubernetes.io/name: xtdb
      template:
        metadata:
          labels:
          app.kubernetes.io/name: xtdb
        spec:
          terminationGracePeriodSeconds: 30
          containers:
            - name: xtdb
              image: $REGISTRY/xtdb:1.21.0
              imagePullPolicy: Always
              ports:
                - containerPort: 3000
              livenessProbe:
                tcpSocket:
                  port: 3000
                periodSeconds: 10
                initialDelaySeconds: 120
                timeoutSeconds: 15
              readinessProbe:
                exec:
                  command:
                    - bash
                    # custom readiness check script
                    - scripts/readiness.sh
                initialDelaySeconds: 30
                periodSeconds: 15
              envFrom:
                - secretRef:
                    name: xtdb-secrets

至于配置,您可以使用配置XTDB所需的环境变量创建一个配置毫米,以及为您的XT节点提供秘密的秘密资源。

在生产中运行XT

XTDB是 unbundled数据库,这意味着它具有很多可以交换或更改的组件,并且可能与不同的技术和其他数据库一起使用。

通常,它由3个部分组成:

  1. 交易日志
  2. 文档商店
  3. 索引商店

我们在XTDB设置中使用PostgreSQL和JDBC适配器有经验,并使用Kafka进行交易日志,并使用RockSDB作为索引存储。

但是,还有许多其他方法和模块可以设置数据库 - 您可以在the documentation.

中找到它们

JDBC

交易日志和文档存储在XTDB中被认为是 Golden商店,这意味着它们应该可靠地持久持久,这与可以在NodeArt上从头开始重建的索引存储不同。

XT支持JDBC(Java数据库连接),该JDBC可以连接到PostgreSQL,MySQL,SQLITE等各种SQL数据库。
在我们的示例中,我们正在使用PostgreSQL + JDBC进行事务日志和文档存储。

要将PostgreSQL和JDBC一起使用,您必须在 deps.edn 首先提供这些模块:

:deps {org.postgresql/postgresql {:mvn/version "42.2.18"}
        com.xtdb/xtdb-jdbc {:mvn/version "1.21.0"}
       ...

如果要使用XTDB部署连接到PostgreSQL的环境变量,也可以在 core.clj 文件中传递它们:

    (def db-spec
      {:host (System/getenv "POSTGRES_HOST")
       :port (get (System/getenv) "POSTGRES_PORT" "5432")
       :dbname (System/getenv "POSTGRES_DB")
       :user (System/getenv "POSTGRES_USER")
       :password (System/getenv "POSTGRES_PASSWORD")})

    (def config
      {:xtdb.jdbc/connection-pool {:dialect {:xtdb/module 'xtdb.jdbc.psql/->dialect}
                                   :pool-opts {:maximumPoolSize 10}
                                   :db-spec db-spec}
       :xtdb/tx-log {:xtdb/module 'xtdb.jdbc/->tx-log
                     :connection-pool :xtdb.jdbc/connection-pool}
       :xtdb/document-store {:xtdb/module 'xtdb.jdbc/->document-store
                             :connection-pool :xtdb.jdbc/connection-pool}})

这样,我们也可以重新使用交易和文档存储的相同连接池。

卡夫卡

但是,推荐的选项(以及生产中最常使用)是利用Kafka进行交易商店。

在节点重新启动期间(例如,在新部署中) XTDB必须从零或最新保存的检查点重建交易日志,这意味着阅读PostgreSQL和我们的经验中的整个交易表,此过程比预期的要慢。

kafka似乎更适合交易日志的目的,因为:

  • 基本上是事件的日志
  • 我们只需要使用一个分区和一个主题
  • 交易可以很快消费

因此,对我们来说最佳和最佳性能设置看起来像:

  • 使用kafka作为交易日志
  • 使用JDBC和关系数据库作为文档存储
  • 使用RockSDB作为索引商店

这样,我们可以确保可以快速创建交易,可以快速重新完成日志,并且文档存储的性能足够且具有弹性。

检查点

如果我们想重建查询索引(例如,在节点重新启动时),则XT可能需要重播交易日志,这有时可能不是那么快,尤其是如果您有较长的更改历史记录。

当我们在集群中运行XTDB时,至关重要的是,当XT实例准备服务db请求时,准备时间至关重要。
幸运的是,XTDB有一个解决该问题的解决方案,称为检查点

现在,有三种方法可以坚持本地查询索引状态:

  • 本地文件(使用Java的Nio文件系统)
  • AWS S3
  • GPC的云存储

AWS S3

在我们的情况下,我们决定使用AWS设置,以便为检查点具有集中式且已配置的存储。
但是,它还需要安装一些其他依赖项:

    ; deps.edn
     :deps {com.xtdb/xtdb-s3 {:mvn/version "1.21.0"}
            software.amazon.awssdk/aws-core {:mvn/version "2.10.91"}

也需要在节点配置中的其他设置。

由于我们对S3的一些要求花了很长时间,因此我们还决定建立一个自定义AWS S3 HTTP客户端:

    (defn- make-s3-client
      "Increases timeouts for AWS S3 HTTP calls"
      []
      (let [timeout (Duration/ofSeconds 30)
            http-client-builder (->
                                 (NettyNioAsyncHttpClient/builder)
                                 (.connectionAcquisitionTimeout timeout)
                                 (.connectionTimeout timeout))]
        (-> (S3AsyncClient/builder)
            (.httpClientBuilder http-client-builder)
            .build)))

    (def checkpoint-name (get (System/getenv) "CHECKPOINT_NAME" ""))

    (def checkpoint-config
      ; Checkpoints are not enabled on a local machine where we don't have the env
      (if (str/blank? checkpoint-name)
        {}
        {:xtdb/module 'xtdb.checkpoint/->checkpointer
         :store
         {:xtdb/module 'xtdb.s3.checkpoint/->cp-store
          :bucket checkpoint-name
          :configurator (fn [_] (reify S3Configurator (makeClient [_this] (make-s3-client))))}
         :Keep-dir-on-close? false
         :approx-frequency (Duration/ofHours 2)}))

一旦配置,XT将每2小时将当前的索引状态持续到S3。一个人可能想调整S3的存储措施策略,以便归档或删除过时的检查点文件。

警告

记忆消耗

基于JVM的应用程序倾向于消耗相当大的内存预算 - 在我们的情况下,使用允许的4GB内存运行XT总是不够的,因此我们决定增加Kubernetes中的内存限制最多8和12千兆字节。

我们观察到的另一个考虑因素是,垂直缩放量对于XTDB群集而言,与水平缩放不同,我们需要等到新节点从检查点恢复或处理交易日志。

RocksDB调整

RockSDB被XT用作索引商店,结果,它可能会消耗大量资源。
为了避免内存预算可能的问题,建议在XTDB配置中进行set RocksDB block cache to 1/3 of available memory

准备探针

取决于您用于XTDB部署的技术堆栈,即使启用了检查点功能,也可以花费一些时间 - 即使启动节点能够处理REST API请求,他们也会赢得交易点。处理节点完成消费。

为了避免这种情况,您可能需要检查最后提交和最后完成的交易之间的差异,例如来自bash脚本:

    #!/bin/bash
    # scripts/readiness.sh: A script that compares the latest submitted and indexed transactions

    set -e
    THRESHOLD=1000

    # Assumes that you have jq and curl installed
    submitted_tx=`curl http://localhost:3000/_xtdb/latest-submitted-tx -H "Accept: application/json" -f | jq .txId`
    completed_tx=`curl http://localhost:3000/_xtdb/latest-completed-tx -H "Accept: application/json" -f | jq .txId`
    diff=$[submitted_tx - completed_tx]

    if ((diff > THRESHOLD)); then
        echo "Node is not ready"
        exit 1
    else
        echo "Node is ready"
    fi

负载平衡和XT群集

XTDB节点之间未共享索引存储,因此每个节点可能具有略有不同的数据表示形式。为了确保完整性,我们可能需要使用等待tx 同步 在提交交易时。

但是,当我们在分布式节点中使用REST API时,可能是站在节点前面的负载平衡器将请求随机分配给数据库,而当我们向一个节点提交事务时,我们最终可以从另一个数据中读取数据,这可能还没有处理该交易。

如果我们想防止这种情况,我们可能需要实现粘性会话或在您的应用程序和数据库节点之间使用HTTP2连接。

结论

XTDB包含了一个时代的概念,并提供了以不变的方式处理数据的强大功能。

但是,这也意味着在与XT的旅途中,您可能会面临其未捆绑的数据库概念造成的一些技术挑战,并通过推理所选组件,技术堆栈和含义来解决它们。

XTDB开发中的新里程碑 - 对我们来说非常有前途,因为它具有更灵活和可扩展的体系结构以及一流的SQL支持,并且可以由PostgreSQL客户端。

我们期待尝试新版本,并希望您喜欢我们有关XT的系列文章。

快乐的黑客,请继续关注!

感谢Carsten的评论!