PG18 de retour, mises à jour nocturnes, tests de reprise et documentation
🚀 Docker Build and Push / build-and-push (15) (push) Successful in 5m29s
🚀 Docker Build and Push / build-and-push (16) (push) Successful in 6m10s
🚀 Docker Build and Push / build-and-push (17) (push) Successful in 5m20s
🚀 Docker Build and Push / build-and-push (18) (push) Successful in 5m21s
🚀 Docker Build and Push / build-and-push (15) (push) Successful in 5m29s
🚀 Docker Build and Push / build-and-push (16) (push) Successful in 6m10s
🚀 Docker Build and Push / build-and-push (17) (push) Successful in 5m20s
🚀 Docker Build and Push / build-and-push (18) (push) Successful in 5m21s
Image - Étape `base` commune : tous les paquets sont mis à niveau (PostgreSQL à la dernière mineure, pgvector, Debian). L'image de base de VectorChord restait figée à la version de sa publication. - En-têtes du serveur épinglés sur la version du serveur installé. PG18 avait été retiré parce que TimescaleDB, compilé avec des en-têtes 18.6, était chargé par un serveur 18.3 (`undefined symbol: palloc0_mul`). - Sonde de santé avec l'utilisateur et la base du conteneur : en `postgres`, elle écrivait une erreur FATAL au journal dès que POSTGRES_USER était défini. - Label `base.name` corrigé : il reprenait la version du paquet au lieu de la version majeure. CI - PG18 ajouté à la matrice. - Exécution nocturne : apt est interrogé dans l'image publiée ; elle n'est reconstruite et republiée que s'il lui reste des mises à jour, sinon elle est seulement testée. Lancement manuel possible. - `docker-build.yml` supprimé : l'ancien workflow à actions externes tournait encore à chaque push, en double de `docker.yml`. - `paths-ignore` : un push de documentation seule ne lance pas la CI. - Labels `version`, `url` et `fr.killinger.apt-refresh`. Tests - `tests.sh`, la même commande en local et en CI : utilisateur et base autres que `postgres`, version majeure du serveur, présence des cinq extensions, redémarrage, et reprise d'une base créée par l'image publiée. Documentation - README en vitrine, CLAUDE.md, docs/architecture.md, docs/exploitation/publication.md, six décisions, docs/a-faire.md.
This commit is contained in:
18 files changed
+956
-242
No files matched your search
@@ -1,106 +0,0 @@
|
|||||||
name: 🚀 Docker Build and Push
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-push:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
pg_version: [15, 16, 17]
|
|
||||||
steps:
|
|
||||||
- name: 📥 Checkout code
|
|
||||||
uses: https://github.com/actions/checkout@v4
|
|
||||||
|
|
||||||
- name: 🛠️ Set up Docker Buildx
|
|
||||||
uses: https://github.com/docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: 🔐 Login to Gitea Registry
|
|
||||||
uses: https://github.com/docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: gitea.killinger.fr
|
|
||||||
username: maxime.killinger
|
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
|
||||||
|
|
||||||
- name: 📦 Build Docker image
|
|
||||||
uses: https://github.com/docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
load: true
|
|
||||||
build-args: |
|
|
||||||
PG_VERSION=${{ matrix.pg_version }}
|
|
||||||
tags: |
|
|
||||||
postgres-ts-vectors:pg${{ matrix.pg_version }}-test
|
|
||||||
|
|
||||||
- name: 🧪 Test image - Start container
|
|
||||||
run: |
|
|
||||||
docker run -d --name test-pg${{ matrix.pg_version }} \
|
|
||||||
-e POSTGRES_PASSWORD=testpass \
|
|
||||||
postgres-ts-vectors:pg${{ matrix.pg_version }}-test
|
|
||||||
|
|
||||||
# Wait for PostgreSQL to be ready
|
|
||||||
echo "Waiting for PostgreSQL to start..."
|
|
||||||
for i in {1..30}; do
|
|
||||||
if docker exec test-pg${{ matrix.pg_version }} pg_isready -U postgres > /dev/null 2>&1; then
|
|
||||||
echo "PostgreSQL is ready!"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: ⚙️ Setup Tests
|
|
||||||
run: |
|
|
||||||
# Wait for valid startup
|
|
||||||
sleep 10
|
|
||||||
docker cp tests/ test-pg${{ matrix.pg_version }}:/tmp/tests/
|
|
||||||
|
|
||||||
- name: "🧪 Test 1: Configuration"
|
|
||||||
run: docker exec test-pg${{ matrix.pg_version }} psql -U postgres -f /tmp/tests/test-1-config.sql
|
|
||||||
|
|
||||||
- name: "🧪 Test 2: TimescaleDB"
|
|
||||||
run: docker exec test-pg${{ matrix.pg_version }} psql -U postgres -f /tmp/tests/test-2-timescale.sql
|
|
||||||
|
|
||||||
- name: "🧪 Test 3: VectorChord"
|
|
||||||
run: docker exec test-pg${{ matrix.pg_version }} psql -U postgres -f /tmp/tests/test-3-vector.sql
|
|
||||||
|
|
||||||
- name: "🧪 Test 4: Hybrid Scenarios"
|
|
||||||
run: docker exec test-pg${{ matrix.pg_version }} psql -U postgres -f /tmp/tests/test-4-hybrid.sql
|
|
||||||
|
|
||||||
- name: "🧪 Test 5: PostGIS"
|
|
||||||
run: docker exec test-pg${{ matrix.pg_version }} psql -U postgres -f /tmp/tests/test-5-postgis.sql
|
|
||||||
|
|
||||||
- name: 🧹 Cleanup test container
|
|
||||||
if: always()
|
|
||||||
run: docker rm -f test-pg${{ matrix.pg_version }} || true
|
|
||||||
|
|
||||||
- name: 🚀 Push Docker image
|
|
||||||
uses: https://github.com/docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
build-args: |
|
|
||||||
PG_VERSION=${{ matrix.pg_version }}
|
|
||||||
tags: |
|
|
||||||
gitea.killinger.fr/maxime.killinger/postgres-ts-vectors:pg${{ matrix.pg_version }}
|
|
||||||
|
|
||||||
- name: 🏷️ Tag latest (PG17 only)
|
|
||||||
if: matrix.pg_version == 17
|
|
||||||
uses: https://github.com/docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
build-args: |
|
|
||||||
PG_VERSION=17
|
|
||||||
tags: |
|
|
||||||
gitea.killinger.fr/maxime.killinger/postgres-ts-vectors:latest
|
|
||||||
|
|
||||||
- name: 🧹 Cleanup Docker images
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
docker rmi postgres-ts-vectors:pg${{ matrix.pg_version }}-test || true
|
|
||||||
docker image prune -f
|
|
||||||
|
|
||||||
|
|
||||||
+61
-52
@@ -4,26 +4,32 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
# Un push qui ne touche que la doc ne reconstruit rien : aucun Markdown n'entre dans
|
||||||
|
# l'image. Un push mêlant code et doc lance toujours la CI.
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- 'docs/**'
|
||||||
|
# Chaque nuit (heure UTC) : les images publiées sont testées et, si apt leur propose des mises
|
||||||
|
# à jour, reconstruites et republiées.
|
||||||
|
# Voir docs/decisions/0005-mises-a-jour-nocturnes-pilotees-par-apt.md.
|
||||||
|
schedule:
|
||||||
|
- cron: '30 3 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build-and-push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
pg_version: [15, 16, 17]
|
pg_version: [15, 16, 17, 18]
|
||||||
# Accélération (2026-09-27) :
|
# Aucune action externe, construction par le démon Docker du runner (sans Buildx) dont le
|
||||||
# - aucune action externe : checkout, login et build-push étaient téléchargés depuis GitHub à
|
# cache persiste d'une CI à l'autre, et une seule construction par version : l'image testée
|
||||||
# chaque job, ~20 s par action ;
|
# est celle qui est poussée. Voir docs/decisions/0004-ci-sans-action-externe.md.
|
||||||
# - construction par le démon Docker du runner, sans Buildx : « Set up Docker Buildx » créait
|
|
||||||
# un constructeur neuf, sans cache, et TimescaleDB était recompilé à chaque job (7 à 9 min).
|
|
||||||
# Le cache du démon persiste d'une CI à l'autre : la compilation n'est rejouée que si la
|
|
||||||
# version de TimescaleDB, de PostgreSQL ou l'image de base change ;
|
|
||||||
# - une seule construction : l'image testée est celle qui est poussée (étiquetage), au lieu de
|
|
||||||
# la reconstruire pour le push puis pour `latest`.
|
|
||||||
env:
|
env:
|
||||||
IMAGE: gitea.killinger.fr/${{ github.repository }}
|
IMAGE: gitea.killinger.fr/${{ github.repository }}
|
||||||
TEST: pg${{ matrix.pg_version }}-test-${{ github.run_id }}
|
PREVIOUS: postgres-ts-vectors-previous:pg${{ matrix.pg_version }}
|
||||||
steps:
|
steps:
|
||||||
|
# Le seul commit poussé, sans historique : c'est tout ce que la construction lit.
|
||||||
- name: 📥 Checkout code
|
- name: 📥 Checkout code
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ github.token }}
|
TOKEN: ${{ github.token }}
|
||||||
@@ -37,67 +43,70 @@ jobs:
|
|||||||
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
|
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
|
||||||
run: echo "$DOCKER_TOKEN" | docker login gitea.killinger.fr -u maxime.killinger --password-stdin
|
run: echo "$DOCKER_TOKEN" | docker login gitea.killinger.fr -u maxime.killinger --password-stdin
|
||||||
|
|
||||||
|
# On demande à apt, dans l'image publiée, s'il lui reste des mises à jour :
|
||||||
|
# - oui, ou image jamais publiée : nouvelle clé APT_REFRESH, les couches apt sont rejouées ;
|
||||||
|
# - non : la clé de l'image publiée (label apt-refresh) est reprise et le cache sert.
|
||||||
|
# L'exécution planifiée ne reconstruit ni ne pousse alors rien : elle teste l'image publiée.
|
||||||
|
# L'image publiée est gardée sous $PREVIOUS : les tests y créent une base, que l'image
|
||||||
|
# reconstruite doit savoir reprendre.
|
||||||
|
- name: 🔎 Check for updates
|
||||||
|
id: updates
|
||||||
|
run: |
|
||||||
|
refresh=""; previous=""
|
||||||
|
if docker pull -q "$IMAGE:pg${{ matrix.pg_version }}" >/dev/null 2>&1; then
|
||||||
|
docker tag "$IMAGE:pg${{ matrix.pg_version }}" "$PREVIOUS"; previous="$PREVIOUS"
|
||||||
|
upgrade=$(docker run --rm --entrypoint sh "$PREVIOUS" -c \
|
||||||
|
'apt-get update -qq -o APT::Update::Error-Mode=any && apt-get -s upgrade --with-new-pkgs')
|
||||||
|
if pending=$(echo "$upgrade" | grep '^Inst'); then
|
||||||
|
echo "Mises à jour disponibles :"; echo "$pending"
|
||||||
|
else
|
||||||
|
refresh=$(docker image inspect -f '{{ index .Config.Labels "fr.killinger.apt-refresh" }}' "$PREVIOUS")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
build=true
|
||||||
|
if [ -n "$refresh" ] && [ "${{ github.event_name }}" = schedule ]; then
|
||||||
|
echo "Image publiée à jour : rien à reconstruire"; build=false; previous=""
|
||||||
|
fi
|
||||||
|
echo "refresh=${refresh:-$(date -u +%Y%m%dT%H%M%SZ)}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "build=$build" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "previous=$previous" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
# `--pull` : l'image de base (tensorchord/vchord-postgres) est vérifiée à chaque construction,
|
# `--pull` : l'image de base (tensorchord/vchord-postgres) est vérifiée à chaque construction,
|
||||||
# sinon ses correctifs de sécurité ne seraient jamais repris avec le cache persistant.
|
# pour recevoir une republication sous la même étiquette malgré le cache persistant.
|
||||||
|
# Labels OCI propres à la construction ; les labels fixes sont dans le Dockerfile.
|
||||||
- name: 📦 Build Docker image
|
- name: 📦 Build Docker image
|
||||||
|
if: steps.updates.outputs.build == 'true'
|
||||||
run: |
|
run: |
|
||||||
docker build --pull \
|
docker build --pull \
|
||||||
--build-arg PG_VERSION=${{ matrix.pg_version }} \
|
--build-arg PG_VERSION=${{ matrix.pg_version }} \
|
||||||
|
--build-arg APT_REFRESH=${{ steps.updates.outputs.refresh }} \
|
||||||
|
--label fr.killinger.apt-refresh=${{ steps.updates.outputs.refresh }} \
|
||||||
|
--label org.opencontainers.image.url=https://gitea.killinger.fr/${{ github.repository }} \
|
||||||
--label org.opencontainers.image.revision=${{ github.sha }} \
|
--label org.opencontainers.image.revision=${{ github.sha }} \
|
||||||
|
--label org.opencontainers.image.version=pg${{ matrix.pg_version }} \
|
||||||
--label org.opencontainers.image.created="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
--label org.opencontainers.image.created="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||||
-t "$IMAGE:pg${{ matrix.pg_version }}" .
|
-t "$IMAGE:pg${{ matrix.pg_version }}" .
|
||||||
|
|
||||||
# Prête quand l'initialisation est terminée (extensions créées, serveur temporaire arrêté) ET
|
# La même commande qu'en local. Sans reconstruction, c'est l'image publiée qui est testée.
|
||||||
# que le serveur définitif accepte les connexions — au lieu d'un `sleep 10` fixe.
|
- name: 🧪 Tests
|
||||||
- name: 🧪 Test image - Start container
|
run: bash tests.sh "$IMAGE:pg${{ matrix.pg_version }}" ${{ steps.updates.outputs.previous }}
|
||||||
run: |
|
|
||||||
docker run -d --name "$TEST" -e POSTGRES_PASSWORD=testpass "$IMAGE:pg${{ matrix.pg_version }}" >/dev/null
|
|
||||||
for i in $(seq 1 90); do
|
|
||||||
if docker logs "$TEST" 2>&1 | grep -q "PostgreSQL init process complete" \
|
|
||||||
&& docker exec "$TEST" pg_isready -U postgres -q; then
|
|
||||||
echo "PostgreSQL prêt en ${i} s"; exit 0
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
docker logs "$TEST" 2>&1 | tail -30
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
- name: ⚙️ Setup Tests
|
# Poussée seulement si les tests sont passés.
|
||||||
run: docker cp tests/ "$TEST":/tmp/tests/
|
|
||||||
|
|
||||||
- name: "🧪 Test 1: Configuration"
|
|
||||||
run: docker exec "$TEST" psql -U postgres -f /tmp/tests/test-1-config.sql
|
|
||||||
|
|
||||||
- name: "🧪 Test 2: TimescaleDB"
|
|
||||||
run: docker exec "$TEST" psql -U postgres -f /tmp/tests/test-2-timescale.sql
|
|
||||||
|
|
||||||
- name: "🧪 Test 3: VectorChord"
|
|
||||||
run: docker exec "$TEST" psql -U postgres -f /tmp/tests/test-3-vector.sql
|
|
||||||
|
|
||||||
- name: "🧪 Test 4: Hybrid Scenarios"
|
|
||||||
run: docker exec "$TEST" psql -U postgres -f /tmp/tests/test-4-hybrid.sql
|
|
||||||
|
|
||||||
- name: "🧪 Test 5: PostGIS"
|
|
||||||
run: docker exec "$TEST" psql -U postgres -f /tmp/tests/test-5-postgis.sql
|
|
||||||
|
|
||||||
- name: 🧹 Cleanup test container
|
|
||||||
if: always()
|
|
||||||
run: docker rm -f "$TEST" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
# Poussée seulement si tous les tests sont passés (étapes précédentes sans `if: always()`).
|
|
||||||
- name: 🚀 Push Docker image
|
- name: 🚀 Push Docker image
|
||||||
|
if: steps.updates.outputs.build == 'true'
|
||||||
run: docker push -q "$IMAGE:pg${{ matrix.pg_version }}"
|
run: docker push -q "$IMAGE:pg${{ matrix.pg_version }}"
|
||||||
|
|
||||||
|
# Voir docs/decisions/0006-latest-reste-sur-postgresql-17.md.
|
||||||
- name: 🏷️ Tag latest (PG17 only)
|
- name: 🏷️ Tag latest (PG17 only)
|
||||||
if: matrix.pg_version == 17
|
if: steps.updates.outputs.build == 'true' && matrix.pg_version == 17
|
||||||
run: |
|
run: |
|
||||||
docker tag "$IMAGE:pg17" "$IMAGE:latest"
|
docker tag "$IMAGE:pg17" "$IMAGE:latest"
|
||||||
docker push -q "$IMAGE:latest"
|
docker push -q "$IMAGE:latest"
|
||||||
|
|
||||||
# Cache borné : il ne doit pas remplir le disque de Tower (la compilation de TimescaleDB
|
# Cache borné : il ne doit pas remplir le disque de Tower.
|
||||||
# pour trois versions de PostgreSQL y tient largement).
|
|
||||||
- name: 🧹 Cleanup Docker images
|
- name: 🧹 Cleanup Docker images
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
|
docker rmi "$PREVIOUS" >/dev/null 2>&1 || true
|
||||||
docker builder prune -f --keep-storage 8GB >/dev/null
|
docker builder prune -f --keep-storage 8GB >/dev/null
|
||||||
docker image prune -f >/dev/null
|
docker image prune -f >/dev/null
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
# postgres-ts-vectors — consignes
|
||||||
|
|
||||||
|
Image Docker de PostgreSQL avec TimescaleDB, VectorChord, pgvector et PostGIS, publiée pour
|
||||||
|
PostgreSQL 15 à 18 dans le registre Gitea. Le dépôt ne contient que la construction de
|
||||||
|
l'image, ses scripts de démarrage et ses tests.
|
||||||
|
Architecture : [docs/architecture.md](docs/architecture.md). Choix de conception :
|
||||||
|
[docs/decisions/](docs/decisions/). Points ouverts : [docs/a-faire.md](docs/a-faire.md).
|
||||||
|
|
||||||
|
## Commandes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Construire une version (PG_VERSION : 15, 16, 17 ou 18 ; 18 par défaut)
|
||||||
|
docker build --build-arg PG_VERSION=17 -t postgres-ts-vectors:pg17 .
|
||||||
|
|
||||||
|
# Rejouer la mise à niveau des paquets, que le cache garderait sinon telle quelle
|
||||||
|
docker build --build-arg PG_VERSION=17 --build-arg APT_REFRESH=$(date +%F) -t postgres-ts-vectors:pg17 .
|
||||||
|
|
||||||
|
# Essayer une autre version de TimescaleDB sans toucher au Dockerfile
|
||||||
|
docker build --build-arg PG_VERSION=17 --build-arg TIMESCALEDB_VERSION=2.30.2 -t postgres-ts-vectors:essai .
|
||||||
|
|
||||||
|
# Tests de l'image
|
||||||
|
./tests.sh postgres-ts-vectors:pg17
|
||||||
|
|
||||||
|
# Tests, plus reprise d'une base créée par l'image publiée
|
||||||
|
docker pull gitea.killinger.fr/maxime.killinger/postgres-ts-vectors:pg17
|
||||||
|
./tests.sh postgres-ts-vectors:pg17 gitea.killinger.fr/maxime.killinger/postgres-ts-vectors:pg17
|
||||||
|
|
||||||
|
# Mises à jour qu'apt propose à une image publiée (ce que la CI regarde chaque nuit)
|
||||||
|
docker run --rm --entrypoint sh gitea.killinger.fr/maxime.killinger/postgres-ts-vectors:pg17 -c \
|
||||||
|
'apt-get update -qq && apt-get -s upgrade --with-new-pkgs | grep ^Inst'
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Tests** : `tests.sh IMAGE [IMAGE_PRECEDENTE]`, en local comme en CI. Il démarre l'image
|
||||||
|
avec un utilisateur et une base autres que `postgres`, vérifie que le serveur a la version
|
||||||
|
majeure de l'étiquette, joue `tests/test-*.sql` dans l'ordre, redémarre le serveur, puis,
|
||||||
|
si une image précédente est donnée, crée une base avec elle (`tests/reprise-1-semis.sql`)
|
||||||
|
et la reprend avec l'image testée (`tests/reprise-2-controle.sql`). Conteneurs et volume
|
||||||
|
sont jetables.
|
||||||
|
- **Lint** : aucun outil configuré.
|
||||||
|
- **CI** (`.gitea/workflows/docker.yml`) : construit, teste et publie les quatre versions à
|
||||||
|
chaque push sur `main`, sauf si tous les fichiers modifiés sont des `.md` ou sous `docs/`
|
||||||
|
(`paths-ignore`). Chaque nuit, elle teste les images publiées et reconstruit celles qui ont
|
||||||
|
des mises à jour. Détail : [docs/exploitation/publication.md](docs/exploitation/publication.md).
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- **Une image par version majeure**, construite par le même Dockerfile avec `PG_VERSION`.
|
||||||
|
Rien n'y est propre à une version : ce qui en dépend se lit dans `PG_MAJOR`, fourni par
|
||||||
|
l'image de base.
|
||||||
|
- **Versions épinglées dans le Dockerfile**
|
||||||
|
([0003](docs/decisions/0003-versions-de-timescaledb-et-vectorchord-epinglees.md)) :
|
||||||
|
`TIMESCALEDB_VERSION` et `VCHORD_VERSION`. Les changer suit la procédure de
|
||||||
|
[docs/exploitation/publication.md](docs/exploitation/publication.md), et met à jour le
|
||||||
|
tableau et les badges du `README.md`.
|
||||||
|
- **Le reste vient d'apt, à jour**
|
||||||
|
([0002](docs/decisions/0002-paquets-a-jour-et-en-tetes-du-serveur.md)) : PostgreSQL,
|
||||||
|
pgvector, PostGIS, Debian. Aucune version de paquet n'est écrite dans le Dockerfile, sauf
|
||||||
|
les en-têtes du serveur, épinglés sur la version du serveur installé.
|
||||||
|
- **Nouvelle extension** : l'installer dans l'étape finale du Dockerfile, l'ajouter à
|
||||||
|
`init-extensions.sh`, à la liste contrôlée par `tests/test-1-config.sql`, lui écrire un
|
||||||
|
`tests/test-N-nom.sql`, et l'ajouter aux deux fichiers `tests/reprise-*.sql`.
|
||||||
|
- **Nouvelle version majeure de PostgreSQL** : l'ajouter à la matrice de la CI, au tableau
|
||||||
|
et au badge du `README.md`, et vérifier le point de montage du volume
|
||||||
|
([docs/architecture.md](docs/architecture.md)).
|
||||||
|
- **Tests SQL** : chaque fichier commence par `\set ON_ERROR_STOP on` et lève une exception
|
||||||
|
quand un contrôle échoue. Ils ne supposent ni l'utilisateur ni la base `postgres`.
|
||||||
|
- **CI sans action externe** ([0004](docs/decisions/0004-ci-sans-action-externe.md)) : des
|
||||||
|
commandes `git` et `docker`, jamais de `uses:`.
|
||||||
|
- **Commentaires** : en français. Le « pourquoi » local reste à côté du code. Les tests SQL
|
||||||
|
`tests/test-*.sql` et les scripts de démarrage sont encore commentés en anglais : on les
|
||||||
|
traduit quand on les modifie.
|
||||||
|
|
||||||
|
## Pièges connus
|
||||||
|
|
||||||
|
- **Changer la version de TimescaleDB rend une base existante inutilisable jusqu'à sa mise
|
||||||
|
à niveau.** L'image ne contient que la bibliothèque de sa version : toute requête échoue
|
||||||
|
avec `could not access file "$libdir/timescaledb-2.28.3"` tant que
|
||||||
|
`ALTER EXTENSION timescaledb UPDATE` n'a pas été exécuté, en première instruction d'une
|
||||||
|
session `psql -X`. `pg_isready` répond pourtant, et le conteneur paraît sain.
|
||||||
|
- **Des en-têtes plus récents que le serveur cassent TimescaleDB au chargement**
|
||||||
|
(`undefined symbol: palloc0_mul`), sans erreur à la compilation. C'est ce qui arrive si
|
||||||
|
l'on installe `postgresql-server-dev` sans mettre le serveur à niveau.
|
||||||
|
- **La couche `apt-get upgrade` est mise en cache comme les autres.** Une construction locale
|
||||||
|
ne reprend les mises à jour qu'avec un nouvel `APT_REFRESH`.
|
||||||
|
- **`PG_VERSION` a deux sens.** Avant le premier `FROM`, c'est l'argument de construction
|
||||||
|
(`18`). Dans les étapes, c'est la variable d'environnement de l'image de base, la version
|
||||||
|
du paquet au moment où TensorChord l'a construite (`18.3-1.pgdg12+1`). Elle reste telle
|
||||||
|
quelle dans l'image publiée, alors que le serveur est mis à niveau : la version du serveur
|
||||||
|
se lit par `postgres --version`.
|
||||||
|
- **Le point de montage du volume change avec PostgreSQL 18**
|
||||||
|
([docs/architecture.md](docs/architecture.md)). Jusqu'à 17, un volume monté sur
|
||||||
|
`/var/lib/postgresql` reste vide : les données partent dans un volume anonyme.
|
||||||
|
- **`init-extensions.sh` ne s'exécute qu'à l'initialisation d'un volume vide.** Une extension
|
||||||
|
ajoutée à l'image n'apparaît pas dans une base existante : il faut l'y créer à la main.
|
||||||
|
- **Remplacer la commande du conteneur remplace `shared_preload_libraries`.** Sans
|
||||||
|
`timescaledb,vchord`, les deux extensions ne se chargent pas.
|
||||||
|
- **L'enveloppe du point d'entrée parcourt tout le volume à chaque démarrage**
|
||||||
|
(`chown -R`) : le démarrage s'allonge avec le nombre de fichiers de la base.
|
||||||
|
- **Le serveur temporaire de l'initialisation répond déjà aux requêtes**, par le socket,
|
||||||
|
avant que les extensions soient créées. Un test qui attend seulement une réponse démarre
|
||||||
|
trop tôt : `tests.sh` attend aussi `PostgreSQL init process complete` dans le journal.
|
||||||
|
- **`docker logs … | grep -q` échoue sous `pipefail`** alors que le motif est trouvé :
|
||||||
|
`grep -q` ferme le tube à la première correspondance. `tests.sh` lit le journal en entier
|
||||||
|
avant de chercher.
|
||||||
|
- **Une exécution nocturne qui échoue aux tests ne publie rien et recompile la nuit
|
||||||
|
suivante** : la clé `APT_REFRESH` n'est gardée que par l'image publiée.
|
||||||
|
- **Chaque reconstruction déclenchée par apt recompile TimescaleDB** : l'étape de compilation
|
||||||
|
part de l'étape mise à niveau.
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
- Messages de commit en français. **Jamais de trailer `Co-Authored-By`.**
|
||||||
|
- **Ne pas pousser sans demande explicite** : un push sur `main` republie les quatre images,
|
||||||
|
dont `latest`, que d'autres projets utilisent comme base de données.
|
||||||
|
- En cas d'échec de push : retenter. Après deux échecs rapprochés, parler d'indisponibilité
|
||||||
|
probablement temporaire de Gitea, sans mettre en cause les identifiants.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- `README.md` : la vitrine du dépôt. Il résume et renvoie, le détail vit ici et dans `docs/`.
|
||||||
|
- `CLAUDE.md` : commandes, conventions, pièges connus.
|
||||||
|
- `docs/architecture.md` : étapes de construction, démarrage, données.
|
||||||
|
- `docs/exploitation/` : CI, mises à jour, changement de version.
|
||||||
|
- `docs/decisions/` : décisions numérotées et figées.
|
||||||
|
- `docs/a-faire.md` : points ouverts.
|
||||||
|
|
||||||
|
Règles :
|
||||||
|
|
||||||
|
1. **Un fait, un seul endroit.** Ailleurs, un lien, jamais une copie.
|
||||||
|
2. **La doc décrit le présent.** Pas de dates, de « corrigé le », ni de récit. L'historique
|
||||||
|
va dans les messages de commit, le « pourquoi » d'un choix dans une décision.
|
||||||
|
3. **Une décision est numérotée et figée** (`0001-titre.md`). On ne la modifie pas : une
|
||||||
|
nouvelle décision la remplace et le dit.
|
||||||
|
4. **`a-faire.md` ne contient que l'ouvert.** Un point terminé est supprimé, pas coché.
|
||||||
|
5. **La doc est mise à jour dans le même commit que le code qu'elle décrit.** Sinon, la
|
||||||
|
tâche n'est pas terminée.
|
||||||
|
6. **Un commentaire cite un document par son nom de fichier**, jamais par un numéro de
|
||||||
|
section.
|
||||||
|
7. **Un fichier au-delà de ~300 lignes se découpe.**
|
||||||
+42
-18
@@ -1,24 +1,43 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
# Versions épinglées : docs/decisions/0003-versions-de-timescaledb-et-vectorchord-epinglees.md
|
||||||
ARG PG_VERSION=18
|
ARG PG_VERSION=18
|
||||||
|
ARG VCHORD_VERSION=1.1.1
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Builder stage - Compile TimescaleDB
|
# Étape base — image VectorChord, paquets mis à niveau
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
FROM tensorchord/vchord-postgres:pg${PG_VERSION}-v1.1.1 AS builder
|
FROM tensorchord/vchord-postgres:pg${PG_VERSION}-v${VCHORD_VERSION} AS base
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
# Install build dependencies
|
# L'image de base n'est republiée qu'à chaque version de VectorChord : les versions mineures
|
||||||
|
# de PostgreSQL, pgvector et les correctifs Debian viennent d'apt.
|
||||||
|
# APT_REFRESH est une clé de cache : la changer rejoue cette couche, ce que fait la CI quand
|
||||||
|
# des mises à jour sont en attente. Voir docs/decisions/0002-paquets-a-jour-et-en-tetes-du-serveur.md.
|
||||||
|
ARG APT_REFRESH=
|
||||||
|
RUN echo "apt refresh: ${APT_REFRESH:-none}" \
|
||||||
|
&& apt-get update \
|
||||||
|
&& DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --with-new-pkgs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Étape builder — compilation de TimescaleDB
|
||||||
|
# =============================================================================
|
||||||
|
FROM base AS builder
|
||||||
|
|
||||||
|
# Les en-têtes du serveur sont épinglés sur la version du serveur installé : des en-têtes plus
|
||||||
|
# récents font référencer à TimescaleDB des symboles que le serveur n'exporte pas
|
||||||
|
# (`palloc0_mul` avec PostgreSQL 18).
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
git \
|
git \
|
||||||
build-essential \
|
build-essential \
|
||||||
cmake \
|
cmake \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
libkrb5-dev \
|
libkrb5-dev \
|
||||||
postgresql-server-dev-$PG_MAJOR \
|
postgresql-server-dev-$PG_MAJOR="$(dpkg-query -W -f='${Version}' postgresql-$PG_MAJOR)" \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Build TimescaleDB from source (stable release)
|
# TimescaleDB compilé depuis l'étiquette de la version stable
|
||||||
ARG TIMESCALEDB_VERSION=2.28.3
|
ARG TIMESCALEDB_VERSION=2.28.3
|
||||||
WORKDIR /build/timescaledb
|
WORKDIR /build/timescaledb
|
||||||
RUN git clone --branch ${TIMESCALEDB_VERSION} --depth 1 https://github.com/timescale/timescaledb.git . \
|
RUN git clone --branch ${TIMESCALEDB_VERSION} --depth 1 https://github.com/timescale/timescaledb.git . \
|
||||||
@@ -26,45 +45,50 @@ RUN git clone --branch ${TIMESCALEDB_VERSION} --depth 1 https://github.com/times
|
|||||||
&& cd build && make -j$(nproc) && make install DESTDIR=/tmp/timescaledb
|
&& cd build && make -j$(nproc) && make install DESTDIR=/tmp/timescaledb
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Final stage - Clean runtime image
|
# Étape finale — image publiée, sans outils de compilation
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
FROM tensorchord/vchord-postgres:pg${PG_VERSION}-v1.1.1 AS final
|
FROM base AS final
|
||||||
|
|
||||||
# OCI Labels
|
# Labels OCI fixes. Ceux qui dépendent de la construction (révision, version, date) sont posés
|
||||||
|
# par la CI.
|
||||||
LABEL org.opencontainers.image.title="TimescaleDB + VectorChord + pgvector + PostGIS"
|
LABEL org.opencontainers.image.title="TimescaleDB + VectorChord + pgvector + PostGIS"
|
||||||
LABEL org.opencontainers.image.description="PostgreSQL with TimescaleDB, VectorChord, pgvector, and PostGIS extensions pre-installed"
|
LABEL org.opencontainers.image.description="PostgreSQL with TimescaleDB, VectorChord, pgvector, and PostGIS extensions pre-installed"
|
||||||
LABEL org.opencontainers.image.source="https://gitea.killinger.fr/maxime.killinger/postgres-ts-vectors"
|
LABEL org.opencontainers.image.source="https://gitea.killinger.fr/maxime.killinger/postgres-ts-vectors"
|
||||||
LABEL org.opencontainers.image.vendor="Maxime Killinger"
|
LABEL org.opencontainers.image.vendor="Maxime Killinger"
|
||||||
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
||||||
LABEL org.opencontainers.image.base.name="tensorchord/vchord-postgres:pg${PG_VERSION}-v1.1.1"
|
# PG_MAJOR et non PG_VERSION : dans cette étape, PG_VERSION est la variable d'environnement de
|
||||||
|
# l'image de base (la version complète du paquet), pas l'argument de construction.
|
||||||
|
ARG VCHORD_VERSION
|
||||||
|
LABEL org.opencontainers.image.base.name="tensorchord/vchord-postgres:pg${PG_MAJOR}-v${VCHORD_VERSION}"
|
||||||
|
|
||||||
# Copy TimescaleDB from builder
|
# TimescaleDB, depuis l'étape de compilation
|
||||||
COPY --from=builder /tmp/timescaledb/usr/lib/postgresql/ /usr/lib/postgresql/
|
COPY --from=builder /tmp/timescaledb/usr/lib/postgresql/ /usr/lib/postgresql/
|
||||||
COPY --from=builder /tmp/timescaledb/usr/share/postgresql/ /usr/share/postgresql/
|
COPY --from=builder /tmp/timescaledb/usr/share/postgresql/ /usr/share/postgresql/
|
||||||
|
|
||||||
USER root
|
# PostGIS
|
||||||
|
|
||||||
# Install PostGIS
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
postgresql-$PG_MAJOR-postgis-3 \
|
postgresql-$PG_MAJOR-postgis-3 \
|
||||||
postgresql-$PG_MAJOR-postgis-3-scripts \
|
postgresql-$PG_MAJOR-postgis-3-scripts \
|
||||||
postgis \
|
postgis \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Add init script for auto-extension creation
|
# Création des extensions à l'initialisation de la base
|
||||||
COPY init-extensions.sh /docker-entrypoint-initdb.d/
|
COPY init-extensions.sh /docker-entrypoint-initdb.d/
|
||||||
RUN sed -i 's/\r$//' /docker-entrypoint-initdb.d/init-extensions.sh && \
|
RUN sed -i 's/\r$//' /docker-entrypoint-initdb.d/init-extensions.sh && \
|
||||||
chmod +x /docker-entrypoint-initdb.d/init-extensions.sh
|
chmod +x /docker-entrypoint-initdb.d/init-extensions.sh
|
||||||
|
|
||||||
# Add entrypoint wrapper to fix volume permissions at runtime
|
# Enveloppe du point d'entrée : droits du volume, puis point d'entrée d'origine
|
||||||
COPY docker-entrypoint-wrapper.sh /usr/local/bin/
|
COPY docker-entrypoint-wrapper.sh /usr/local/bin/
|
||||||
RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint-wrapper.sh && \
|
RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint-wrapper.sh && \
|
||||||
chmod +x /usr/local/bin/docker-entrypoint-wrapper.sh
|
chmod +x /usr/local/bin/docker-entrypoint-wrapper.sh
|
||||||
|
|
||||||
# Healthcheck
|
# Sonde avec l'utilisateur et la base du conteneur : sonder en `postgres` écrit
|
||||||
|
# `FATAL: role "postgres" does not exist` au journal toutes les 30 s dès que POSTGRES_USER
|
||||||
|
# est défini.
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
|
||||||
CMD pg_isready -U postgres || exit 1
|
CMD pg_isready -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-${POSTGRES_USER:-postgres}}" || exit 1
|
||||||
|
|
||||||
# Run as root initially, wrapper will fix permissions then switch to postgres
|
# Démarre en root : l'enveloppe corrige les droits, le point d'entrée d'origine passe ensuite
|
||||||
|
# à l'utilisateur postgres.
|
||||||
ENTRYPOINT ["docker-entrypoint-wrapper.sh"]
|
ENTRYPOINT ["docker-entrypoint-wrapper.sh"]
|
||||||
CMD ["postgres", "-c", "shared_preload_libraries=timescaledb,vchord"]
|
CMD ["postgres", "-c", "shared_preload_libraries=timescaledb,vchord"]
|
||||||
@@ -1,97 +1,134 @@
|
|||||||
# TimescaleDB + VectorChord + pgvector + PostGIS
|
<h1 align="center">postgres-ts-vectors</h1>
|
||||||
|
<p align="center">Image Docker de PostgreSQL avec TimescaleDB, VectorChord, pgvector et PostGIS déjà installés.</p>
|
||||||
|
|
||||||
[](https://gitea.killinger.fr/maxime.killinger/postgres-ts-vectors/actions)
|
<p align="center">
|
||||||
|
<a href="https://gitea.killinger.fr/maxime.killinger/postgres-ts-vectors/actions?workflow=docker.yml"><img src="https://gitea.killinger.fr/maxime.killinger/postgres-ts-vectors/actions/workflows/docker.yml/badge.svg?branch=main" alt="CI"></a>
|
||||||
|
<img src="https://img.shields.io/badge/PostgreSQL-15%20%C2%B7%2016%20%C2%B7%2017%20%C2%B7%2018-4169E1?style=flat-square&logo=postgresql&logoColor=white" alt="PostgreSQL 15, 16, 17 et 18">
|
||||||
|
<img src="https://img.shields.io/badge/TimescaleDB-2.28.3-FDB515?style=flat-square&logo=timescale&logoColor=black" alt="TimescaleDB 2.28.3">
|
||||||
|
<img src="https://img.shields.io/badge/VectorChord-1.1.1-6E56CF?style=flat-square" alt="VectorChord 1.1.1">
|
||||||
|
<img src="https://img.shields.io/badge/pgvector-0.8-336791?style=flat-square" alt="pgvector 0.8">
|
||||||
|
<img src="https://img.shields.io/badge/PostGIS-3-5CAE58?style=flat-square" alt="PostGIS 3">
|
||||||
|
<img src="https://img.shields.io/badge/Docker-une%20image%20par%20version-2496ED?style=flat-square&logo=docker&logoColor=white" alt="Docker, une image par version de PostgreSQL">
|
||||||
|
</p>
|
||||||
|
|
||||||
Docker image combining **PostgreSQL** with four powerful extensions:
|
## Présentation
|
||||||
|
|
||||||
- **[TimescaleDB](https://www.timescale.com/)** - Time-series database extension
|
Une image par version majeure de PostgreSQL, prête à servir des séries temporelles, de la
|
||||||
- **[VectorChord](https://vectorchord.ai/)** - High-performance vector similarity search
|
recherche vectorielle et des données géographiques dans la même base :
|
||||||
- **[pgvector](https://github.com/pgvector/pgvector)** - Open-source vector similarity search
|
|
||||||
- **[PostGIS](https://postgis.net/)** - Geographic objects support
|
|
||||||
|
|
||||||
## Supported PostgreSQL Versions
|
- **[TimescaleDB](https://www.timescale.com/)** : séries temporelles (hypertables) ;
|
||||||
|
- **[VectorChord](https://vectorchord.ai/)** et **[pgvector](https://github.com/pgvector/pgvector)** :
|
||||||
|
type `vector` et index de recherche par similarité ;
|
||||||
|
- **[PostGIS](https://postgis.net/)** : objets géographiques.
|
||||||
|
|
||||||
| Tag | PostgreSQL | TimescaleDB | VectorChord | pgvector | PostGIS |
|
Les extensions sont créées au premier démarrage, dans la base du conteneur. L'image part de
|
||||||
|-----|------------|-------------|-------------|----------|---------|
|
celle de VectorChord, mise à niveau à la construction : [docs/architecture.md](docs/architecture.md).
|
||||||
| `pg15` | 15 | 2.28.3 | 1.1.1 | 0.8.1 | 3.x |
|
|
||||||
| `pg16` | 16 | 2.28.3 | 1.1.1 | 0.8.1 | 3.x |
|
|
||||||
| `pg17`, `latest` | 17 | 2.28.3 | 1.1.1 | 0.8.1 | 3.x |
|
|
||||||
|
|
||||||
## Quick Start
|
## Images
|
||||||
|
|
||||||
|
Publiées sous `gitea.killinger.fr/maxime.killinger/postgres-ts-vectors`.
|
||||||
|
|
||||||
|
| Étiquette | PostgreSQL | TimescaleDB | VectorChord | pgvector | PostGIS |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `pg15` | 15 | 2.28.3 | 1.1.1 | 0.8 | 3 |
|
||||||
|
| `pg16` | 16 | 2.28.3 | 1.1.1 | 0.8 | 3 |
|
||||||
|
| `pg17`, `latest` | 17 | 2.28.3 | 1.1.1 | 0.8 | 3 |
|
||||||
|
| `pg18` | 18 | 2.28.3 | 1.1.1 | 0.8 | 3 |
|
||||||
|
|
||||||
|
TimescaleDB et VectorChord sont épinglés. PostgreSQL, pgvector et PostGIS suivent leurs
|
||||||
|
versions mineures : les images sont reconstruites dès qu'apt en propose
|
||||||
|
([Mises à jour](#mises-à-jour)). Les versions exactes se lisent dans un conteneur :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ma-base psql -U postgres -Atc "select version()" \
|
||||||
|
-c "select extname, extversion from pg_extension"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Démarrage rapide
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
docker run -d \
|
||||||
--name my-postgres \
|
--name ma-base \
|
||||||
-e POSTGRES_PASSWORD=mysecretpassword \
|
-e POSTGRES_PASSWORD=motdepasse \
|
||||||
-v postgres_data:/var/lib/postgresql \
|
-v donnees_pg:/var/lib/postgresql \
|
||||||
-p 5432:5432 \
|
-p 5432:5432 \
|
||||||
gitea.killinger.fr/maxime.killinger/postgres-ts-vectors:latest
|
gitea.killinger.fr/maxime.killinger/postgres-ts-vectors:pg18
|
||||||
```
|
```
|
||||||
|
|
||||||
All extensions are **automatically created** on first startup.
|
Le point de montage du volume dépend de la version de PostgreSQL :
|
||||||
|
|
||||||
## Data Persistence
|
| Étiquette | Monter le volume sur |
|
||||||
|
|---|---|
|
||||||
|
| `pg15`, `pg16`, `pg17`, `latest` | `/var/lib/postgresql/data` |
|
||||||
|
| `pg18` | `/var/lib/postgresql` |
|
||||||
|
|
||||||
> [!IMPORTANT]
|
Sur l'autre chemin, les données d'une image jusqu'à PostgreSQL 17 partent dans un volume
|
||||||
> Starting with PostgreSQL 18, mount volumes at `/var/lib/postgresql` (not `/var/lib/postgresql/data`).
|
anonyme, perdu quand le conteneur est recréé, et l'image `pg18` refuse de démarrer.
|
||||||
> Data will be stored in a versioned subdirectory (e.g., `/var/lib/postgresql/18/main`), which facilitates future upgrades with `pg_upgrade`.
|
|
||||||
|
|
||||||
## Environment Variables
|
## Configuration
|
||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Rôle | Défaut |
|
||||||
|----------|-------------|---------|
|
|---|---|---|
|
||||||
| `POSTGRES_PASSWORD` | PostgreSQL password (required) | - |
|
| `POSTGRES_PASSWORD` | Mot de passe, obligatoire | aucun |
|
||||||
| `POSTGRES_USER` | PostgreSQL user | `postgres` |
|
| `POSTGRES_USER` | Utilisateur | `postgres` |
|
||||||
| `POSTGRES_DB` | Default database | `postgres` |
|
| `POSTGRES_DB` | Base, où les extensions sont créées | le nom de l'utilisateur |
|
||||||
|
|
||||||
## Extensions Usage
|
Ce sont les variables de l'image officielle `postgres`. Une commande qui remplace celle de
|
||||||
|
l'image doit garder `-c shared_preload_libraries=timescaledb,vchord`, sans quoi TimescaleDB
|
||||||
|
et VectorChord ne sont pas chargés.
|
||||||
|
|
||||||
### TimescaleDB
|
## Utilisation
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- Create a hypertable
|
-- Série temporelle
|
||||||
CREATE TABLE conditions (
|
CREATE TABLE mesures (time TIMESTAMPTZ NOT NULL, capteur INTEGER, temperature DOUBLE PRECISION);
|
||||||
time TIMESTAMPTZ NOT NULL,
|
SELECT create_hypertable('mesures', 'time');
|
||||||
device_id INTEGER,
|
|
||||||
temperature DOUBLE PRECISION
|
|
||||||
);
|
|
||||||
|
|
||||||
SELECT create_hypertable('conditions', 'time');
|
-- Recherche vectorielle
|
||||||
```
|
CREATE TABLE documents (id SERIAL PRIMARY KEY, contenu TEXT, embedding vector(1536));
|
||||||
|
|
||||||
### VectorChord / pgvector
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Create a table with vector embeddings
|
|
||||||
CREATE TABLE documents (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
content TEXT,
|
|
||||||
embedding vector(1536)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create an index for fast similarity search
|
|
||||||
CREATE INDEX ON documents USING vchordrq (embedding vector_l2_ops);
|
CREATE INDEX ON documents USING vchordrq (embedding vector_l2_ops);
|
||||||
|
SELECT * FROM documents ORDER BY embedding <-> '[0.1, 0.2, ...]' LIMIT 10;
|
||||||
|
|
||||||
-- Query similar documents
|
-- Géographie
|
||||||
SELECT * FROM documents
|
CREATE TABLE lieux (id SERIAL PRIMARY KEY, geom geometry(Point, 4326));
|
||||||
ORDER BY embedding <-> '[0.1, 0.2, ...]'
|
SELECT ST_Distance(a.geom, b.geom) FROM lieux a, lieux b WHERE a.id = 1 AND b.id = 2;
|
||||||
LIMIT 10;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building Locally
|
## Mises à jour
|
||||||
|
|
||||||
|
Chaque nuit, la CI teste les images publiées et reconstruit celles pour lesquelles apt
|
||||||
|
propose des mises à jour : versions mineures de PostgreSQL, pgvector, PostGIS, correctifs
|
||||||
|
Debian. Une version de TimescaleDB ou de VectorChord se change à la main, car une base
|
||||||
|
existante doit alors être mise à niveau :
|
||||||
|
[docs/exploitation/publication.md](docs/exploitation/publication.md).
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build for PG18
|
docker build --build-arg PG_VERSION=18 -t postgres-ts-vectors:pg18 .
|
||||||
docker build --build-arg PG_VERSION=18 -t my-image:pg18 .
|
./tests.sh postgres-ts-vectors:pg18
|
||||||
|
|
||||||
# Build for PG16
|
|
||||||
docker build --build-arg PG_VERSION=16 -t my-image:pg16 .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
`tests.sh` démarre l'image, vérifie les extensions, les exerce, redémarre le serveur et,
|
||||||
|
avec une seconde image en argument, reprend une base créée par celle-ci. La CI lance la même
|
||||||
|
commande avant de publier. Détail dans [CLAUDE.md](CLAUDE.md).
|
||||||
|
|
||||||
This project is licensed under the Apache 2.0 License.
|
## Documentation
|
||||||
|
|
||||||
- TimescaleDB: [Timescale License](https://github.com/timescale/timescaledb/blob/main/LICENSE)
|
- [CLAUDE.md](CLAUDE.md) : commandes, conventions, pièges connus.
|
||||||
- VectorChord: [Apache 2.0](https://github.com/tensorchord/VectorChord/blob/main/LICENSE)
|
- [docs/architecture.md](docs/architecture.md) : étapes de construction, démarrage du
|
||||||
- pgvector: [PostgreSQL License](https://github.com/pgvector/pgvector/blob/master/LICENSE)
|
conteneur, emplacement des données.
|
||||||
|
- [docs/exploitation/publication.md](docs/exploitation/publication.md) : CI, mises à jour,
|
||||||
|
changement de version.
|
||||||
|
- [docs/decisions/](docs/decisions/) : choix de conception.
|
||||||
|
- [docs/a-faire.md](docs/a-faire.md) : points ouverts.
|
||||||
|
|
||||||
|
## Licences
|
||||||
|
|
||||||
|
Le dépôt est sous licence Apache 2.0. Les extensions gardent la leur :
|
||||||
|
[TimescaleDB](https://github.com/timescale/timescaledb/blob/main/LICENSE),
|
||||||
|
[VectorChord](https://github.com/tensorchord/VectorChord/blob/main/LICENSE),
|
||||||
|
[pgvector](https://github.com/pgvector/pgvector/blob/master/LICENSE),
|
||||||
|
[PostGIS](https://postgis.net/) (GPL v2 ou ultérieure).
|
||||||
|
|
||||||
|
Image réalisée par [Maxime Killinger](https://www.linkedin.com/in/maxime-killinger-8504b7b6).
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# À faire
|
||||||
|
|
||||||
|
Ce fichier ne contient que l'ouvert. Un point terminé est supprimé.
|
||||||
|
|
||||||
|
## Versions
|
||||||
|
|
||||||
|
- **TimescaleDB 2.30.2 est disponible**, l'image épingle 2.28.3. Vérifié sur PostgreSQL 17 :
|
||||||
|
la 2.30.2 compile, passe les tests et reprend une base créée en 2.28.3 après
|
||||||
|
`ALTER EXTENSION timescaledb UPDATE`. Reste à le vérifier pour 15, 16 et 18, puis à suivre
|
||||||
|
la procédure de [exploitation/publication.md](exploitation/publication.md), base de Health
|
||||||
|
comprise.
|
||||||
|
|
||||||
|
## CI
|
||||||
|
|
||||||
|
- **Le cache de construction est borné à 8 Go, et les couches des quatre versions en pèsent
|
||||||
|
environ 7,3** (1,8 Go par version, dont 1,1 pour les outils de compilation), sur un cache
|
||||||
|
partagé avec les autres dépôts du runner. Une couche évincée est reconstruite au push
|
||||||
|
suivant. À mesurer sur Tower avant de relever la borne.
|
||||||
|
- **Vérifier qu'un échec de l'exécution nocturne est notifié** par Gitea. Sinon, seuls la page
|
||||||
|
Actions et le badge du README le montrent.
|
||||||
|
|
||||||
|
## Dépôt
|
||||||
|
|
||||||
|
- **`init-timescale.sh` n'est copié dans aucune image.** À supprimer s'il ne sert plus.
|
||||||
|
- **`build.sh` nomme l'image `timescaledb-vectorchord`**, un nom que rien d'autre n'utilise.
|
||||||
|
- **`docker-entrypoint-wrapper.sh` refait `chown -R` sur tout le volume à chaque
|
||||||
|
démarrage.** Ne le faire que si le propriétaire n'est pas `postgres` éviterait le parcours
|
||||||
|
d'une grosse base.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
tensorchord/vchord-postgres:pgN-v1.1.1 PostgreSQL N, VectorChord, pgvector (Debian 12)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
base ........ apt-get upgrade : PostgreSQL (mineure), pgvector, Debian
|
||||||
|
├──▶ builder ... en-têtes du serveur + compilation de TimescaleDB
|
||||||
|
│ │ bibliothèques et scripts SQL
|
||||||
|
▼ ▼
|
||||||
|
final ....... TimescaleDB copié, PostGIS (apt), scripts de démarrage
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
gitea.killinger.fr/maxime.killinger/postgres-ts-vectors:pgN
|
||||||
|
```
|
||||||
|
|
||||||
|
Le même Dockerfile produit une image par version majeure de PostgreSQL, choisie par
|
||||||
|
l'argument `PG_VERSION`. Les trois étapes s'enchaînent ainsi parce que le serveur et ses
|
||||||
|
en-têtes doivent avoir la même version
|
||||||
|
([0002](decisions/0002-paquets-a-jour-et-en-tetes-du-serveur.md)).
|
||||||
|
|
||||||
|
## Fichiers
|
||||||
|
|
||||||
|
| Fichier | Rôle |
|
||||||
|
|---|---|
|
||||||
|
| `Dockerfile` | Construction de l'image, en trois étapes |
|
||||||
|
| `init-extensions.sh` | Création des extensions à l'initialisation de la base |
|
||||||
|
| `docker-entrypoint-wrapper.sh` | Point d'entrée : droits du volume, puis point d'entrée de l'image `postgres` |
|
||||||
|
| `tests.sh` | Tests de l'image, en local et en CI |
|
||||||
|
| `tests/test-*.sql` | Contrôles joués sur une base neuve, un fichier par extension ou combinaison |
|
||||||
|
| `tests/reprise-*.sql` | Données créées par l'image précédente, puis relues par la nouvelle |
|
||||||
|
| `build.sh` | Construction locale d'une version |
|
||||||
|
| `.gitea/workflows/docker.yml` | CI : construction, tests, publication, mises à jour nocturnes |
|
||||||
|
|
||||||
|
## D'où vient chaque composant
|
||||||
|
|
||||||
|
| Composant | Source | Version |
|
||||||
|
|---|---|---|
|
||||||
|
| PostgreSQL | image de base, puis `apt-get upgrade` (dépôt PGDG) | dernière mineure de la majeure |
|
||||||
|
| VectorChord | image de base | `VCHORD_VERSION` du Dockerfile |
|
||||||
|
| pgvector | image de base, puis `apt-get upgrade` (dépôt PGDG) | dernière disponible |
|
||||||
|
| TimescaleDB | compilé depuis l'étiquette Git de la version | `TIMESCALEDB_VERSION` du Dockerfile |
|
||||||
|
| PostGIS | paquet `postgresql-N-postgis-3` (dépôt PGDG) | dernière disponible |
|
||||||
|
|
||||||
|
Pourquoi cet assemblage :
|
||||||
|
[0001](decisions/0001-image-vectorchord-et-timescaledb-compile.md). Pourquoi deux versions
|
||||||
|
sont épinglées et pas les autres :
|
||||||
|
[0003](decisions/0003-versions-de-timescaledb-et-vectorchord-epinglees.md).
|
||||||
|
|
||||||
|
## Étapes de construction
|
||||||
|
|
||||||
|
- **`base`** part de l'image de VectorChord et met tous ses paquets à niveau. L'argument
|
||||||
|
`APT_REFRESH` ne sert qu'à invalider le cache de cette couche : sa valeur est affichée,
|
||||||
|
rien d'autre ne la lit.
|
||||||
|
- **`builder`** part de `base`, installe les outils de compilation et les en-têtes du
|
||||||
|
serveur dans la version exacte du serveur installé, puis compile TimescaleDB et l'installe
|
||||||
|
dans `/tmp/timescaledb`.
|
||||||
|
- **`final`** part de `base`, reçoit de `builder` les bibliothèques (`/usr/lib/postgresql/`)
|
||||||
|
et les scripts SQL (`/usr/share/postgresql/`) de TimescaleDB, installe PostGIS, copie les
|
||||||
|
deux scripts de démarrage et déclare la sonde de santé. Aucun outil de compilation n'y
|
||||||
|
entre.
|
||||||
|
|
||||||
|
Labels de l'image : titre, description, source, auteur, licence et image de base sont dans
|
||||||
|
le Dockerfile ; révision, version, date et clé `fr.killinger.apt-refresh` sont posés par la
|
||||||
|
CI ([exploitation/publication.md](exploitation/publication.md)).
|
||||||
|
|
||||||
|
## Démarrage du conteneur
|
||||||
|
|
||||||
|
1. `docker-entrypoint-wrapper.sh` démarre en root, crée `/var/lib/postgresql` s'il manque,
|
||||||
|
en donne la propriété à `postgres` et le ferme aux autres utilisateurs. Un volume monté
|
||||||
|
arrive en effet avec les droits de son hôte.
|
||||||
|
2. Il passe la main à `docker-entrypoint.sh`, le point d'entrée de l'image officielle
|
||||||
|
`postgres`, qui prend l'identité `postgres`.
|
||||||
|
3. Sur un volume vide, ce point d'entrée initialise la base, démarre un serveur temporaire
|
||||||
|
et exécute `init-extensions.sh`, qui crée `timescaledb`, `vector`, `vchord`, `postgis` et
|
||||||
|
`postgis_topology` dans la base `POSTGRES_DB`. Sur un volume déjà initialisé, rien de
|
||||||
|
cela n'a lieu.
|
||||||
|
4. Le serveur définitif démarre avec la commande de l'image :
|
||||||
|
`postgres -c shared_preload_libraries=timescaledb,vchord`. Les deux bibliothèques doivent
|
||||||
|
être préchargées pour que leurs extensions fonctionnent.
|
||||||
|
|
||||||
|
La sonde de santé interroge `pg_isready` toutes les 30 secondes, avec l'utilisateur et la
|
||||||
|
base du conteneur.
|
||||||
|
|
||||||
|
## Données
|
||||||
|
|
||||||
|
| Étiquette | Volume déclaré par l'image | Répertoire des données (`PGDATA`) |
|
||||||
|
|---|---|---|
|
||||||
|
| `pg15`, `pg16`, `pg17`, `latest` | `/var/lib/postgresql/data` | `/var/lib/postgresql/data` |
|
||||||
|
| `pg18` | `/var/lib/postgresql` | `/var/lib/postgresql/18/docker` |
|
||||||
|
|
||||||
|
Ces emplacements sont ceux de l'image officielle `postgres`, que l'image de base reprend.
|
||||||
|
À partir de PostgreSQL 18, le répertoire porte le numéro de la version majeure, pour que
|
||||||
|
deux versions cohabitent dans le même volume pendant un `pg_upgrade`. Une image `pg18` qui
|
||||||
|
trouve un volume monté sur `/var/lib/postgresql/data` refuse de démarrer.
|
||||||
|
|
||||||
|
Passer d'une version majeure à la suivante n'est pas un changement d'étiquette : les données
|
||||||
|
d'une version ne sont pas lisibles par une autre sans `pg_upgrade` ou sauvegarde et
|
||||||
|
restauration ([0006](decisions/0006-latest-reste-sur-postgresql-17.md)).
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# 0001 — Image de VectorChord en base, TimescaleDB compilé, PostGIS par apt
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
L'image doit réunir PostgreSQL, TimescaleDB, VectorChord, pgvector et PostGIS, pour plusieurs
|
||||||
|
versions majeures de PostgreSQL. TensorChord publie `tensorchord/vchord-postgres`, qui
|
||||||
|
contient déjà PostgreSQL, VectorChord et pgvector, construite sur l'image officielle
|
||||||
|
`postgres` (Debian 12, dépôt de paquets PGDG).
|
||||||
|
|
||||||
|
## Décision
|
||||||
|
|
||||||
|
- L'image part de `tensorchord/vchord-postgres:pgN-vX.Y.Z`.
|
||||||
|
- TimescaleDB est compilé depuis l'étiquette Git de sa version, dans une étape séparée. Seuls
|
||||||
|
ses bibliothèques et ses scripts SQL passent dans l'image finale.
|
||||||
|
- PostGIS est installé par le paquet `postgresql-N-postgis-3` du dépôt PGDG.
|
||||||
|
- Les extensions sont créées à l'initialisation de la base par un script de
|
||||||
|
`/docker-entrypoint-initdb.d/`, et la commande de l'image précharge `timescaledb` et
|
||||||
|
`vchord`.
|
||||||
|
|
||||||
|
## Conséquences
|
||||||
|
|
||||||
|
- Le même Dockerfile sert toutes les versions de PostgreSQL pour lesquelles TensorChord
|
||||||
|
publie une image.
|
||||||
|
- Une construction sans cache compile TimescaleDB, ce qui prend plusieurs minutes par
|
||||||
|
version.
|
||||||
|
- Les outils de compilation ne sont pas dans l'image publiée.
|
||||||
|
- Variables d'environnement, point d'entrée et emplacement des données sont ceux de l'image
|
||||||
|
officielle `postgres`.
|
||||||
|
|
||||||
|
## Attestée par
|
||||||
|
|
||||||
|
`Dockerfile` ; `init-extensions.sh` ; `tests/test-*.sql`.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# 0002 — Paquets mis à niveau à la construction, en-têtes épinglés sur le serveur
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
L'image de base n'est republiée qu'à chaque version de VectorChord. Entre deux, son
|
||||||
|
PostgreSQL prend du retard sur les versions mineures et les correctifs de sécurité : une
|
||||||
|
image de base contenait PostgreSQL 18.3 quand le dépôt PGDG en était à 18.6, avec une
|
||||||
|
trentaine d'autres paquets en retard.
|
||||||
|
|
||||||
|
La compilation de TimescaleDB installe les en-têtes du serveur par apt, donc dans la
|
||||||
|
dernière version. Compilé avec des en-têtes 18.6 puis chargé par un serveur 18.3,
|
||||||
|
TimescaleDB échouait à la création de l'extension (`undefined symbol: palloc0_mul`), sans
|
||||||
|
erreur à la compilation. PostgreSQL 18 avait été retiré de la CI pour cette raison.
|
||||||
|
|
||||||
|
## Décision
|
||||||
|
|
||||||
|
- Une étape `base`, commune à la compilation et à l'image finale, met tous les paquets à
|
||||||
|
niveau (`apt-get upgrade --with-new-pkgs`). Le serveur publié est donc à la dernière
|
||||||
|
version mineure.
|
||||||
|
- L'étape de compilation installe les en-têtes dans la version exacte du serveur installé,
|
||||||
|
lue par `dpkg-query`, et non dans la dernière disponible.
|
||||||
|
- La couche de mise à niveau porte une clé de cache, l'argument `APT_REFRESH`.
|
||||||
|
|
||||||
|
## Conséquences
|
||||||
|
|
||||||
|
- PostgreSQL 18 est de nouveau construit et publié.
|
||||||
|
- Serveur et en-têtes ne peuvent plus diverger : si la version voulue des en-têtes n'est
|
||||||
|
plus dans le dépôt, la construction échoue au lieu de produire une image qui ne charge
|
||||||
|
pas TimescaleDB.
|
||||||
|
- La variable d'environnement `PG_VERSION` héritée de l'image de base n'est plus la version
|
||||||
|
du serveur.
|
||||||
|
- Sans changement de `APT_REFRESH`, le cache garde la mise à niveau telle qu'elle a été
|
||||||
|
faite : c'est la CI qui décide quand la rejouer
|
||||||
|
([0005](0005-mises-a-jour-nocturnes-pilotees-par-apt.md)).
|
||||||
|
|
||||||
|
## Attestée par
|
||||||
|
|
||||||
|
`Dockerfile` (étapes `base` et `builder`) ; commit `dcf4169`, qui avait retiré PostgreSQL 18.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# 0003 — Versions de TimescaleDB et de VectorChord épinglées
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
TimescaleDB nomme sa bibliothèque d'après sa version (`timescaledb-2.28.3.so`), et une base
|
||||||
|
retient la version de l'extension qu'elle a installée. L'image ne contient que la
|
||||||
|
bibliothèque de la version compilée. Démarrée sur une base créée avec une autre version,
|
||||||
|
elle refuse toute requête (`could not access file "$libdir/timescaledb-2.28.3"`) jusqu'à
|
||||||
|
l'exécution de `ALTER EXTENSION timescaledb UPDATE`. Le serveur, lui, démarre et répond à
|
||||||
|
`pg_isready`.
|
||||||
|
|
||||||
|
Les versions mineures de PostgreSQL, pgvector et PostGIS n'ont pas cet effet : une base
|
||||||
|
existante redémarre sur la nouvelle image sans intervention.
|
||||||
|
|
||||||
|
## Décision
|
||||||
|
|
||||||
|
- `TIMESCALEDB_VERSION` et `VCHORD_VERSION` sont écrits dans le Dockerfile. La CI ne les fait
|
||||||
|
jamais avancer d'elle-même.
|
||||||
|
- Les changer est un commit, précédé du test de reprise et d'un avertissement aux
|
||||||
|
exploitants ([exploitation/publication.md](../exploitation/publication.md)).
|
||||||
|
- Tout le reste suit apt ([0002](0002-paquets-a-jour-et-en-tetes-du-serveur.md)).
|
||||||
|
|
||||||
|
## Conséquences
|
||||||
|
|
||||||
|
- Aucune republication automatique ne peut rendre une base inutilisable.
|
||||||
|
- TimescaleDB et VectorChord prennent du retard tant que personne ne change leur version.
|
||||||
|
- Le test de reprise de `tests.sh` commence par mettre les extensions à niveau : il vérifie
|
||||||
|
que la procédure fonctionne, pas que la base repart sans elle.
|
||||||
|
|
||||||
|
## Attestée par
|
||||||
|
|
||||||
|
`Dockerfile` ; `tests/reprise-2-controle.sql`.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# 0004 — CI sans action externe, cache du démon, une construction par version
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
La CI téléchargeait depuis GitHub, à chaque job, quatre actions (`checkout`,
|
||||||
|
`setup-buildx`, `login`, `build-push`), une vingtaine de secondes chacune, pour des
|
||||||
|
opérations que l'image du runner sait faire avec `git` et `docker`. `setup-buildx` créait un
|
||||||
|
constructeur neuf, sans cache : TimescaleDB était recompilé à chaque job, 7 à 9 minutes par
|
||||||
|
version. L'image était construite jusqu'à trois fois : pour les tests, pour la publication,
|
||||||
|
puis pour `latest`. Tout push relançait la CI, même quand il ne touchait que la documentation.
|
||||||
|
|
||||||
|
## Décision
|
||||||
|
|
||||||
|
- Récupération du seul commit concerné par `git fetch --depth 1`, connexion au registre par
|
||||||
|
`docker login`, construction par `docker build --pull` avec le démon Docker du runner,
|
||||||
|
dont le cache persiste d'un job à l'autre.
|
||||||
|
- Une seule construction par version : l'image testée est celle qui est poussée, et `latest`
|
||||||
|
est une seconde étiquette de la même image.
|
||||||
|
- Les tests sont une commande du dépôt, `tests.sh`, la même en local.
|
||||||
|
- `paths-ignore` sur `**.md` et `docs/**` : un push de documentation seule ne lance pas la CI.
|
||||||
|
- Cache de construction borné à 8 Go, images sans étiquette supprimées en fin de job.
|
||||||
|
|
||||||
|
## Conséquences
|
||||||
|
|
||||||
|
- La CI ne dépend plus de la disponibilité de GitHub pour ses actions. Elle en dépend encore
|
||||||
|
pour le code source de TimescaleDB, quand il faut le recompiler.
|
||||||
|
- La compilation n'est rejouée que si l'image de base, les paquets ou la version de
|
||||||
|
TimescaleDB changent.
|
||||||
|
- Le runner doit donner accès à un démon Docker, ce qui est le cas du runner commun aux
|
||||||
|
dépôts de Health et aux sites.
|
||||||
|
- Le cache est partagé avec les autres dépôts du runner, chacun le bornant à 8 Go.
|
||||||
|
|
||||||
|
## Attestée par
|
||||||
|
|
||||||
|
`.gitea/workflows/docker.yml` ; commit `2a0f84e` ; la même décision dans nk-environnement
|
||||||
|
(`0011-ci-sans-action-externe.md`) et dans les dépôts de Health.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# 0005 — Mises à jour nocturnes, déclenchées par ce qu'apt propose
|
||||||
|
|
||||||
|
Complète [0002](0002-paquets-a-jour-et-en-tetes-du-serveur.md) et
|
||||||
|
[0004](0004-ci-sans-action-externe.md).
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Les images ne changeaient qu'à un push. Entre deux, elles ne recevaient ni version mineure de
|
||||||
|
PostgreSQL ni correctif de sécurité. Le cache persistant du runner aggrave le problème : une
|
||||||
|
couche `apt-get upgrade` déjà construite n'est jamais rejouée. Reconstruire sans cache chaque
|
||||||
|
nuit recompilerait TimescaleDB quatre fois et republierait quatre images identiques aux
|
||||||
|
précédentes.
|
||||||
|
|
||||||
|
## Décision
|
||||||
|
|
||||||
|
- La CI tourne chaque nuit. Pour chaque version, elle tire l'image publiée et y simule
|
||||||
|
`apt-get upgrade --with-new-pkgs`.
|
||||||
|
- S'il reste des paquets à mettre à niveau, ou si l'image n'existe pas, elle construit avec
|
||||||
|
une nouvelle clé `APT_REFRESH`, teste et publie.
|
||||||
|
- Sinon elle ne construit ni ne publie : elle joue les tests sur l'image publiée.
|
||||||
|
- La clé d'une image est gardée dans son label `fr.killinger.apt-refresh`. Un push la
|
||||||
|
reprend quand l'image publiée est à jour, pour que le cache serve.
|
||||||
|
- Les versions de TimescaleDB et de VectorChord ne sont pas concernées
|
||||||
|
([0003](0003-versions-de-timescaledb-et-vectorchord-epinglees.md)).
|
||||||
|
|
||||||
|
## Conséquences
|
||||||
|
|
||||||
|
- Une version mineure de PostgreSQL ou un correctif Debian est publié la nuit qui suit son
|
||||||
|
arrivée dans apt, sans intervention.
|
||||||
|
- Une nuit sans mise à jour coûte une simulation apt et les tests, par version.
|
||||||
|
- L'état de référence est l'image publiée, pas le cache du runner : un cache perdu ne
|
||||||
|
provoque ni reconstruction ni publication inutiles.
|
||||||
|
- Tant qu'une reconstruction échoue aux tests, rien n'est publié et elle est retentée la nuit
|
||||||
|
suivante.
|
||||||
|
- La simulation et la construction utilisent la même commande apt : après une publication,
|
||||||
|
l'image n'a plus rien en attente et la nuit suivante ne reconstruit pas.
|
||||||
|
- Une republication de l'image de base sous la même étiquette n'est reprise qu'à la
|
||||||
|
prochaine reconstruction, par `--pull`.
|
||||||
|
|
||||||
|
## Attestée par
|
||||||
|
|
||||||
|
`.gitea/workflows/docker.yml` (étape « Check for updates ») ; `Dockerfile` (`APT_REFRESH`).
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# 0006 — `latest` reste sur PostgreSQL 17
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
L'image est publiée pour PostgreSQL 15 à 18. `latest` désigne PostgreSQL 17, et la base de
|
||||||
|
Health l'utilise, avec un volume monté sur `/var/lib/postgresql/data`. Les données d'une
|
||||||
|
version majeure ne sont pas lisibles par la suivante, et l'image `pg18` attend son volume sur
|
||||||
|
`/var/lib/postgresql`.
|
||||||
|
|
||||||
|
## Décision
|
||||||
|
|
||||||
|
- `latest` reste une seconde étiquette de `pg17`. L'arrivée de `pg18` ne la déplace pas.
|
||||||
|
- Un nouveau projet choisit une étiquette `pgN` explicite.
|
||||||
|
|
||||||
|
## Conséquences
|
||||||
|
|
||||||
|
- Aucun exploitant ne change de version majeure en tirant `latest`.
|
||||||
|
- `latest` ne désigne pas la version la plus récente de PostgreSQL.
|
||||||
|
- Déplacer `latest` demandera une nouvelle décision, après migration des bases qui
|
||||||
|
l'utilisent (`pg_upgrade`, ou sauvegarde et restauration) et changement de leur point de
|
||||||
|
montage.
|
||||||
|
|
||||||
|
## Attestée par
|
||||||
|
|
||||||
|
`.gitea/workflows/docker.yml` (étape « Tag latest ») ; `docker-compose.yml` de
|
||||||
|
health-workspace.
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# Publication et mises à jour
|
||||||
|
|
||||||
|
Les images sont construites, testées et publiées par la CI (`.gitea/workflows/docker.yml`),
|
||||||
|
jamais à la main. Un push sur `main` **est une publication** : il remplace les quatre images,
|
||||||
|
dont `latest`. Personne ne pousse sans demande explicite de l'utilisateur.
|
||||||
|
|
||||||
|
## Déclencheurs
|
||||||
|
|
||||||
|
| Déclencheur | Effet |
|
||||||
|
|---|---|
|
||||||
|
| Push sur `main` | Construction, tests et publication des quatre versions. Sans effet si tous les fichiers modifiés sont des `.md` ou sous `docs/` |
|
||||||
|
| Chaque nuit, 3 h 30 UTC | Tests des images publiées ; reconstruction et publication de celles qui ont des mises à jour |
|
||||||
|
| Lancement manuel (`workflow_dispatch`) | Comme un push |
|
||||||
|
|
||||||
|
## Chaîne de publication
|
||||||
|
|
||||||
|
Un job par version de PostgreSQL (15, 16, 17, 18), sans action externe
|
||||||
|
([0004](../decisions/0004-ci-sans-action-externe.md)) :
|
||||||
|
|
||||||
|
1. Récupération du seul commit concerné et connexion au registre.
|
||||||
|
2. **Recherche des mises à jour.** L'image publiée est tirée, et apt y simule une mise à
|
||||||
|
niveau. S'il reste des paquets à mettre à niveau, ou si l'image n'a jamais été publiée,
|
||||||
|
une nouvelle clé `APT_REFRESH` est tirée de l'heure. Sinon la clé de l'image publiée,
|
||||||
|
lue dans son label `fr.killinger.apt-refresh`, est reprise
|
||||||
|
([0005](../decisions/0005-mises-a-jour-nocturnes-pilotees-par-apt.md)).
|
||||||
|
3. **Construction** avec `docker build --pull`, par le démon Docker du runner. Avec une clé
|
||||||
|
reprise, tout sort du cache. Avec une clé nouvelle, les paquets sont mis à niveau et
|
||||||
|
TimescaleDB est recompilé. La nuit, cette étape est sautée quand l'image est à jour.
|
||||||
|
4. **Tests** par `tests.sh`, avec l'image publiée comme image précédente : la nouvelle image
|
||||||
|
doit reprendre une base créée par celle qu'elle remplace. La nuit, sans reconstruction,
|
||||||
|
c'est l'image publiée elle-même qui est testée.
|
||||||
|
5. **Publication** de `pgN`, seulement si les tests passent, puis de `latest` pour
|
||||||
|
PostgreSQL 17 ([0006](../decisions/0006-latest-reste-sur-postgresql-17.md)).
|
||||||
|
6. Nettoyage : cache de construction borné à 8 Go, images sans étiquette supprimées.
|
||||||
|
|
||||||
|
La CI ne déclenche aucun déploiement. Un conteneur passe à la nouvelle image quand son
|
||||||
|
exploitant la tire et le recrée.
|
||||||
|
|
||||||
|
| Secret du dépôt | Usage |
|
||||||
|
|---|---|
|
||||||
|
| `secrets.DOCKER_TOKEN` | Connexion au registre Gitea (compte `maxime.killinger`) |
|
||||||
|
|
||||||
|
## Ce qui se met à jour seul, et ce qui ne le fait pas
|
||||||
|
|
||||||
|
| Composant | Mise à jour |
|
||||||
|
|---|---|
|
||||||
|
| PostgreSQL (version mineure), pgvector, PostGIS, paquets Debian | Automatique, la nuit qui suit leur arrivée dans apt |
|
||||||
|
| TimescaleDB, VectorChord | À la main, dans le Dockerfile ([0003](../decisions/0003-versions-de-timescaledb-et-vectorchord-epinglees.md)) |
|
||||||
|
| Nouvelle version majeure de PostgreSQL | À la main, dans la matrice de la CI |
|
||||||
|
|
||||||
|
Une mise à jour automatique ne demande rien à l'exploitant d'une base : le conteneur recréé
|
||||||
|
repart sur ses données. Les extensions installées gardent la version de leurs objets SQL
|
||||||
|
tant qu'on ne les met pas à niveau, ce qui reste facultatif pour pgvector et PostGIS :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER EXTENSION vector UPDATE;
|
||||||
|
SELECT postgis_extensions_upgrade();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changer la version de TimescaleDB
|
||||||
|
|
||||||
|
1. Construire et tester en local contre l'image publiée, pour chaque version de PostgreSQL :
|
||||||
|
```bash
|
||||||
|
docker build --build-arg PG_VERSION=17 --build-arg TIMESCALEDB_VERSION=2.30.2 -t postgres-ts-vectors:essai .
|
||||||
|
docker pull gitea.killinger.fr/maxime.killinger/postgres-ts-vectors:pg17
|
||||||
|
./tests.sh postgres-ts-vectors:essai gitea.killinger.fr/maxime.killinger/postgres-ts-vectors:pg17
|
||||||
|
```
|
||||||
|
Une version de TimescaleDB qui abandonne une version de PostgreSQL échoue à la
|
||||||
|
compilation : cette version de PostgreSQL sort alors de la matrice, ou garde l'ancienne
|
||||||
|
version de TimescaleDB.
|
||||||
|
2. Changer `TIMESCALEDB_VERSION` dans le `Dockerfile`, le tableau et le badge du `README.md`.
|
||||||
|
3. Prévenir les exploitants **avant** de pousser. Dès qu'un conteneur est recréé sur la
|
||||||
|
nouvelle image, sa base refuse toute requête jusqu'à l'exécution de :
|
||||||
|
```bash
|
||||||
|
docker exec -it <conteneur> psql -X -U <utilisateur> -d <base> -c 'ALTER EXTENSION timescaledb UPDATE'
|
||||||
|
```
|
||||||
|
La commande est à passer dans chaque base qui a l'extension. `-X` et une session neuve
|
||||||
|
sont nécessaires : l'instruction doit être la première de la session.
|
||||||
|
|
||||||
|
## Changer la version de VectorChord
|
||||||
|
|
||||||
|
Elle fait partie de l'étiquette de l'image de base. Vérifier que TensorChord publie
|
||||||
|
`tensorchord/vchord-postgres:pgN-vX.Y.Z` pour les quatre versions de PostgreSQL, changer
|
||||||
|
`VCHORD_VERSION` dans le `Dockerfile`, le tableau et le badge du `README.md`, puis tester
|
||||||
|
comme pour TimescaleDB. Côté base : `ALTER EXTENSION vchord UPDATE`.
|
||||||
|
|
||||||
|
## Vérifier une image publiée
|
||||||
|
|
||||||
|
```bash
|
||||||
|
IMAGE=gitea.killinger.fr/maxime.killinger/postgres-ts-vectors:pg17
|
||||||
|
docker pull "$IMAGE"
|
||||||
|
|
||||||
|
# Commit, date de construction, clé de mise à jour, image de base
|
||||||
|
docker image inspect "$IMAGE" -f '{{json .Config.Labels}}'
|
||||||
|
|
||||||
|
# Version du serveur et des paquets d'extensions
|
||||||
|
docker run --rm --entrypoint sh "$IMAGE" -c \
|
||||||
|
'postgres --version; dpkg-query -W -f "\${Package} \${Version}\n" "postgresql-$PG_MAJOR-*" | grep " ."'
|
||||||
|
|
||||||
|
# La suite de tests, sur l'image publiée
|
||||||
|
./tests.sh "$IMAGE"
|
||||||
|
```
|
||||||
|
|
||||||
|
| Label | Contenu |
|
||||||
|
|---|---|
|
||||||
|
| `org.opencontainers.image.revision` | Commit du dépôt qui a construit l'image |
|
||||||
|
| `org.opencontainers.image.created` | Date de construction |
|
||||||
|
| `org.opencontainers.image.version` | Étiquette `pgN` |
|
||||||
|
| `org.opencontainers.image.base.name` | Image de VectorChord dont elle part |
|
||||||
|
| `fr.killinger.apt-refresh` | Clé de la dernière mise à niveau des paquets, à l'heure UTC |
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Tests de l'image, en local comme en CI.
|
||||||
|
#
|
||||||
|
# ./tests.sh IMAGE démarrage, extensions, tests SQL, redémarrage
|
||||||
|
# ./tests.sh IMAGE PRECEDENTE en plus : reprise d'une base créée par l'image PRECEDENTE
|
||||||
|
#
|
||||||
|
# Tout tourne dans des conteneurs et un volume jetables, sans port publié, supprimés à la fin.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
IMAGE="${1:?usage : tests.sh IMAGE [IMAGE_PRECEDENTE]}"
|
||||||
|
PRECEDENTE="${2:-}"
|
||||||
|
ID="ptvtest$$"
|
||||||
|
# Utilisateur et base autres que `postgres`, comme chez les consommateurs de l'image : les
|
||||||
|
# extensions doivent être créées dans POSTGRES_DB, par POSTGRES_USER.
|
||||||
|
OPTIONS=(-e POSTGRES_USER=test -e POSTGRES_PASSWORD=test -e POSTGRES_DB=test
|
||||||
|
--memory=768m --memory-swap=768m) # le runner de CI est limité en mémoire
|
||||||
|
|
||||||
|
nettoyer() {
|
||||||
|
docker rm -f -v "$ID" "$ID-reprise" >/dev/null 2>&1 || true
|
||||||
|
docker volume rm "$ID-donnees" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
trap nettoyer EXIT
|
||||||
|
|
||||||
|
sql() { docker exec -i "$1" psql -X -q -v ON_ERROR_STOP=1 -U test -d test "${@:2}"; }
|
||||||
|
|
||||||
|
# Trois sondes, de la plus exigeante à la moins exigeante.
|
||||||
|
# Premier démarrage : le serveur temporaire de l'initialisation répond déjà aux requêtes, avant
|
||||||
|
# que les extensions soient créées ; il faut aussi la fin de l'initialisation dans le journal.
|
||||||
|
# Le journal est lu en entier avant la recherche : `grep -q` branché sur `docker logs` le coupe
|
||||||
|
# dès la première correspondance, ce que `pipefail` compte comme un échec.
|
||||||
|
initialise() {
|
||||||
|
local journal
|
||||||
|
journal=$(docker logs "$1" 2>&1)
|
||||||
|
grep -q "PostgreSQL init process complete" <<<"$journal" && repond "$1"
|
||||||
|
}
|
||||||
|
repond() { sql "$1" -Atc 'select 1' >/dev/null 2>&1; }
|
||||||
|
# Sans requête : après un changement de version de TimescaleDB, toute requête échoue tant que
|
||||||
|
# l'extension n'est pas mise à niveau (tests/reprise-2-controle.sql).
|
||||||
|
accepte() { docker exec "$1" pg_isready -q -U test -d test; }
|
||||||
|
|
||||||
|
attendre() { # attendre SONDE CONTENEUR
|
||||||
|
for _ in $(seq 1 90); do
|
||||||
|
"$1" "$2" && return 0
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
docker logs "$2" 2>&1 | tail -30 >&2
|
||||||
|
echo "ÉCHEC : $2 ne démarre pas" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "== Démarrage de $IMAGE"
|
||||||
|
docker run -d --name "$ID" "${OPTIONS[@]}" "$IMAGE" >/dev/null
|
||||||
|
attendre initialise "$ID"
|
||||||
|
|
||||||
|
# L'étiquette pgN doit contenir un serveur N : PG_MAJOR vient de l'image de base.
|
||||||
|
attendue=$(docker exec "$ID" printenv PG_MAJOR)
|
||||||
|
servie=$(sql "$ID" -Atc "select current_setting('server_version_num')::int / 10000")
|
||||||
|
[[ "$servie" == "$attendue" ]] || { echo "ÉCHEC : serveur $servie, image pg$attendue" >&2; exit 1; }
|
||||||
|
|
||||||
|
for fichier in tests/test-*.sql; do
|
||||||
|
echo "== $fichier"
|
||||||
|
sql "$ID" < "$fichier"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "== Redémarrage sur des données existantes"
|
||||||
|
docker restart "$ID" >/dev/null
|
||||||
|
attendre repond "$ID"
|
||||||
|
sql "$ID" < tests/test-1-config.sql
|
||||||
|
|
||||||
|
if [[ -n "$PRECEDENTE" ]]; then
|
||||||
|
echo "== Reprise d'une base créée par $PRECEDENTE"
|
||||||
|
# Le volume se monte là où l'image déclare le sien : /var/lib/postgresql/data jusqu'à
|
||||||
|
# PostgreSQL 17, /var/lib/postgresql à partir de 18.
|
||||||
|
montage=$(docker image inspect -f '{{range $chemin, $_ := .Config.Volumes}}{{$chemin}}{{end}}' "$IMAGE")
|
||||||
|
docker volume create "$ID-donnees" >/dev/null
|
||||||
|
docker run -d --name "$ID-reprise" "${OPTIONS[@]}" -v "$ID-donnees:$montage" "$PRECEDENTE" >/dev/null
|
||||||
|
attendre initialise "$ID-reprise"
|
||||||
|
sql "$ID-reprise" < tests/reprise-1-semis.sql
|
||||||
|
docker stop "$ID-reprise" >/dev/null
|
||||||
|
docker rm "$ID-reprise" >/dev/null
|
||||||
|
|
||||||
|
docker run -d --name "$ID-reprise" "${OPTIONS[@]}" -v "$ID-donnees:$montage" "$IMAGE" >/dev/null
|
||||||
|
attendre accepte "$ID-reprise"
|
||||||
|
sql "$ID-reprise" < tests/reprise-2-controle.sql
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== Versions"
|
||||||
|
sql "$ID" -At -c "select version()" \
|
||||||
|
-c "select string_agg(extname || ' ' || extversion, ', ' order by extname) from pg_extension"
|
||||||
|
echo "Tests réussis : $IMAGE"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
-- Reprise 1 : données créées par l'image précédente, relues ensuite par la nouvelle
|
||||||
|
-- (reprise-2-controle.sql). Une table par extension.
|
||||||
|
\set ON_ERROR_STOP on
|
||||||
|
|
||||||
|
CREATE TABLE reprise_mesures (
|
||||||
|
time TIMESTAMPTZ NOT NULL,
|
||||||
|
capteur INTEGER,
|
||||||
|
valeur DOUBLE PRECISION
|
||||||
|
);
|
||||||
|
SELECT create_hypertable('reprise_mesures', 'time');
|
||||||
|
INSERT INTO reprise_mesures
|
||||||
|
SELECT TIMESTAMPTZ '2026-01-01' + n * INTERVAL '1 day', 1, n FROM generate_series(1, 30) AS n;
|
||||||
|
|
||||||
|
CREATE TABLE reprise_vecteurs (
|
||||||
|
id bigserial PRIMARY KEY,
|
||||||
|
embedding vector(3)
|
||||||
|
);
|
||||||
|
INSERT INTO reprise_vecteurs (embedding) VALUES ('[1,2,3]'), ('[4,5,6]'), ('[1.1, 2.1, 3.1]');
|
||||||
|
CREATE INDEX ON reprise_vecteurs USING vchordrq (embedding vector_l2_ops);
|
||||||
|
|
||||||
|
CREATE TABLE reprise_lieux (
|
||||||
|
id serial PRIMARY KEY,
|
||||||
|
geom geometry(Point, 4326)
|
||||||
|
);
|
||||||
|
INSERT INTO reprise_lieux (geom) VALUES (ST_GeomFromText('POINT(7.75 48.58)', 4326));
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
-- Reprise 2 : la nouvelle image démarre sur les données de la précédente
|
||||||
|
-- (reprise-1-semis.sql). Les extensions sont d'abord mises à niveau, comme le fait
|
||||||
|
-- l'exploitant ; la première instruction de la session doit être celle de TimescaleDB.
|
||||||
|
\set ON_ERROR_STOP on
|
||||||
|
|
||||||
|
ALTER EXTENSION timescaledb UPDATE;
|
||||||
|
ALTER EXTENSION vector UPDATE;
|
||||||
|
ALTER EXTENSION vchord UPDATE;
|
||||||
|
SELECT postgis_extensions_upgrade();
|
||||||
|
|
||||||
|
INSERT INTO reprise_mesures VALUES (TIMESTAMPTZ '2026-03-01', 1, 99);
|
||||||
|
INSERT INTO reprise_vecteurs (embedding) VALUES ('[7,8,9]');
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
mesures int;
|
||||||
|
plus_proche int;
|
||||||
|
lieux int;
|
||||||
|
BEGIN
|
||||||
|
SELECT count(*) INTO mesures FROM reprise_mesures;
|
||||||
|
IF mesures != 31 THEN
|
||||||
|
RAISE EXCEPTION 'Hypertable : 31 lignes attendues, % lues', mesures;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SET LOCAL enable_seqscan = off;
|
||||||
|
SELECT id INTO plus_proche FROM reprise_vecteurs ORDER BY embedding <-> '[1,2,3]' LIMIT 1;
|
||||||
|
IF plus_proche != 1 THEN
|
||||||
|
RAISE EXCEPTION 'Index vchordrq : identifiant 1 attendu, % lu', plus_proche;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT count(*) INTO lieux FROM reprise_lieux
|
||||||
|
WHERE ST_DWithin(geom, ST_GeomFromText('POINT(7.75 48.58)', 4326), 0.01);
|
||||||
|
IF lieux != 1 THEN
|
||||||
|
RAISE EXCEPTION 'PostGIS : 1 lieu attendu, % lus', lieux;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RAISE NOTICE 'Reprise : OK';
|
||||||
|
END
|
||||||
|
$$;
|
||||||
@@ -4,6 +4,19 @@
|
|||||||
SELECT version();
|
SELECT version();
|
||||||
SELECT extname, extversion FROM pg_extension;
|
SELECT extname, extversion FROM pg_extension;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
missing text;
|
||||||
|
BEGIN
|
||||||
|
SELECT string_agg(name, ', ') INTO missing
|
||||||
|
FROM unnest(ARRAY['timescaledb', 'vector', 'vchord', 'postgis', 'postgis_topology']) AS name
|
||||||
|
WHERE name NOT IN (SELECT extname FROM pg_extension);
|
||||||
|
IF missing IS NOT NULL THEN
|
||||||
|
RAISE EXCEPTION 'Missing extensions: %', missing;
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
DO $$
|
DO $$
|
||||||
DECLARE
|
DECLARE
|
||||||
preload_libs text;
|
preload_libs text;
|
||||||
|
|||||||
Reference in new issue
Block a user