angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

rqlite: een alternatief voor SQLite met hoge beschikbaarheid en distributed

Er zijn veel beperkingen, maar er zijn ook veel gebruiksmogelijkheden voor rqlite in plaats van SQlite.

17 oktober 2023
In
post main image
https://unsplash.com/@_christianlambert

In een project gebruik ik een SQLite database. De gegevens zijn niet kritisch, ze kunnen op elk moment opnieuw worden geladen. Toch wil ik niet dat een deel van de applicatie niet meer reageert als de SQLite database tijdelijk niet beschikbaar is.

Ik was op zoek naar een snelle, min of meer fouttolerante database, en ook naar een distributed, zodat ik sommige leesmodules kan repliceren. Er kwamen een paar oplossingen naar voren bij het zoeken op internet en rqlite leek een goede keuze.

In deze post zet ik een rqlite cluster met drie nodes op met Docker-Compose en benader ik de nodes met een Python applicatie.

Zoals altijd draai ik Ubuntu 22.04.

Beperkingen van rqlite

Ik begin met de beperkingen, omdat ze misschien niet geschikt zijn voor jouw geval. Hier zijn ze:

  • Transacties worden niet ondersteund.
  • Er is een klein risico op gegevensverlies in het geval dat het knooppunt crasht voordat gegevens in de wachtrij zijn opgeslagen.
  • Snelheid. Verwacht niet dezelfde tijden als bij het rechtstreeks benaderen van een SQLite database. Niet alleen is er de netwerkoverhead, ook de schrijflatentie is veel hoger dan die van SQLite , veroorzaakt door het Raft consensusalgoritme. Het gebruik van bulk writes verbetert de prestaties aanzienlijk.
  • Niet-deterministische functies en andere, zie de rqlite 'Developer Guide'.

Er staat meer informatie in het document 'Comparing Litestream, rqlite, and dqlite', zie onderstaande links.

De bestanden docker-compose.yml en '.env

Er zijn veel manieren om een rqlite cluster op te zetten. Hier gebruiken we de rqlite Docker image om een cluster van drie rqlite knooppunten te maken volgens de instructies voor 'Automatisch Bootstrapping', zie onderstaande koppelingen.

We stellen geen poorten bloot aan het hostsysteem, maar maken de nodes (alleen) beschikbaar op een Docker netwerk. Essentieel is het instellen van de hostnaam voor de nodes! De hostnaam wordt door de rqlite nodes gebruikt voor ontdekking en andere applicaties gebruiken de hostnamen om toegang te krijgen tot het cluster van nodes. En we gebruiken Volumes omdat we de gegevens die zijn opgeslagen in Raft willen behouden, zelfs als het cluster voor korte tijd uitvalt.

Belangrijk: Verwijder na het wijzigen van het bestand docker-compose.yml en/of '.env' de gegevens uit de gekoppelde mappen (./data/rqlite-node-1, ./data/rqlite-node-2, ./data/rqlite-node-3). Als je dit niet doet, kun je allerlei vreemd gedrag krijgen!

Het bestand docker-compose.yml:

# docker-compose.yml

version: '3.7'

services:
  rqlite-node-1:
    image: rqlite/rqlite:7.21.4
    hostname: ${RQLITE_NODE_1_HOSTNAME}
    volumes:
      - ./data/rqlite-node-1:/rqlite/file/data
    command:
      - rqlited
      - -node-id
      - "${RQLITE_NODE_1_NODE_ID}"
      - -http-addr
      - "${RQLITE_NODE_1_HTTP_ADDR}"
      - -raft-addr
      - "${RQLITE_NODE_1_RAFT_ADDR}"
      - -http-adv-addr
      - "${RQLITE_NODE_1_HTTP_ADV_ADDR}"
      - -raft-adv-addr
      - "${RQLITE_NODE_1_RAFT_ADV_ADDR}"
      # join
      - -bootstrap-expect
      - "3"
      - -join
      - "${RQLITE_NODE_1_JOIN_ADDR},${RQLITE_NODE_2_JOIN_ADDR},${RQLITE_NODE_3_JOIN_ADDR}"
      - /rqlite/file/data
    networks:
      - rqlite-cluster-network

  rqlite-node-2:
    image: rqlite/rqlite:7.21.4
    hostname: ${RQLITE_NODE_2_HOSTNAME}
    volumes:
      - ./data/rqlite-node-2:/rqlite/file/data
    command:
      - rqlited
      - -node-id
      - "${RQLITE_NODE_2_NODE_ID}"
      - -http-addr
      - "${RQLITE_NODE_2_HTTP_ADDR}"
      - -raft-addr
      - "${RQLITE_NODE_2_RAFT_ADDR}"
      - -http-adv-addr
      - "${RQLITE_NODE_2_HTTP_ADV_ADDR}"
      - -raft-adv-addr
      - "${RQLITE_NODE_2_RAFT_ADV_ADDR}"
      # join
      - -bootstrap-expect
      - "3"
      - -join
      - "${RQLITE_NODE_1_JOIN_ADDR},${RQLITE_NODE_2_JOIN_ADDR},${RQLITE_NODE_3_JOIN_ADDR}"
      - /rqlite/file/data
    networks:
      - rqlite-cluster-network

  rqlite-node-3:
    image: rqlite/rqlite:7.21.4
    hostname: ${RQLITE_NODE_3_HOSTNAME}
    volumes:
      - ./data/rqlite-node-3:/rqlite/file/data
    command:
      - rqlited
      - -node-id
      - "${RQLITE_NODE_3_NODE_ID}"
      - -http-addr
      - "${RQLITE_NODE_3_HTTP_ADDR}"
      - -raft-addr
      - "${RQLITE_NODE_3_RAFT_ADDR}"
      - -http-adv-addr
      - "${RQLITE_NODE_3_HTTP_ADV_ADDR}"
      - -raft-adv-addr
      - "${RQLITE_NODE_3_RAFT_ADV_ADDR}"
      # join
      - -bootstrap-expect
      - "3"
      - -join
      - "${RQLITE_NODE_1_JOIN_ADDR},${RQLITE_NODE_2_JOIN_ADDR},${RQLITE_NODE_3_JOIN_ADDR}"
      - /rqlite/file/data
    networks:
      - rqlite-cluster-network

networks:
  rqlite-cluster-network:
    external: true
    name: rqlite-cluster-network

The '.env'-file:

# .env

COMPOSE_PROJECT_NAME=rqlite-cluster

# RQLITE_NODE_1
RQLITE_NODE_1_HOSTNAME=rqlite-node-1
RQLITE_NODE_1_NODE_ID=rqlite-node-1
RQLITE_NODE_1_DATA_DIR=/rqlite/file/data
RQLITE_NODE_1_HTTP_ADDR=rqlite-node-1:4001
RQLITE_NODE_1_RAFT_ADDR=rqlite-node-1:4002
RQLITE_NODE_1_HTTP_ADV_ADDR=rqlite-node-1:4001
RQLITE_NODE_1_RAFT_ADV_ADDR=rqlite-node-1:4002
# join
RQLITE_NODE_1_JOIN_ADDR=rqlite-node-1:4001

# RQLITE_NODE_2
RQLITE_NODE_2_HOSTNAME=rqlite-node-2
RQLITE_NODE_2_NODE_ID=rqlite-node-2
RQLITE_NODE_2_DATA_DIR=/rqlite/file/data
RQLITE_NODE_2_HTTP_ADDR=rqlite-node-2:4001
RQLITE_NODE_2_RAFT_ADDR=rqlite-node-2:4002
RQLITE_NODE_2_HTTP_ADV_ADDR=rqlite-node-2:4001
RQLITE_NODE_2_RAFT_ADV_ADDR=rqlite-node-2:4002
# join
RQLITE_NODE_2_JOIN_ADDR=rqlite-node-2:4001

# RQLITE_NODE_3
RQLITE_NODE_3_HOSTNAME=rqlite-node-3
RQLITE_NODE_3_NODE_ID=rqlite-node-3
RQLITE_NODE_3_DATA_DIR=/rqlite/file/data
RQLITE_NODE_3_HTTP_ADDR=rqlite-node-3:4001
RQLITE_NODE_3_RAFT_ADDR=rqlite-node-3:4002
RQLITE_NODE_3_HTTP_ADV_ADDR=rqlite-node-3:4001
RQLITE_NODE_3_RAFT_ADV_ADDR=rqlite-node-3:4002
# join
RQLITE_NODE_3_JOIN_ADDR=rqlite-node-3:4001

Maak nu het Docker netwerk aan:

> docker network create rqlite-cluster-network

En start het cluster:

> docker-compose up

De berichten in de terminal laten zien dat de knooppunten rqlite contact met elkaar maken. Staat het cluster echt aan? Om dit te controleren opent u een andere terminal en voert u een van de rqlite containers in:

> docker exec -it rqlite-cluster_rqlite-node-3_1 sh

Maak vervolgens verbinding met een van de knooppunten:

# rqlite -H rqlite-node-1

Resultaat:

Welcome to the rqlite CLI. Enter ".help" for usage hints.
Version v7.21.4, commit 971921f1352bdc73e4e66a1ec43be8c1028ff18b, branch master
Connected to rqlited version v7.21.4
rqlite-node-1:4001>

Geef het commando '.nodes'. Resultaat:

rqlite-node-2:
  leader: false
  time: 0.001115574
  api_addr: http://rqlite-node-2:4001
  addr: rqlite-node-2:4002
  reachable: true
rqlite-node-3:
  leader: false
  time: 0.001581149
  api_addr: http://rqlite-node-3:4001
  addr: rqlite-node-3:4002
  reachable: true
rqlite-node-1:
  time: 0.000009044
  api_addr: http://rqlite-node-1:4001
  addr: rqlite-node-1:4002
  reachable: true
  leader: true

Ziezo, het cluster is up!

Laten we nu een SQL commando proberen vanuit een andere container die verbonden is met het clusternetwerk, hier gebruiken we de 'nicolaka/netshoot' image:

> docker run -it --network rqlite-cluster-network nicolaka/netshoot bash

Geef het commando om een tabel te maken:

# curl -XPOST 'rqlite-node-2:4001/db/execute?pretty&timings' -H "Content-Type: application/json" -d '[
    "CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT, age INTEGER)"
]'

Resultaat:

{
    "results": [
        {
            "time": 0.000179355
        }
    ],
    "time": 0.018545186
}

Herhaal het commando en het resultaat is:

{
    "results": [
        {
            "error": "table foo already exists"
        }
    ],
    "time": 0.017034644
}

Geweldig, ons rqlite cluster is up and running.

Toegang tot het rqlite cluster met Python

Het rqlite project heeft ook verschillende clients, waaronder pyrqlite, een client voor Python. We gebruiken het voorbeeld van pyrqlite op de rqlite Github pagina. We brengen twee wijzigingen aan:

  • De 'host'.
  • We laten de tabel vallen als deze al bestaat.

Maak op het hostsysteem een submap 'code' en voeg het volgende bestand toe:

# rqlite_test.py

import pyrqlite.dbapi2 as dbapi2

# Connect to the database
connection = dbapi2.connect(
    host='rqlite-node-2',
    port=4001,
)

try:
    with connection.cursor() as cursor:
        cursor.execute('DROP TABLE IF EXISTS foo') 
        cursor.execute('CREATE TABLE foo (id integer not null primary key, name text)')
        cursor.executemany('INSERT INTO foo(name) VALUES(?)', seq_of_parameters=(('a',), ('b',)))

    with connection.cursor() as cursor:
        # Read a single record with qmark parameter style
        sql = "SELECT `id`, `name` FROM `foo` WHERE `name`=?"
        cursor.execute(sql, ('a',))
        result = cursor.fetchone()
        print(result)
        # Read a single record with named parameter style
        sql = "SELECT `id`, `name` FROM `foo` WHERE `name`=:name"
        cursor.execute(sql, {'name': 'b'})
        result = cursor.fetchone()
        print(result)
finally:
    connection.close()

Start en voer een Python container in, verbonden met de 'rqlite-cluster-network', en monteer onze code op het hostsysteem op '/code' in de container:

> docker run -it --net rqlite-cluster-network -v ${PWD}/code:/code python:3.11.5-slim-bullseye bash

In de container installeert u pyrqlite:

# pip install pyrqlite

Ga in de container naar de map '/code' en voer het script uit:

# python rqlite_test.py

Resultaat:

OrderedDict([('id', 1), ('name', 'a')])
OrderedDict([('id', 2), ('name', 'b')])

Werkt!

Samenvatting

Hoewel het heel eenvoudig lijkt om rqlite te gebruiken in plaats van SQLite, is dat niet zo! U moet bepalen (lezen, lezen, lezen) of rqlite aan uw eisen voldoet, wat niet eenvoudig is omdat de verschillen en beperkingen op verschillende pagina's in de documentatie worden vermeld.

Het maken van een rqlite cluster is niet moeilijk als je Docker of Docker Swarm gebruikt. Er is ook een handleiding voor Kubernetes. Het rqlite cluster geeft ons een dist gedistribueerde, min of meer fouttolerante, SQLite database.

Om fouttolerantie op applicatieniveau te krijgen, moeten we een lijst van rqlite knooppunten (hosts) aan onze applicatie toevoegen en code toevoegen om naar een ander rqlite knooppunt te schakelen als er geen rqlite knooppunt beschikbaar is. Hoe dan ook, voor mijn geval is rqlite een perfecte oplossing!

Links / credits

Comparing Litestream, rqlite, and dqlite
https://gcore.com/learning/comparing-litestream-rqlite-dqlite

rqlite
https://rqlite.io

rqlite - Automatic clustering: Automatic Bootstrapping
https://rqlite.io/docs/clustering/automatic-clustering

rqlite - Developer Guide
https://rqlite.io/docs/api

rqlite/rqlite Docker image
https://hub.docker.com/r/rqlite/rqlite

Lees meer

rqlite

Laat een reactie achter

Reageer anoniem of log in om commentaar te geven.

Opmerkingen (1)

Laat een antwoord achter

Antwoord anoniem of log in om te antwoorden.

avatar

I'm the creator of rqlite -- nice article. I'm glad you find the software useful.
Philip
(https://www.philipotoole.com)