[HA supabase][Try 2] Highly available PostgreSQL in Supabase with Supavisor

Try Supavisor:



https://www.youtube.com/watch?v=JvBT4XBdoUE

https://www.youtube.com/watch?v=ogYNmJOFEpk


1. clone https://github.com/supabase/supavisor (only need folder "./dev")

2. mix file:

docker-compose.yml

version: "3"

services:
  db:
    image: supabase/postgres:15.1.0.148
    container_name: supavisor-db
    ports:
      - "6432:5432"
    volumes:
      - ./dev/postgres:/docker-entrypoint-initdb.d/
      # Uncomment to set MD5 authentication method on unitialized databases
      # - ./dev/postgres/md5/etc/postgresql/pg_hba.conf:/etc/postgresql/pg_hba.conf
    command: postgres -c config_file=/etc/postgresql/postgresql.conf
    environment:
      POSTGRES_HOST: /var/run/postgresql
      POSTGRES_PASSWORD: postgres
      # Uncomment to set MD5 authentication method on unitialized databases
      # POSTGRES_INITDB_ARGS: --auth-host=md5
  supavisor:
    # build: .
    image: supabase/supavisor:1.1.67
    container_name: supavisor
    ports:
      - 4000:4000
      - 5452:5452
      - 6543:6543
    environment:
      PORT: 4000
      PROXY_PORT_SESSION: 5452
      PROXY_PORT_TRANSACTION: 6543
      DATABASE_URL: "ecto://postgres:postgres@db:5432/postgres"
      CLUSTER_POSTGRES: "true"
      SECRET_KEY_BASE: "1234567890123456789012123456789012345678901234567890321234567
8901234567890123456789032123456789012345678901234567890323456789032"
      VAULT_ENC_KEY: "12345678901234567890123456789032"
      API_JWT_SECRET: "dev"
      METRICS_JWT_SECRET: "dev"
      REGION: "local"
      ERL_AFLAGS: -proto_dist inet_tcp
    command:  sh -c "/app/bin/migrate && /app/bin/server"
    depends_on:
      - db



3. Runs twice, because postgresql initializes some data

docker compose up -d

docker compose up -d

Let's try:

    depends_on:
      db:
        condition: service_healthy

4. https://supabase.github.io/supavisor/development/setup/

JWT secret key: dev (read docker-compose.yml)

JWT payload:

{
  "iss": "supabase",
  "role": "anon",
  "iat": 1645192824,
  "exp": 1960768824
}

Curl  1: add tenant (connect from docker to host: use ip 172.17.0.1)

curl --location --request PUT 'http://192.168.43.164:4000/api/tenants/dev_tenant' \
--header 'Accept: */*' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNjQ1MTkyODI0LCJleHAiOjE5NjA3Njg4MjR9
.M9jrxyvPLkUxWgOYSf5dNdJ8v_eRrq810ShFRT8N-6M' \
--header 'Content-Type: application/json' \
--data '{
  "tenant": {
    "db_host": "172.17.0.1",
    "db_port": 6432,
    "db_database": "postgres",
    "ip_version": "auto",
    "enforce_ssl": false,
    "require_user": false,
    "auth_query": "SELECT rolname, rolpassword FROM pg_authid WHERE rolname=$1;",
    "users": [
      {
        "db_user": "postgres",
        "db_password": "postgres",
        "pool_size": 20,
        "mode_type": "transaction",
        "is_manager": true
      }
    ]
  }
}'


5. Psql test:

By default, Supavisor uses port 6543 for transaction mode and 5452 for session mode:

"C:\Users\ADMIN\AppData\Local\Programs\pgAdmin 4\runtime\psql.exe" -h 192.168.43.164 -p 6543 -U postgres.dev_tenant -d postgres

(password: postgres)

Working!!!


"C:\Users\ADMIN\AppData\Local\Programs\pgAdmin 4\runtime\psql.exe" -h 192.168.43.164 -p 6543 -U spam -d postgres

 => psql: error: connection to server at "192.168.43.164", port 6543 failed: FATAL:  Tenant or user not found


6. supavisor is connection pooler

With docker image: supabase/postgres:15.1.0.147. Create container => supabase-db 1, 2, 3

Connect to supbase-db 1,2,3 with supavisor 

Config supase-auth,kong,... (disable analytics, vector) with dbuser postfix ".dev_tenant"

#.env

POSTGRES_HOST=172.17.0.1
POSTGRES_DB=postgres
POSTGRES_PORT=6543

USER_SUPABASE_ADMIN=supabase_admin.dev_tenant
USER_AUTHENTICATOR=authenticator.dev_tenant
USER_SUPABASE_STORAGE_ADMIN=supabase_storage_admin.dev_tenant
USER_SUPABASE_AUTH_ADMIN=supabase_auth_admin.dev_tenant

#docker-compose.yml example change

name: supabase
services:
...
  realtime:
    container_name: realtime-dev.supabase-realtime
    image: supabase/realtime:v2.25.50
    healthcheck:
      test:
        [
          "CMD",
          "bash",
          "-c",
          "printf \\0 > /dev/tcp/localhost/4000"
        ]
      timeout: 5s
      interval: 5s
      retries: 3
    restart: unless-stopped
    environment:
      PORT: 4000
      DB_HOST: ${POSTGRES_HOST}
      DB_PORT: ${POSTGRES_PORT}
      DB_USER: ${USER_SUPABASE_ADMIN}
      DB_PASSWORD: ${POSTGRES_PASSWORD}
      DB_NAME: ${POSTGRES_DB}
      DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
      DB_ENC_KEY: supabaserealtime
      API_JWT_SECRET: ${JWT_SECRET}
      ...


7. Next

Try config replicate PostgreSQL for container supabase-db-1,2,3 in image supabase/postgres

Maybe help:

https://gist.github.com/vadv/e55fca418d6a14da71f01a95da493fae

https://github.com/marcel-dempers/docker-development-youtube-series/blob/master/storage/databases/postgresql/3-replication/README.md (https://www.youtube.com/watch?v=FC2JMBYDcJE)



My train:

Edit container supabase-db (192.168.43.163:5432)

docker exec -u postgres -it supabase-db /bin/bash
apt update
apt install nano
nano /etc/postgresql/pg_hba.conf
-> add this
    host     replication     postgres         0.0.0.0/0        md5
/usr/lib/postgresql/15/bin/pg_ctl reload
exit
docker commit supabase-db supabase/postgres:15.1.0.147.mix.1
# new image supabase/postgres:15.1.0.147.mix.1 for replication username: postgres

Create postgresql with 192.168.43.164:5001

#docker-compose.pg_basebackup.yml
name: supabase
services:
  cli3:
    stdin_open: true
    tty: true
    container_name: supabase-cli-3
    image: postgres:15.0
    volumes:
      - ./volumes/db3/data:/data:Z
    entrypoint:
      - /bin/bash


docker compose -f docker-compose.pg_basebackup.yml up -d

docker exec -it supabase-cli3 /bin/bash

pg_basebackup -h 192.168.43.163 -p 5432 -U postgres -D /data/ -Fp -Xs -R

# Edit file: ./volumes/db3/data/postgresql.auto.conf

primary_conninfo = 'user=postgres password='your-super-secret-and-long-postgres-password' host=192.168.43.163 port=5432'

# docker-compose.rep.yml

name: supabase
services:
  rep3:
    stdin_open: true
    tty: true
    container_name: supabase-rep-3
    environment:
      - POSTGRES_USER=postgres
      # password is of "primary" <- 'your-super-secret-and-long-postgres-password'
      - POSTGRES_PASSWORD=admin123
      - POSTGRES_DB=postgres
      - PGDATA=/data
    volumes:
      - ./volumes/db3/data:/data:Z
      - ./volumes/db3/config:/config:Z
      - ./volumes/db3/archive:/mnt/server/archive:Z
    ports:
      - 5001:5432
    image: postgres:15.0
    command: -c 'config_file=/config/postgresql.conf'

docker compose -f docker-compose.rep.yml up -d

Test:

"C:\Users\ADMIN\AppData\Local\Programs\pgAdmin 4\runtime\psql.exe"

-h 192.168.43.163 -p 5001 -U postgres -d postgres

# password: your-super-secret-and-long-postgres-password

# edit in supabase (192.168.43.163:5432) and check in 192.168.43.154:5001


8.  Wait supavisor can be load balanced across read-replicas...

2024/07/10

Future Work

  • Load balancing
    • Queries can be load balanced across read-replicas
    • Load balancing is independent of Postgres high-availability management (see below)



Now with pgcat, can read replica:



I config pgcat as connection pooler
+ read primary and replica
+ good metric http://0.0.0.0:9930/metrics
+ good with pgbench

Not use stolon for replica, use native tool "pg_basebackup"

Hope enjoy with supavisor in future!!!



NOTE 2024/07/11 pgcat:

+ Service realtimeauth need direct connect to supabase-db (5432) !

+ With pgcat, some api supabase error when call replica not read operator !!!

+ :(

NOTE 2024/07/11 supavisor, sometimes it works, sometimes it fails

+ error: prepared statement "0" already exist

+ error: An invalid response was received from the upstream server

-> my fix: Service realtime, auth, function=> direct connect to supabase-db (5432)

mix service "rest" with environment: PGRST_DB_URI

postgres://authenticator.dev_tenant:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:6543,authenticator:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:5432/${POSTGRES_DB}

doc from https://github.com/PostgREST/postgrest/issues/3414
# having only one pg listening on 5432 this will succeed, since libpq will try the second host
psql postgresql://user:pass@localhost:5433,localhost:5432/postgres

# this will now fail since our pg is read-write and we tell libpq to only use a read-only session
psql postgresql://user:pass@localhost:5433,localhost:5432/postgres?target_session_attrs=read-only


BUG:

catch change data with service_role_key

    // need to run the code over and over again (sql query)
    grant USAGE on schema btth to service_role;
    grant all on all tables in schema btth to service_role;

I known cacth change design to authenticated BUT ...... 


Comments