Ubuntu22.04にQuestdbを入れてinfluxDBと比較してみた。

QuestDBは、高スループットの取り込みと高速SQLクエリを実現するオープンソースの時系列データベースという事らしく、influxDBよりも高速に動作するという話なので両者の性能差を比較してみたいと思います。


# 前提
仮想マシンスペック： 8vCPU / 8GB_MEM
OS：Ubuntu 22.04.1 LTS
java: openjdk-11-jdk
Maven: 3.9.8


# 1. 必要packageのインストール
```bash
apt install openjdk-11-jdk git
wget https://dlcdn.apache.org/maven/maven-3/3.9.8/binaries/apache-maven-3.9.8-bin.tar.gz
tar zxvf apache-maven-3.9.8-bin.tar.gz
mv apache-maven-3.9.8 /opt/maven
```

JAVA_HOMEを設定していないとビルド時に失敗しますのであらかじめ設定しておきます。
```bash
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
```

詳しくは下記公式サイトをご確認ください。

https://questdb.io/docs/deployment/systemd/


# 2. ビルド
githubからcloneしてビルドします。ビルドの際にテストをスキップするように「DskipTests」を追加し、実行可能バイナリとしてビルドするため、「build-web-console,build-binaries」もオプションを指定します。
```bash
git clone https://github.com/questdb/questdb.git
cd questdb
/opt/maven/bin/mvn clean package -DskipTests -P build-web-console,build-binaries
```

以下のように表示されればビルド完了です。
```bash
[INFO]
[INFO] --- jacoco:0.8.8:report (default-report) @ questdb-parent ---
[INFO] Skipping JaCoCo execution due to missing execution data file.
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for QuestDB 8.1.1-SNAPSHOT:
[INFO]
[INFO] QuestDB ............................................ SUCCESS [ 41.248 s]
[INFO] JMH benchmarks for QuestDB ......................... SUCCESS [  6.641 s]
[INFO] Command line utils for QuestDB ..................... SUCCESS [  2.252 s]
[INFO] Examples for QuestDB ............................... SUCCESS [  0.308 s]
[INFO] Compatibility tests for QuestDB .................... SUCCESS [  3.323 s]
[INFO] QuestDB ............................................ SUCCESS [  0.386 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  54.331 s
[INFO] Finished at: 2024-08-05T09:25:23Z
[INFO] ------------------------------------------------------------------------
```

# 3. 起動設定

先程ビルドしたファイルを/usr/local/bin配下に配置します。
```bash
cp -pr core/target/questdb-8.1.1-SNAPSHOT-no-jre-bin.tar.gz /tmp/questdb.tar.gz
cd /tmp/
tar zxvf questdb.tar.gz
mv questdb-8.1.1-SNAPSHOT-no-jre-bin /usr/local/bin/questdb
mkdir /var/lib/questdb
```

続いてカーネルパラメータを調整しておきます。
```bash
vim /etc/sysctl.conf
fs.file-max=1048576
vm.max_map_count=1048576
:wq

sysctl -p
```

では起動設定ファイルを作成しましょう。
```systemd:/lib/systemd/system/questdb.service
[Unit]
Description=QuestDB
Documentation=https://www.questdb.io/docs/
After=network.target

[Service]
Type=simple
Restart=always
RestartSec=2
LimitNOFILE=2147483647
# Adjust java path to match requirements of a given distro
ExecStart=/usr/lib/jvm/java-11-openjdk-amd64/bin/java \
--add-exports java.base/jdk.internal.math=io.questdb \
-p /usr/local/bin/questdb/questdb.jar \
-m io.questdb/io.questdb.ServerMain \
-DQuestDB-Runtime-66535 \
-ea -Dnoebug \
-XX:+UnlockExperimentalVMOptions \
-XX:+AlwaysPreTouch \
-XX:+UseParallelOldGC \
-d /var/lib/questdb

ExecReload=/bin/kill -s HUP $MAINPID

# Prevent writes to /usr, /boot, and /etc
ProtectSystem=full
StandardError=syslog
SyslogIdentifier=questdb

[Install]
WantedBy=multi-user.target
```

起動設定ファイルを有効化して、サービス起動してみます。
```bash
systemctl enable questdb.service
systemctl start questdb
```

早速ブラウザからアクセスします。URLは[http://IP:9000]でアクセスできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/7021f5c6-686b-c0da-491c-db53d98de41c.png)

# 4. 性能比較
性能値についてGithub上にも記載はあるものの、テスト前提が96コアのうち24コア使用とかかれていて。。。。（そんなに準備できません）

:::note info
Our demo is running on c5.metal instance and using 24 cores out of 96.
:::

ということで貧弱な仮想マシンでどれくらい差がでるのか試してみます。同じ仮想マシンにinfluxDBとQuestdbを入れて試しました。

まずはデータをdummyデータを生成します。
```python:dummy.py
import pandas as pd
import numpy as np
import datetime

Num_Records = 1000000

timestamps = pd.date_range(start='2023-01-01', periods=Num_Records, freq='T')
sensor_ids = np.random.choice(['sensor_1', 'sensor_2', 'sensor_3', 'sensor_4', 'sensor_5'], size=Num_Records)
values = np.random.normal(loc=20, scale=5, size=Num_Records)

data = pd.DataFrame({'timestamp': timestamps, 'sensor_id': sensor_ids, 'value': values})

data.to_csv('dummy_sensor_data.csv', index=False)
```

作成されたデータdummyデータをQuestdbおよびinfluxDBにimportします。以下スクリプトを作成してそれぞれ実行しておきましょう。

```python:import_Questdb.py
from influxdb import InfluxDBClient
import pandas as pd

data = pd.read_csv('dummy_sensor_data.csv')

client = InfluxDBClient(host='x.x.x.x', port=9000)  # ご自身のIPに置き換えてください。
client.switch_database('live-est001')

batch_size = 10000
points = []

for i, row in data.iterrows():
    point = {
        "measurement": "sensor_data",
        "time": row['timestamp'],
        "tags": {
            "sensor_id": row['sensor_id']
        },
        "fields": {
            "value": row['value']
        }
    }
    points.append(point)

    if i % batch_size == 0 and i > 0:
        client.write_points(points)
        points = []

if points:
    client.write_points(points)
```

```python:import_influxdb.py
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS
import pandas as pd

data = pd.read_csv('dummy_sensor_data.csv')

url = "http://x.x.x.x:8086"                               #ご自身の環境に置き換え
token = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" #ご自身のTokenに置き換え
org = "live-est001"                                        #ご自身の環境に置き換え
bucket = "live-est001"                                     #ご自身の環境に置き換え

client = InfluxDBClient(url=url, token=token, org=org)
write_api = client.write_api(write_options=SYNCHRONOUS)

batch_size = 10000
points = []

for i, row in data.iterrows():
    point = Point("sensor_data")\
        .tag("sensor_id", row['sensor_id'])\
        .field("value", row['value'])\
        .time(row['timestamp'], WritePrecision.NS)
    points.append(point)

    if i % batch_size == 0 and i > 0:
        write_api.write(bucket=bucket, org=org, record=points)
        points = []

if points:
    write_api.write(bucket=bucket, org=org, record=points)

client.close()
```

ではdummyデータがそれぞれのDBに入りましたので、比較してみます。
一定期間内(2024年1月1日～12月31日）のセンサーごとの最大値、最小値、平均値を取得するクエリをそれぞれ流してみました。

#### influxDB(Fluxスクリプト)
```sql
from(bucket: "live-est001")
  |> range(start: 2024-01-01T00:00:00Z, stop: 2024-12-31T23:59:59Z)
  |> filter(fn: (r) => r._measurement == "sensor_data")
  |> group(columns: ["sensor_id"])
  |> aggregateWindow(every: 1h, fn: max, createEmpty: false)
  |> yield(name: "max_value")

from(bucket: "live-est001")
  |> range(start: 2024-01-01T00:00:00Z, stop: 2024-12-31T23:59:59Z)
  |> filter(fn: (r) => r._measurement == "sensor_data")
  |> group(columns: ["sensor_id"])
  |> aggregateWindow(every: 1h, fn: min, createEmpty: false)
  |> yield(name: "min_value")

from(bucket: "live-est001")
  |> range(start: 2024-01-01T00:00:00Z, stop: 2024-12-31T23:59:59Z)
  |> filter(fn: (r) => r._measurement == "sensor_data")
  |> group(columns: ["sensor_id"])
  |> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
  |> yield(name: "avg_value")
```

#### 実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/2cd0cd2e-b9ec-3bbe-628a-bfdd25033219.png)





#### QuestDB

```sql
SELECT
  sensor_id,
  MAX(value) AS max_value,
  MIN(value) AS min_value,
  AVG(value) AS avg_value
FROM
  sensor_data
WHERE
  timestamp BETWEEN '2024-01-01T00:00:00Z' AND '2024-12-31T23:59:59Z'
GROUP BY
  sensor_id;
```

##### 実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/9a3c1871-4601-4fd8-eed9-efa5a6789bcb.png)

#### 性能比較
なんと約1/122でした！
|influxdb  |Questdb  |
|---|---|
|3.29s  |27ms  |


# その他
今回はそれほど込み入った比較をしておりませんが、性能は圧倒的ですね。詳細は以下に記載がありますのでご確認ください。

https://github.com/questdb/questdb?tab=readme-ov-file#how-questdb-compares-to-other-open-source-tsdbs