<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Kuznetsov</title>
    <description>The latest articles on DEV Community by Alex Kuznetsov (@ne1ro).</description>
    <link>https://dev.to/ne1ro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F989781%2Fe9d3fa39-82a5-4470-862c-724be4f9dde8.JPG</url>
      <title>DEV Community: Alex Kuznetsov</title>
      <link>https://dev.to/ne1ro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ne1ro"/>
    <language>en</language>
    <item>
      <title>Taming Time: how to run XTDB in production</title>
      <dc:creator>Alex Kuznetsov</dc:creator>
      <pubDate>Wed, 19 Jul 2023 11:13:57 +0000</pubDate>
      <link>https://dev.to/marleyspoon/taming-time-how-to-run-xtdb-in-production-2lgd</link>
      <guid>https://dev.to/marleyspoon/taming-time-how-to-run-xtdb-in-production-2lgd</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/ne1ro/series/22497"&gt;In the previous articles&lt;/a&gt;, we explored the concept of bitemporality and discussed how to get started with XTDB, a bitemporal immutable database. Now, let’s dive into the technical details of deploying XTDB and running it in production. This blog post aims to provide valuable insights and considerations to keep in mind during this process.&lt;/p&gt;

&lt;h1&gt;
  
  
  XTDB 1
&lt;/h1&gt;

&lt;p&gt;Before we proceed, it’s important to note that the experiences shared in this article are based on working with XTDB version 1.21.&lt;br&gt;
It’s worth mentioning that your experience may vary, especially with the introduction of &lt;a href="https://www.xtdb.com/v2"&gt;XTDB 2.0&lt;/a&gt; and subsequent versions.&lt;/p&gt;
&lt;h1&gt;
  
  
  Deploying XTDB
&lt;/h1&gt;

&lt;p&gt;Deploying XTDB in a production environment offers several options, each with its advantages and considerations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  run as a part of your JVM application&lt;/li&gt;
&lt;li&gt;  run separately, but on the same server together with your application&lt;/li&gt;
&lt;li&gt;  run on a standalone node or in a separate container&lt;/li&gt;
&lt;li&gt;  run a cluster of XTDB nodes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve a resilient and scalable setup, I recommend running a cluster of XTDB nodes, with each node deployed as a separate Docker container managed by Kubernetes. This allows easy orchestration, automatic scaling, and simplified management of the XTDB cluster.&lt;/p&gt;
&lt;h2&gt;
  
  
  Containerised XTDB
&lt;/h2&gt;

&lt;p&gt;In this section, we will explore how to build and run a Docker container with a custom configured XTDB application &lt;em&gt;(Clojure project)&lt;/em&gt; in a Kubernetes cluster. However, in case you don’t need to have a custom build and can simply use a &lt;a href="https://hub.docker.com/r/juxt/xtdb-standalone-rocksdb"&gt;standalone Docker image&lt;/a&gt;, you can skip right to the "Authentication" section.&lt;/p&gt;
&lt;h3&gt;
  
  
  Uberjar
&lt;/h3&gt;

&lt;p&gt;If we want to run the XTDB in Docker the most fitting way to run it in a container would be to compile our preconfigured XT application into a single “uberjar” file.&lt;/p&gt;

&lt;p&gt;This can be done by employing &lt;a href="https://github.com/tonsky/uberdeps"&gt;Uberdeps&lt;/a&gt; in our Clojure project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;; deps.edn&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="no"&gt;:aliases&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:uberdeps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:replace-deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uberdeps/uberdeps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.1.0"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="no"&gt;:replace-paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="no"&gt;:main-opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"-m"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"uberdeps.uberjar"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have it installed, you can compile your project into a single Uberjar file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clj -M:uberdeps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;

&lt;p&gt;We want our XTDB Docker image to be as lightweight as possible, so the best approach would be to have a multi-stage build image that builds and runs the Uberjar on the selected JVM image:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;    &lt;span class="c"&gt;# Build clojure uberjar&lt;/span&gt;
    FROM clojure:openjdk-17-tools-deps-alpine AS BUILD

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

    RUN clojure -M:uberdeps

    &lt;span class="c"&gt;# Copy and run uberjar&lt;/span&gt;
    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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Graceful shutdown
&lt;/h3&gt;

&lt;p&gt;Another requirement for running XT in Kubernetes is to gracefully shut down, e.g., on killing containers or Kubernetes deployment restart. To ensure that, we have to change our &lt;code&gt;xtdb.clj&lt;/code&gt; file by adding a &lt;code&gt;SIGTERM&lt;/code&gt; signal handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;    ;; 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))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;Since we run our XTDB separately from our application containers, we might want to ensure that the requests from services to the database are properly authenticated.&lt;/p&gt;

&lt;p&gt;In order to ensure that we need to change the way we start XTDB by providing JWKS &lt;a href="https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets"&gt;(JSON Web Token key set)&lt;/a&gt; as an environment variable to the XTDB node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;; core.clj&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb.http-server/server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="no"&gt;:jwks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"XTDB_JWKS"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to send the compatible JWT token from your application requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bearer &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;your_jwt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Kubernetes
&lt;/h2&gt;

&lt;p&gt;As we’ve decided to go with the running XT nodes as Docker containers in a Kubernetes cluster, we need to prepare Kubernetes manifests for that purpose.&lt;/p&gt;

&lt;p&gt;The simplest way to achieve that is to create a Kubernetes stateful set of multiple XT containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="s"&gt;---&lt;/span&gt;
    &lt;span class="s"&gt;apiVersion&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
    &lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StatefulSet&lt;/span&gt;
    &lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb-headless&lt;/span&gt;
      &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb&lt;/span&gt;
        &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;terminationGracePeriodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
          &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb&lt;/span&gt;
              &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$REGISTRY/xtdb:1.21.0&lt;/span&gt;
              &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Always&lt;/span&gt;
              &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
              &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;tcpSocket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
                &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
                &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;
                &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
              &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
                    &lt;span class="c1"&gt;# custom readiness check script&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;scripts/readiness.sh&lt;/span&gt;
                &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
                &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
              &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xtdb-secrets&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As for the configuration, you can create a configmap with environment variables required for configuring XTDB and a secrets resource for providing secrets to your XT nodes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Running XT in production
&lt;/h1&gt;

&lt;p&gt;XTDB is an &lt;em&gt;unbundled database&lt;/em&gt; which means that it has a lot of components that can be swapped or changed — and it might work with different technologies and other databases.&lt;/p&gt;

&lt;p&gt;In general, it consists of 3 parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Transaction log&lt;/li&gt;
&lt;li&gt; Document store&lt;/li&gt;
&lt;li&gt; Index store&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We had an experience using PostgreSQL and JDBC adapter for our XTDB setup, as well as experimenting with Kafka for transaction log — and using RocksDB as an index storage.&lt;/p&gt;

&lt;p&gt;However, there are many more other ways and modules to setup the database — you can find them in &lt;a href="https://docs.xtdb.com/administration/configuring/"&gt;the documentation.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  JDBC
&lt;/h2&gt;

&lt;p&gt;The transaction log and document store are considered to be &lt;strong&gt;golden stores&lt;/strong&gt; in XTDB — which means that they should be reliably persisted, unlike the index storage that can be rebuilt from scratch on node restart.&lt;/p&gt;

&lt;p&gt;XT supports JDBC (Java Database Connectivity) which allows us to connect to various SQL databases like PostgreSQL, MySQL, SQLite, and others.&lt;br&gt;
In our example, we were using PostgreSQL + JDBC for both transaction log and document store.&lt;/p&gt;

&lt;p&gt;To use PostgreSQL and JDBC together, you have to provide these modules in your &lt;em&gt;deps.edn&lt;/em&gt; first:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:deps {org.postgresql/postgresql {:mvn/version "42.2.18"}
        com.xtdb/xtdb-jdbc {:mvn/version "1.21.0"}
       ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;If you want to use environment variables for connecting to PostgreSQL from the XTDB deployment you can also pass them in &lt;strong&gt;core.clj&lt;/strong&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-spec&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_HOST"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_PORT"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"5432"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:dbname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_DB"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_USER"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:dialect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc.psql/-&amp;gt;dialect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                   &lt;/span&gt;&lt;span class="no"&gt;:pool-opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:maximumPoolSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                   &lt;/span&gt;&lt;span class="no"&gt;:db-spec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-spec&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb/tx-log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc/-&amp;gt;tx-log&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb/document-store&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc/-&amp;gt;document-store&lt;/span&gt;&lt;span class="w"&gt;
                             &lt;/span&gt;&lt;span class="no"&gt;:connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way we can also re-use the same connection pool for both transaction and document store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kafka
&lt;/h2&gt;

&lt;p&gt;However, the recommended option (and also most often used in production) is to leverage Kafka for the transaction store.&lt;/p&gt;

&lt;p&gt;During node restarts &lt;em&gt;(e.g. on new deployments)&lt;/em&gt; XTDB has to rebuild the transaction log from zero or the latest saved checkpoint, which means reading the whole transaction table in PostgreSQL — and in our experience, this process was slower than expected.&lt;/p&gt;

&lt;p&gt;Kafka seems to be a better fit for the very purpose of the transaction log because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  it’s basically a log of events&lt;/li&gt;
&lt;li&gt;  we only need to use just one partition and one topic&lt;/li&gt;
&lt;li&gt;  the transactions can be consumed very quickly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus, the most optimal and performant setup for us looked like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  using Kafka as a transaction log&lt;/li&gt;
&lt;li&gt;  using JDBC and relational database as a document store&lt;/li&gt;
&lt;li&gt;  using RocksDB as an index store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That way, we can ensure that the transactions can be created quickly, the log can be re-consumed fast, and the document storage is performant enough and resilient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checkpoints
&lt;/h2&gt;

&lt;p&gt;If we want to rebuild the query indices (e.g. on node restart), XT might need to replay the transaction log — which sometimes might be not so fast especially if you have a long history of changes.&lt;/p&gt;

&lt;p&gt;As we run our XTDB in a cluster, it’s vital that the readiness time — when the XT instance is ready to serve DB requests — is as low as possible.&lt;br&gt;
Fortunately, XTDB has a solution for that problem called &lt;strong&gt;checkpoints&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Right now, there are three ways to persist the local query indices state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  local files (using Java’s NIO file system)&lt;/li&gt;
&lt;li&gt;  AWS S3&lt;/li&gt;
&lt;li&gt;  GPC’s cloud storage&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  AWS S3
&lt;/h3&gt;

&lt;p&gt;In our case, we’ve decided to go with the AWS setup in order to have a centralised and already configured storage for the checkpoints.&lt;br&gt;
However, it also requires some additional dependencies to be installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;; deps.edn&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-s3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;software.amazon.awssdk/aws-core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"2.10.91"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional setup in the node configuration is also required.&lt;/p&gt;

&lt;p&gt;As we’ve experienced some requests to S3 taking a long time, we’ve also decided to build a custom AWS S3 HTTP client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make-s3-client&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s"&gt;"Increases timeouts for AWS S3 HTTP calls"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Duration/ofSeconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;http-client-builder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;NettyNioAsyncHttpClient/builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.connectionAcquisitionTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.connectionTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;S3AsyncClient/builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;.httpClientBuilder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;http-client-builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"CHECKPOINT_NAME"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint-config&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;; Checkpoints are not enabled on a local machine where we don't have the env&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str/blank?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint-name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.checkpoint/-&amp;gt;checkpointer&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="no"&gt;:store&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.s3.checkpoint/-&amp;gt;cp-store&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="no"&gt;:bucket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint-name&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="no"&gt;:configurator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;S3Configurator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;makeClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_this&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;make-s3-client&lt;/span&gt;&lt;span class="p"&gt;))))}&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="no"&gt;:Keep-dir-on-close?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="no"&gt;:approx-frequency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Duration/ofHours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once configured, XT will persist the current index state to S3 every 2 hours. One might want to adjust S3’s bucket policy so it archives or removes the obsolete checkpoints files.&lt;/p&gt;

&lt;h1&gt;
  
  
  Caveats
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Memory consumption
&lt;/h2&gt;

&lt;p&gt;JVM-based applications tend to consume quite a significant amount of memory budget — in our case, running XT with allowed 4GB of memory wasn’t always enough so we’ve decided to increase the memory limits in Kubernetes up to 8 gigabytes.&lt;/p&gt;

&lt;p&gt;Another consideration we’ve observed is that the vertical scaling works better for an XTDB cluster — unlike the horizontal scaling, where we need to wait until the new node restores from the checkpoints or processes the transactions log.&lt;/p&gt;

&lt;h2&gt;
  
  
  RocksDB tuning
&lt;/h2&gt;

&lt;p&gt;RocksDB is being used by XT as an index store, and as the result, it might consume quite a significant amount of resources.&lt;br&gt;
In order to avoid possible issues with the memory budgeting, it’s recommended to &lt;a href="https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#block-cache-size"&gt;set RocksDB block cache to 1/3 of available memory&lt;/a&gt; which can be done in XTDB configuration.&lt;/p&gt;
&lt;h2&gt;
  
  
  Readiness probes
&lt;/h2&gt;

&lt;p&gt;Depending on your technology stack that you use for XTDB deployment, consuming the transaction log even with the checkpoints feature enabled can take some time — and even though the starting node is able to handle REST API requests, they won’t be processed until the node finishes the consumption.&lt;/p&gt;

&lt;p&gt;To avoid that, you might need to check the difference between the last submitted and last completed transactions, e.g. from a bash script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
    &lt;span class="c"&gt;# scripts/readiness.sh: A script that compares the latest submitted and indexed transactions&lt;/span&gt;

    &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
    &lt;span class="nv"&gt;THRESHOLD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1000

    &lt;span class="c"&gt;# Assumes that you have jq and curl installed&lt;/span&gt;
    &lt;span class="nv"&gt;submitted_tx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;curl http://localhost:3000/_xtdb/latest-submitted-tx &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; | jq .txId&lt;span class="sb"&gt;`&lt;/span&gt;
    &lt;span class="nv"&gt;completed_tx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;curl http://localhost:3000/_xtdb/latest-completed-tx &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; | jq .txId&lt;span class="sb"&gt;`&lt;/span&gt;
    &lt;span class="nv"&gt;diff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$[&lt;/span&gt;submitted_tx - completed_tx]

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;diff &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; THRESHOLD&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Node is not ready"&lt;/span&gt;
        &lt;span class="nb"&gt;exit &lt;/span&gt;1
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Node is ready"&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Load balancing and XT cluster
&lt;/h2&gt;

&lt;p&gt;The index storage is not shared between XTDB nodes — so every node might have a slightly different data representation. To ensure integrity, we might need to use &lt;em&gt;await-tx&lt;/em&gt; or &lt;em&gt;sync&lt;/em&gt; functions whenever we submit a transaction.&lt;/p&gt;

&lt;p&gt;However, when we use REST API in a distributed cluster of nodes, it might be that the load balancer that stands in front of the nodes distributes requests to the database randomly — and when we submit a transaction to one node, we can end up reading data from another, which might have not processed that transaction yet.&lt;/p&gt;

&lt;p&gt;If we want to prevent such situation, we might need to implement sticky sessions or use &lt;a href="https://dev.to/marleyspoon/taming-the-time-how-to-install-develop-with-xtdb-2lbf#http2"&gt;HTTP2&lt;/a&gt; connections between your applications and database nodes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;XTDB embraces the bitemporality concept and provides powerful capabilities of handling your data in an immutable way.&lt;/p&gt;

&lt;p&gt;However, this also implies that during your journey with XT, you might face some technical challenges caused by its unbundled database concept — and resolve them by reasoning about the selected components, technology stack, and implications.&lt;/p&gt;

&lt;p&gt;The new milestone in XTDB’s development — &lt;a href="https://www.xtdb.com/v2"&gt;XTDB 2.0&lt;/a&gt; — looks very promising for us as it has a more flexible and scalable architecture as well as the first-class SQL support — and can be used by PostgreSQL clients.&lt;/p&gt;

&lt;p&gt;We look forward to trying out the new version and hope that you’ve enjoyed our series of articles about XT.&lt;/p&gt;

&lt;p&gt;Happy hacking, and stay tuned!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks &lt;a href="https://dev.to/carpmeister"&gt;Carsten&lt;/a&gt; and &lt;a href="https://dev.to/adamniedzielski"&gt;Adam&lt;/a&gt; for the reviews!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>clojure</category>
      <category>docker</category>
      <category>kubernetes</category>
      <category>database</category>
    </item>
    <item>
      <title>Taming Time: how to install &amp; develop with XTDB</title>
      <dc:creator>Alex Kuznetsov</dc:creator>
      <pubDate>Wed, 05 Apr 2023 14:43:22 +0000</pubDate>
      <link>https://dev.to/marleyspoon/taming-the-time-how-to-install-develop-with-xtdb-2lbf</link>
      <guid>https://dev.to/marleyspoon/taming-the-time-how-to-install-develop-with-xtdb-2lbf</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/marleyspoon/bitemporality-or-how-to-change-the-past-3k4f"&gt;previous article&lt;/a&gt;, we discussed the concept of bitemporality and how it can be used to solve complex architectural problems.&lt;/p&gt;

&lt;p&gt;At MarleySpoon, we’ve used XTDB (or ’XT’ for short) for our new order management system &lt;em&gt;(OMS)&lt;/em&gt;, and discovered a lot of interesting insights about the database itself, the concept of bitemporality, and how developing a project using an immutable, bitemporal database could look like.&lt;/p&gt;

&lt;p&gt;In this article, we will be focusing primarily on our development experience (installing, testing with XTDB) from an Elixir application, and we cover more details about deploying, running XT in cluster, and tuning in production in the upcoming article.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is XTDB
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://xtdb.com/"&gt;XTDB&lt;/a&gt;, or Cross-Time Database, is a distributed and transactional database system designed to handle complex and changing data with ease.&lt;br&gt;
It is based on a bitemporal model, which allows for the tracking of both the valid time and transaction time of data, enabling powerful and flexible querying capabilities.&lt;br&gt;
With XTDB, developers can work with immutable data structures, which simplifies development and improves reliability.&lt;br&gt;
Its graph query language, Datalog, provides a powerful and expressive way to navigate relationships within the data.&lt;/p&gt;

&lt;p&gt;As we’ve illustrated before, XT has a lot of benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Bitemporal&lt;/li&gt;
&lt;li&gt;  Supports retroactive corrections&lt;/li&gt;
&lt;li&gt;  Document and graph-based&lt;/li&gt;
&lt;li&gt;  Flexible data schema&lt;/li&gt;
&lt;li&gt;  Unbundled (can be deployed on top of a lot of other DBs and persistence solutions)&lt;/li&gt;
&lt;li&gt;  Can be used within JVM or through REST API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As one can see, XT is quite different from most of the widely used SQL and NoSQL databases - and while it provides great benefits for dealing with immutable data and retroactive corrections, it also requires an understanding of some of its implementation principles.&lt;/p&gt;
&lt;h1&gt;
  
  
  How we planned to use XTDB
&lt;/h1&gt;

&lt;p&gt;At &lt;a href="//marleyspoon.com"&gt;MarleySpoon&lt;/a&gt;, we ship boxes with recipes and ingredients to our customers. The &lt;strong&gt;orders&lt;/strong&gt; and our subscription model are the backbone of the whole bussiness logic and that is also reflected in how we build our software.&lt;/p&gt;

&lt;p&gt;The core of the orders system is the orders state machine, and although the states are essentially simple, there might still be cases where we could have discrepancies - and in such cases, we would like to have more options to debug or restore orders to a previous state, as well as &lt;em&gt;retroactively&lt;/em&gt; correct their data and push the change to dependent subsystems.&lt;/p&gt;

&lt;p&gt;The shift from the legacy monolith architecture to service-oriented architecture coupled with the introduction of new OMS also required us to be more careful when it comes to eventual consistency - the &lt;strong&gt;transaction&lt;/strong&gt; and &lt;strong&gt;valid&lt;/strong&gt; times can be different in the resulting systems so we can’t just work around it by using the persistence stack we’ve used to (e.g. relational DBs with updates in place).&lt;/p&gt;

&lt;p&gt;Initially, we considered adding transaction and valid time columns to PostgreSQL to implement bitemporality, as this seemed like a straightforward solution. However, upon further analysis, we realized that this approach would introduce significant complexity to the system design. In particular, any foreign keys would need to take the bitemporal columns into account, meaning that queries would need to consider both the entity relationship and its temporal context. This would require significant changes to the database schema, query design, and application code, and would likely lead to a higher risk of errors and data inconsistencies.&lt;/p&gt;

&lt;p&gt;We also considered event sourcing for our needs, but it would add significant incidental complexity, requiring changes to other services’ architectures and a significant amount of application-level code changes to ensure that all events were captured and persisted correctly.&lt;/p&gt;

&lt;p&gt;After considering various approaches to implementing bitemporality in our system, we decided to give XTDB a try due to its native support for bitemporality and graph database capabilities. We designed our data model around XTDB’s capabilities and incorporated the database into our Elixir application.&lt;/p&gt;
&lt;h1&gt;
  
  
  Installing XTDB
&lt;/h1&gt;

&lt;p&gt;There are multiple ways to install and use XTDB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Use it as a JVM dependency by simply adding it to your JVM project:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;; deps.edn&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;  Using a pre-built XTDB JAR on a local machine&lt;/li&gt;
&lt;li&gt;  Through a &lt;a href="https://hub.docker.com/r/juxt/xtdb-in-memory"&gt;Docker image&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;XTDB is developed in the Clojure programming language and it is very convenient to run it from any Clojure program - so we’ve decided to write a simple app in Clojure that would run XTDB for us and provide all required setup.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installing dependencies
&lt;/h2&gt;

&lt;p&gt;We’ve installed Clojure using &lt;span&gt;asdf&lt;/span&gt; version manager as it’s very convenient to pin JVM and Clojure versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="c"&gt;# .tool-versions&lt;/span&gt;
    openjdk-18
    clojure 1.11.0.1100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step was creating a new Clojure app using &lt;a href="https://clojure.org/guides/deps_and_cli"&gt;deps CLI&lt;/a&gt; - all of the necessary dependencies were provided in a single file &lt;em&gt;(deps.edn)&lt;/em&gt;.&lt;br&gt;
XTDB is very modular so we have to install PostgreSQL support, HTTP client and server, metrics, and other tools as separate packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;;; deps.edn&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="no"&gt;:deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;org.clojure/clojure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.11.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="c1"&gt;;; Persistence&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;org.postgresql/postgresql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"42.2.18"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-jdbc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-rocksdb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="c1"&gt;;; HTTP Client&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-http-client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="c1"&gt;;; HTTP Server&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;com.xtdb/xtdb-http-server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Application entry point &amp;amp; configuration
&lt;/h2&gt;

&lt;p&gt;One of the primary reasons we developed a wrapper for XTDB was to enable us to run an XTDB cluster in a Kubernetes environment. We wanted to simplify the setup process by allowing configuration through environment variables, rather than relying on external configuration files. This allowed us to easily manage XTDB’s configuration within Kubernetes and provided us with greater flexibility in managing our XTDB cluster.&lt;/p&gt;

&lt;p&gt;We’ve created a &lt;span&gt;xtdb.clj&lt;/span&gt; file that is the entry point to the database wrapper and which also has all the required configuration there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-spec&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_HOST"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_PORT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:dbname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_DB"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_USER"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"POSTGRES_PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb.http-server/server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="no"&gt;:jwks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"XTDB_JWKS"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; auth&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb.rocksdb/block-cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.rocksdb/-&amp;gt;lru-block-cache&lt;/span&gt;&lt;span class="w"&gt;
                                  &lt;/span&gt;&lt;span class="no"&gt;:cache-size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; RocksDB cache size&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb/index-store&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:kv-store&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.rocksdb/-&amp;gt;kv-store&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="no"&gt;:db-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/tmp/xtdb/indexes"&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="no"&gt;:checkpointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkpoint-config&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="no"&gt;:block-cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:xtdb.rocksdb/block-cache&lt;/span&gt;&lt;span class="w"&gt;
                                     &lt;/span&gt;&lt;span class="no"&gt;:metrics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.rocksdb.metrics/-&amp;gt;metrics&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:dialect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc.psql/-&amp;gt;dialect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                   &lt;/span&gt;&lt;span class="no"&gt;:pool-opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:maximumPoolSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                   &lt;/span&gt;&lt;span class="no"&gt;:db-spec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db-spec&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb/tx-log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc/-&amp;gt;tx-log&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="no"&gt;:connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="no"&gt;:xtdb/document-store&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xtdb/module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.jdbc/-&amp;gt;document-store&lt;/span&gt;&lt;span class="w"&gt;
                             &lt;/span&gt;&lt;span class="no"&gt;:connection-pool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:xtdb.jdbc/connection-pool&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the initial configuration is done we can provide a simple entry point function that will start an XT node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;;; XT node - hydrated on start&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;xt-node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;-main&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s"&gt;"Starts a new XTDB node"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;xtdb/start-node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Started a new XT node ..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;seed/seed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; seed data that we need on start&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;xtdb/sync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;log/info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Loaded data into a new XT node"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reset!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;xt-node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; set xt-node with the started node&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nb"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c1"&gt;;; Can be used to run REPL or local XT instance&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;;; As running it from CLI can assume passing some command line arguments&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;;; we should accept a list of optional arguments as a function param&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;;; Runs -main function to start a new XT node&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-main&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Running XT on a local machine
&lt;/h1&gt;

&lt;p&gt;Once our XT app is installed and configured, we can run &lt;code&gt;clj -X xtdb.core/start&lt;/code&gt; in order to start it on a local machine.&lt;br&gt;
This will enable web UI and REST API on &lt;span&gt;&lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Connecting from REPL
&lt;/h2&gt;

&lt;p&gt;To run XT in REPL we can instead just execute &lt;span&gt;clj&lt;/span&gt; in shell, given that we are in the root directory of the Clojure project.&lt;/p&gt;

&lt;p&gt;That will start a new Clojure REPL, and if we want to start XT from there, it’s sufficient to use functions we’ve implemented beforehand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;in-ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'xtdb.core&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; switches current namespace to XT wrapper's core namespace&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connecting to a remote node
&lt;/h2&gt;

&lt;p&gt;Another important benefit of XT that we unfortunately didn’t explore enough is that by using the &lt;span&gt;http-client&lt;/span&gt; dependency, we’re able to connect to any remote node that is accessible to us by HTTP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remote-node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;xt/new-api-client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remote-url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;xt/submit-tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remote-node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="no"&gt;::xt/put&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:xt/id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:bar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:bar&lt;/span&gt;&lt;span class="p"&gt;}]])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; submits a transaction on the remote node&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus, we can connect from REPL to any production XT instance and run queries / submit transactions there.&lt;/p&gt;

&lt;h1&gt;
  
  
  REST API
&lt;/h1&gt;

&lt;p&gt;XT is very convenient to use from any Clojure or JVM-based application, however, for clients implemented in other programming languages or different virtual machines, we should be using&lt;a href="https://xtdb.com/docs/"&gt; XT’s REST API&lt;/a&gt;.&lt;br&gt;
XT has a very rich HTTP API that covers most of its functionality &lt;em&gt;(although some of it is only available by Clojure/Java client)&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  API formats
&lt;/h2&gt;

&lt;p&gt;XT supports multiple formats: &lt;em&gt;application/edn, application/json&lt;/em&gt; and &lt;em&gt;application/transit+json&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As XT is written in Clojure and it natively supports Clojure’s data types, we were not satisfied with available JSON types and decided to give &lt;a href="https://github.com/edn-format/edn"&gt;EDN&lt;/a&gt; a try - that way we would have way more supported types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  symbols, e.g. Elixir atoms&lt;/li&gt;
&lt;li&gt;  decimals&lt;/li&gt;
&lt;li&gt;  dates and timestamps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, we had some issues with encoding Elixir/BEAM VM terms to EDN and, in general, the performance of the format - so the &lt;a href="https://github.com/cognitect/transit-format"&gt;Transit/JSON&lt;/a&gt; would be an improvement as apart from being compatible with regular JSON and essentially more performant it also has a more precise types conversion.&lt;/p&gt;
&lt;h2&gt;
  
  
  Main endpoints
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;span&gt;GET /_xtdb/status&lt;/span&gt; - can be used as a health check for an XT node&lt;/li&gt;
&lt;li&gt;  &lt;span&gt;GET /_xtdb/entity&lt;/span&gt; - gets a single entity from the database&lt;/li&gt;
&lt;li&gt;  &lt;span&gt;GET /_xtdb/query&lt;/span&gt; - performs a single Datalog query&lt;/li&gt;
&lt;li&gt;  &lt;span&gt;POST /_xtdb/submit-tx&lt;/span&gt; - submits database transactions&lt;/li&gt;
&lt;li&gt;  &lt;span&gt;GET /_xtdb/await-tx&lt;/span&gt; - waits until the transaction was indexed on the node&lt;/li&gt;
&lt;li&gt;  &lt;span&gt;GET /_xtdb/tx-committed&lt;/span&gt; - checks if the transaction was successfully committed&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Elixir HTTP client
&lt;/h1&gt;

&lt;p&gt;As at that point in time when we started applying XT at MarleySpoon there were no Elixir libraries fully supporting XT’s REST API and covering our needs so we’ve started writing our own HTTP adapter.&lt;/p&gt;
&lt;h3&gt;
  
  
  Umbrella application
&lt;/h3&gt;

&lt;p&gt;As OMS was the only project where we were trying XTDB, we’ve decided to move all the Clojure code, as well as the new Elixir HTTP into the same umbrella app:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/
  ...
  xtdb/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;That way we could also easily start an XTDB node right in our project and use it from the Elixir application, e.g. by starting through docker-compose.&lt;/p&gt;
&lt;h3&gt;
  
  
  HTTP2
&lt;/h3&gt;

&lt;p&gt;XT’s REST API supports the second revision of HTTP format out of the box - which means that we can have a stable and more performant connection between XT clients (application servers) and the database instances.&lt;/p&gt;

&lt;p&gt;When running an XTDB cluster in production, HTTP2 can be particularly useful. By using HTTP2, direct connections can be established between the client application and an XTDB node, rather than relying on load balancing across multiple instances. Since XTDB doesn’t enforce an equal state between nodes, the same request could yield different results on different nodes - but using HTTP2 eliminates the issue and ensures consistent results for each request.&lt;/p&gt;

&lt;p&gt;However, not every Elixir’s HTTP client supports sending requests using HTTP2 - so we have to search for another option rather than using HTTPoison that we widely use in other projects.&lt;br&gt;
We’ve decided to go with &lt;a href="https://github.com/sneako/finch"&gt;Finch&lt;/a&gt;, as apart from supporting HTTP2 it also focuses on performance and provides telemetry support out of the box - which we’ve found very useful for tracing and debugging purposes.&lt;/p&gt;

&lt;p&gt;Using HTTP2 with Finch requires some initial configuration as we have to have just one connection in a connection pool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# apps/xtdb/lib/application.ex&lt;/span&gt;
    &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="no"&gt;Finch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;Xtdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ConnectionPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;pools:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
              &lt;span class="ss"&gt;default:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;count:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;protocol:&lt;/span&gt; &lt;span class="ss"&gt;:http2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;strategy:&lt;/span&gt; &lt;span class="ss"&gt;:one_for_one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;Xtdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Telemetry &amp;amp; OpenTelemetry
&lt;/h3&gt;

&lt;p&gt;As was mentioned in a previous section, using Finch as an HTTP Client library is a great step to have a seamless telemetry integration.&lt;/p&gt;

&lt;p&gt;Finch provides the next Telemetry events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  request start/stop&lt;/li&gt;
&lt;li&gt;  request exception&lt;/li&gt;
&lt;li&gt;  queue start/stop/exception&lt;/li&gt;
&lt;li&gt;  connection start/stop/exception&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and others, but for our purpose, we will be more interested only in the first two events.&lt;/p&gt;

&lt;p&gt;In order to integrate the XTDB client with OpenTelemetry we wrote a simple module that watches Finch’s telemetry events and pushes them to the OpenTelemetry collector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Checking for optional opentelemetry dependency&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Code&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ensure_compiled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Xtdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;OpenTelemetry&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="nv"&gt;@tracer_id&lt;/span&gt; &lt;span class="bp"&gt;__MODULE__&lt;/span&gt;

          &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;
          &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="n"&gt;attach_http_request_start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;attach_http_request_stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="ss"&gt;:ok&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;

          &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;attach_http_request_start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="ss"&gt;:telemetry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:http_request_start&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;@http_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:send&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:start&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
              &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_http_request_start&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;%{}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;

          &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;attach_http_request_stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="ss"&gt;:telemetry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:http_request_stop&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;@http_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:stop&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
              &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_http_request_stop&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;%{}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;

          &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_http_request_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;_measurements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;request:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;_config&lt;/span&gt;
              &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="o"&gt;...&lt;/span&gt;
            &lt;span class="no"&gt;Otel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_telemetry_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@tracer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"XT &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;request_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{},&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
              &lt;span class="ss"&gt;kind:&lt;/span&gt; &lt;span class="ss"&gt;:internal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="ss"&gt;attributes:&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;

          &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_http_request_stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;_measurements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;request:&lt;/span&gt; &lt;span class="n"&gt;_request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="no"&gt;ConnectionPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;result:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;status:&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
                &lt;span class="n"&gt;_config&lt;/span&gt;
              &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Otel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_current_telemetry_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@tracer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{})&lt;/span&gt;
            &lt;span class="no"&gt;Span&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:"http.status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;...&lt;/span&gt;
            &lt;span class="no"&gt;OpenTelemetry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_telemetry_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@tracer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{})&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Encoding EDN
&lt;/h3&gt;

&lt;p&gt;In Elixir are using &lt;a href="https://github.com/jfacorro/Eden"&gt;Eden&lt;/a&gt; library in order to decode and encode data from and to EDN format. In most cases it works without any issues, however, for decimals we had to implement protocol support for Elixir &lt;em&gt;Decimal&lt;/em&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Implements Eden.Encode protocol for Decimal structs&lt;/span&gt;
    &lt;span class="k"&gt;defimpl&lt;/span&gt; &lt;span class="no"&gt;Eden&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;for:&lt;/span&gt; &lt;span class="no"&gt;Decimal&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="c1"&gt;# Adds a decimal digit number so it can be picked by EDN&lt;/span&gt;
      &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="o"&gt;.&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"M"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way you can also implement support of any other custom type or struct that you might need to persist in XT.&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing XTDB
&lt;/h1&gt;

&lt;p&gt;Because XTDB is an immutable database, it’s not so simple to delete data from it. This can have implications when it comes to testing, as traditional methods of clearing and resetting a database may not be effective. To integrate XT into a test suite, the most straightforward approach is to run an in-memory node alongside the suite. This allows for more granular control over data, as the in-memory node can be reset or recreated as needed.&lt;/p&gt;

&lt;p&gt;We’ve achieved that by using the in-memory XTDB Docker image in our docker-compose setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Wait for Docker container to be ready&lt;/span&gt;
    &lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dokku/wait&lt;/span&gt;

    &lt;span class="na"&gt;xtdb_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;juxt/xtdb-in-memory:1.21.0&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;3000:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and also created a simple shell script that we use in order to run the integration test suite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
    docker-compose up &lt;span class="nt"&gt;--remove-orphans&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; xtdb_test
    docker-compose run &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; xtdb_test:3000
    docker-compose run mix_test
    docker-compose stop xtdb_test mix_test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the case of unit tests, as any request coming through the XTDB client is basically an HTTP request, we could also use VCR cassettes as mocks in order to avoid sending real requests to the test instances.&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s next?
&lt;/h1&gt;

&lt;p&gt;In this article, we’ve discussed how XTDB can be used alongside applications written in Elixir and demonstrated how to implement a simple HTTP client for working with the database. We also covered how to develop and test applications using XTDB, and the importance of running an in-memory node for testing purposes.&lt;/p&gt;

&lt;p&gt;In the third and final part of this series, we’ll be sharing how we used Docker and docker-compose to set up a local development environment for XTDB, as well as how we deployed and ran it in production. We’ll also be discussing caveats and issues we encountered, and how we addressed them.&lt;/p&gt;

&lt;p&gt;We can also recommend some more reading on the topic if you’re interested in developing with XTDB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://xtdb.com/docs/"&gt;https://xtdb.com/docs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://xtdb.com/blog/xtdb-command-line/"&gt;https://xtdb.com/blog/xtdb-command-line/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy Hacking and stay tuned!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks &lt;a href="https://dev.to/carpmeister"&gt;Carsten&lt;/a&gt; for the review!&lt;/em&gt;&lt;br&gt;
&lt;small&gt;Cover image credit: DALL·E 2.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>bitemporality</category>
      <category>elixir</category>
      <category>clojure</category>
      <category>database</category>
    </item>
    <item>
      <title>Bitemporality, or how to change the past</title>
      <dc:creator>Alex Kuznetsov</dc:creator>
      <pubDate>Fri, 06 Jan 2023 08:34:00 +0000</pubDate>
      <link>https://dev.to/marleyspoon/bitemporality-or-how-to-change-the-past-3k4f</link>
      <guid>https://dev.to/marleyspoon/bitemporality-or-how-to-change-the-past-3k4f</guid>
      <description>&lt;p&gt;We can definitely see the whole history of humanity as a chain of &lt;em&gt;events&lt;/em&gt;:&lt;br&gt;
tiny events, big events, huge events, crucial events – some of them were negligible and some, on the contrary, predefined the next link in the chain and changed the way our world looks now.&lt;/p&gt;

&lt;p&gt;However, the more details we dig out about our history, the more white spots we erase, the more surprising and even contradictious things we see – things that sometimes can completely refute our views and assumptions. Things that we thought and believed to be &lt;strong&gt;facts&lt;/strong&gt; can be invalidated – and we will need to &lt;strong&gt;alter the past&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every kind of record system is not perfect and can contain errors. But what if we wanted to build a system that is resilient to such errors? What if we wanted to change the past but keep the history as a track of facts?&lt;/p&gt;
&lt;h1&gt;
  
  
  Updates in place
&lt;/h1&gt;

&lt;p&gt;Throughout almost all the existence of computers and computer science, resources were the biggest limitation and one of the most complex issues.&lt;/p&gt;

&lt;p&gt;Our progress allowed us to switch from bytes to kilobytes, from kilobytes to mega and gigabytes. Modern PCs, smartphones, and cloud systems can easily handle virtually unlimited amounts of data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The storage&lt;/strong&gt;, which was physically enormous at the beginning of the computer era and started with devices taking over the halls and rooms, moved to CDs, floppy disks and hard drives – so we could even carry movies, tons of photos and music in our pockets. The storage, which always seemed to be the problem, became the smallest of our headaches when it came to building software.&lt;/p&gt;

&lt;p&gt;Nonetheless, the habit &lt;em&gt;(the bad one)&lt;/em&gt; we inherited from the dawn of the computer era is still there and arguably is one of the biggest developer’s headaches – &lt;strong&gt;mutability&lt;/strong&gt;. Before the progress in the storage capacity, we were very careful with how we consumed the RAM and disk space, and that forced us to mutate variables and data instead of persisting them in the immutable way, i.e. saving a new record instead of updating an existing one.&lt;/p&gt;

&lt;p&gt;That habit is still haunting us, even though functional programming languages and &lt;a href="https://en.wikipedia.org/wiki/Immutable_object"&gt;the immutability approach&lt;/a&gt; are on the rise, we still &lt;em&gt;(mostly)&lt;/em&gt; update our records in place – as that is the way most popular databases are built.&lt;/p&gt;
&lt;h2&gt;
  
  
  An audit system
&lt;/h2&gt;

&lt;p&gt;How can we design a record tracking system using a common mutable DB? Let’s say we use PostgreSQL and define a simple &lt;code&gt;audits&lt;/code&gt; table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;


&lt;colgroup&gt;
&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Issues found&lt;/th&gt;
&lt;th&gt;Auditor&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;Joe&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;OverHypeD&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;However, one and a half months ago we’ve discovered that Max forgot to write down the records for BullSheepInc — and as the column has default of &lt;code&gt;0&lt;/code&gt;, we were unaware of the change for quite some time. Now, if we want to fix it, we have to &lt;strong&gt;overwrite&lt;/strong&gt; the existing record:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;


&lt;colgroup&gt;
&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Issues found&lt;/th&gt;
&lt;th&gt;Auditor&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;Joe&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;OverHypeD&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;&lt;b&gt;101&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;01.04.2022&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The drawbacks of such an approach are clearly visible: we’ve lost track of the changes and forgot about the mistake. As our reports were already sent, there won’t be any punishment for BullSheepInc.&lt;/p&gt;
&lt;h1&gt;
  
  
  Temporality
&lt;/h1&gt;

&lt;p&gt;The next step from mutability to our goal of designing a perfect track record system will be to &lt;strong&gt;persist changes as facts&lt;/strong&gt;, instead of updating data in place.&lt;br&gt;
  In order to build a truly immutable system we want to disallow overriding records in our audit system. Instead, we will be storing any kind of data change as a&lt;br&gt;
separate row in the database:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;


&lt;colgroup&gt;
&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Issues found&lt;/th&gt;
&lt;th&gt;Auditor&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;Joe&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;OverHypeD&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;&lt;b&gt;101&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;01.04.2022&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It looks like this option worked out better for us; now we see that the inventory record change was tracked and we’ve actually discovered some issues.&lt;br&gt;
We could stop there and pretend we’ve built the most advanced audit system, but that would be too far away from being the truth, as soon we’ve got yet another request …&lt;/p&gt;
&lt;h1&gt;
  
  
  Retroactive changes
&lt;/h1&gt;

&lt;p&gt;Once our employees started seeing not just audit records but also data fixes, John has recalled that it was actually him who did the counting, and the amount of found issues was actually &lt;em&gt;99&lt;/em&gt; instead of &lt;em&gt;101&lt;/em&gt;.&lt;br&gt;
We’ve got a serious problem now as the new record doesn’t fit into our data model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;


&lt;colgroup&gt;
&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Issues found&lt;/th&gt;
&lt;th&gt;Auditor&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;Joe&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;OverHypeD&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;&lt;b&gt;101&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Max&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;01.04.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;&lt;b&gt;99&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;John&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;01.05.2022&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Which record is really &lt;em&gt;valid&lt;/em&gt; now? Should we trust Max or John? How should we define what was the error and &lt;strong&gt;how it was corrected&lt;/strong&gt; ?&lt;br&gt;
That’s where the concept of &lt;strong&gt;bitemporality&lt;/strong&gt; comes to the rescue.&lt;/p&gt;
&lt;h1&gt;
  
  
  Bitemporality
&lt;/h1&gt;

&lt;p&gt;In the example above, we have only one time column: the record, or &lt;em&gt;transaction date&lt;/em&gt;.&lt;br&gt;
Bitemporality assumes adding another time dimension — the so-called &lt;strong&gt;valid time&lt;/strong&gt; or effective time — along the &lt;strong&gt;transaction time&lt;/strong&gt; for tracking &lt;strong&gt;when the change really happened&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction time&lt;/strong&gt; represents the time when the record was inserted into the data storage. This can be quite useful for audit purposes, tracking changes and event sourcing.&lt;br&gt;
&lt;strong&gt;Valid time&lt;/strong&gt; represents when the change became &lt;em&gt;valid&lt;/em&gt; and happened in the real world.&lt;/p&gt;

&lt;p&gt;If we follow these definitions we can say that &lt;em&gt;transaction&lt;/em&gt; time is the time we &lt;em&gt;thought&lt;/em&gt; the data was correct at that point in time — and it was &lt;em&gt;actually&lt;/em&gt; correct on the &lt;strong&gt;valid&lt;/strong&gt; time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On the 15th of February, we’ve thought Max has not found any issues.&lt;br&gt;
On the 1st of April, Max corrected the number of issues to be 101.&lt;br&gt;
On May 1st, we’ve discovered that John actually found 99 issues.&lt;br&gt;
In reality, we want the actual amount of issues recorded to be 99 as of 15th of February.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a bitemporal system transaction time is immutable and can be only increased while valid time can be any past or future timestamp.&lt;br&gt;
Let’s see how we can redesign the audit system using these two time dimensions:&lt;/p&gt;
&lt;h2&gt;
  
  
  The perfect audit system™
&lt;/h2&gt;

&lt;p&gt;Now that we know how to utilise transaction and valid dates, we can change our records by writing the record time as &lt;em&gt;transaction date&lt;/em&gt; and&lt;br&gt;
time when it became valid as &lt;em&gt;valid time&lt;/em&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;


&lt;colgroup&gt;
&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;

&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Issues found&lt;/th&gt;
&lt;th&gt;Auditor&lt;/th&gt;
&lt;th&gt;Transaction date&lt;/th&gt;
&lt;th&gt;Valid date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;Joe&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;td&gt;15.09.2021&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;OverHypeD&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;td&gt;14.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;01.04.2022&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;


&lt;tr&gt;
&lt;td&gt;BullSheepInc&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;John&lt;/td&gt;
&lt;td&gt;01.05.2022&lt;/td&gt;
&lt;td&gt;15.02.2022&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let’s execute some queries to our database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Audit&lt;/span&gt;
      &lt;span class="c1"&gt;# @return [Hash] a hash with auditor, issues found and transaction date fields&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# calling the DB ...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"BullSheepInc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# {auditor: "John", issues_found: 99, transaction_date: "01.05.2022"}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"BullSheepInc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"15.09.2021"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# {auditor: "Joe", issues_found: 180, transaction_date: "15.09.2021"}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"BullSheepInc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"01.01.2021"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# nil - we didn't inspect the company as of 01.01.2021&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"BullSheepInc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"15.02.2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"01.04.2022"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# {auditor: "Max", issues_found: 101, transaction_date: "01.04.2022"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, now we have the latest correct value returned by default,&lt;br&gt;
but we can also fetch the record the record as it was on a given valid date in the past.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use-cases
&lt;/h2&gt;

&lt;p&gt;Bitemporality can be proven useful for any system where you have a track of the data and where it’s &lt;em&gt;possible&lt;/em&gt; to have errors and recover them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  payrolls, payment systems&lt;/li&gt;
&lt;li&gt;  auditing&lt;/li&gt;
&lt;li&gt;  risk systems&lt;/li&gt;
&lt;li&gt;  blockchains&lt;/li&gt;
&lt;li&gt;  insurance&lt;/li&gt;
&lt;li&gt;  compliance &amp;amp; privacy&lt;/li&gt;
&lt;li&gt;  temporal data management&lt;/li&gt;
&lt;li&gt;  event-based systems&lt;/li&gt;
&lt;li&gt;  distributed transactions&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Cross-time Database
&lt;/h1&gt;

&lt;p&gt;Supporting bitemporality in an existing database might be not a trivial task, especially when it comes to the traditional relational database where we all relations between tables should also take into account bitemporal columns.&lt;/p&gt;

&lt;p&gt;At the moment, the most prominent open-source solution is &lt;a href="https://xtdb.com"&gt;XTDB (or cross-time) database&lt;/a&gt; developed by &lt;a href="https://juxt.pro"&gt;JUXT&lt;/a&gt; which has a lot of benefits compared to its competitors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Bitemporal at its core&lt;/li&gt;
&lt;li&gt;  Supports retroactive corrections&lt;/li&gt;
&lt;li&gt;  Document&amp;amp;graph based (ultimately a store of versioned documents)&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://en.wikipedia.org/wiki/Datalog"&gt;Datalog queries&lt;/a&gt; and SQL support&lt;/li&gt;
&lt;li&gt;  Data eviction (supports eviction of active and historical data to assist with technical compliance for information privacy regulations)&lt;/li&gt;
&lt;li&gt;  Distributed and scalable&lt;/li&gt;
&lt;li&gt;  Unbundled database (can be deployed on top of many existing technologies and databases like Kafka, JDBC, AWS S3)&lt;/li&gt;
&lt;li&gt;  Can be easily integrated into any existing JVM application or connected using its REST API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we will explore more in the next articles, XTDB can be used as a ready solution for building immutable and bitemporal software.&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s next?
&lt;/h1&gt;

&lt;p&gt;As one can see, bitemporality can be a perfect match for cases where we build systems that are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Tracking the history of changes or how data was changing over time&lt;/li&gt;
&lt;li&gt; Can potentially have errors, corrections or data adjustments&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If we take that concept as the cornerstone of such systems there will be way more chances they will be successful and&lt;br&gt;
we will escape from mutability issues.&lt;/p&gt;

&lt;p&gt;We can also recommend some more reading on the topic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://martinfowler.com/articles/bitemporal-history.html"&gt;Martin Fowler on Bitemporality&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://xtdb.com/"&gt;XTDB — the open database with temporal graph query&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.xtdb.com/concepts/bitemporality/"&gt;Bitemporality concept in XTDB docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the upcoming article we will share our experience working with XTDB, connecting to it from an Elixir application and what we learned from it.&lt;/p&gt;

&lt;p&gt;Happy Hacking and stay tuned!&lt;/p&gt;

&lt;p&gt;Thanks &lt;a href="https://dev.to/carpmeister"&gt;Carsten&lt;/a&gt; for the review! &lt;/p&gt;

</description>
      <category>architecture</category>
      <category>bitemporality</category>
      <category>database</category>
    </item>
  </channel>
</rss>
