Skip to main content
pgx est le driver PostgreSQL le plus utilisé pour Go, et pgxpool est son pool de connexions. Dans ce guide, vous allez connecter un service Go à ClickHouse Managed Postgres via une connexion TLS vérifiée, créer une table todos à l’aide d’une migration golang-migrate, puis traiter des requêtes de création, de lecture, de mise à jour et de suppression depuis un serveur net/http.

Prérequis

  • Go 1.25 ou version ultérieure. Ce guide a été testé avec Go 1.27.1, pgx 5.11.0 et golang-migrate 4.20.1.
  • Un compte ClickHouse Cloud
  • psql, pour créer la base de données. Vous pouvez également exécuter le statement CREATE DATABASE dans la console SQL.

Créer un service ClickHouse Managed Postgres

Dans la console ClickHouse Cloud, cliquez sur New service et sélectionnez Postgres. L’instance est prête en quelques minutes. Pour une procédure pas à pas, consultez le guide de démarrage rapide.

Obtenir vos informations de connexion

Cliquez sur Connect dans la barre latérale gauche de votre service. Laissez Directly sélectionné et activez Use SSL. L’URL de connexion de l’onglet url utilise désormais le port 5432 et se termine par sslmode=verify-full&sslrootcert=<service-name>-ca-certificate.pem. Cliquez sur Download certificat de CA. Vous pourrez également le télécharger plus tard depuis Settings → certificat de CA. Ce certificat est propre à votre instance, ce qui permet à pgx de vérifier qu’il communique bien avec votre serveur. Dans ce guide, la connexion à Postgres se fait directement sur le port 5432, aussi bien pour les migrations que pour l’application. Un service Go est un processus de longue durée, et pgxpool y maintient déjà un pool de connexions : un second pooler est donc inutile. Si vous exécutez de nombreuses instances du service et avez besoin de plus de connexions client que Postgres n’en autorise, utilisez le PgBouncer intégré (via PgBouncer, port 6432) pour l’application ; consultez Utiliser PgBouncer, qui nécessite un paramètre de connexion supplémentaire.

Configurer le projet

Créez un module Go, ajoutez pgx, puis installez la CLI migrate avec le driver pgx :
L’indicateur -tags 'pgx5' compile la CLI migrate avec son driver pgx v5, qui utilise le schéma d’URL pgx5://. go install place le binaire dans $GOBIN, ou dans $(go env GOPATH)/bin si GOBIN n’est pas défini ; veillez donc à ce que ce répertoire figure dans votre PATH.
Vous partez d’une application existante ?Ignorez les étapes mkdir et go mod init. Dans votre module, exécutez go get github.com/jackc/pgx/v5 ainsi que la commande go install ci-dessus, puis passez directement à Configurer la connexion. Si votre code utilise database/sql, stdlib.OpenDBFromPool encapsule le même pool sous la forme d’un *sql.DB, ce qui vous permet de migrer progressivement vos requêtes existantes ; remplacez alors les caractères de substitution ? par $1, $2, etc.

Configurer la connexion

Déplacez le certificat de CA téléchargé dans le répertoire du projet, puis renommez-le en ca-certificate.pem. Créez une base de données pour l’application. Remplacez <PASSWORD> et l’hôte par les valeurs affichées dans la fenêtre modale Connect :
Définissez deux variables d’environnement dans votre shell. Elles pointent vers la même base de données guide_go et ne diffèrent que par le schéma d’URL : l’application lit DATABASE_URL, tandis que la CLI migrate utilise MIGRATE_URL :
pgx analyse l’URL de la même manière que libpq. Avec sslmode=verify-full, il charge le fichier de CA indiqué par sslrootcert et vérifie à la fois la chaîne de certificats et le nom d’hôte; vous n’avez donc besoin d’aucun code TLS. Le path sslrootcert est résolu par rapport au répertoire depuis lequel vous exécutez les commandes. Si votre mot de passe contient des caractères spéciaux tels que @, / ou #, encodez-les au format URL.
Définissez toujours sslmode=verify-fullSans sslmode, pgx utilise prefer, qui ne vérifie pas le certificat du serveur, même si vous avez défini sslrootcert. Avec sslmode=verify-full et un fichier de CA incorrect, pgx échoue avec l’erreur x509: certificate signed by unknown authority; si le fichier n’existe pas, il échoue avec unable to read CA file. Si vous vous connectez via l’adresse IP plutôt que via le nom d’hôte, il échoue avec x509: cannot validate certificate for <IP> because it doesn't contain any IP SANs.

Créer le schéma à l’aide d’une migration

Créez les fichiers de migration :
Complétez la migration up :
migrations/000001_create_todos.up.sql
Et la migration down, qui l’annule :
migrations/000001_create_todos.down.sql
Appliquez la migration :
migrate enregistre la version actuelle dans la table schema_migrations et n’applique ensuite que les migrations plus récentes. Versionnez le répertoire migrations avec votre code.
Exécutez toujours migrate via la connexion directe (port 5432). Pendant son exécution, il détient un verrou consultatif (advisory lock) au niveau de la session, afin d’empêcher deux déploiements d’effectuer une migration en même temps. Avec PgBouncer, le verrouillage et le déverrouillage peuvent s’exécuter sur des connexions Postgres différentes : le verrou n’est alors jamais libéré, et les exécutions suivantes de migrate se bloquent ou échouent avec l’erreur timeout: can't acquire database lock.

Interroger la base de données

Créez db.go. Ce fichier crée le pool à partir de DATABASE_URL et vérifie la connexion au démarrage en demandant à Postgres quelle version de TLS est utilisée par la session :
db.go
Créez todos.go avec les handlers HTTP. Chaque handler exécute une requête paramétrée sur le pool, puis pgx.CollectRows et pgx.CollectExactlyOneRow mappent les lignes vers la struct Todo en se basant sur le nom des colonnes :
todos.go
Transmettre les valeurs sous forme d’arguments ($1) plutôt que de les insérer dans la string SQL protège contre les injections SQL : pgx les envoie séparément du texte de la requête. Inutile de vérifier séparément l’erreur renvoyée par pool.Query : pgx.CollectRows et pgx.CollectExactlyOneRow la renvoient. Créez main.go. Ce fichier crée un pool unique pour l’ensemble du processus, sert les routes sur le port 3114 et ferme le pool lors d’un Ctrl+C. Dans une application existante, appelez plutôt newPool une seule fois au démarrage, puis registerTodoRoutes sur votre http.ServeMux :
main.go

Exécuter et vérifier

Ajoutez les dépendances restantes de pgxpool dans go.mod et go.sum, puis démarrez le serveur :
TLSv1.3 provient de pg_stat_ssl sur le serveur, ce qui confirme que la session est bien chiffrée. Si vous faites pointer sslrootcert vers tout autre certificat, le serveur ne démarre pas et inscrit dans ses logs x509: certificate signed by unknown authority, preuve que le certificat est réellement vérifié. Dans un second terminal, créez deux tâches, marquez la première comme terminée et supprimez la seconde :
La requête PATCH renvoie la ligne mise à jour :
Ouvrez http://localhost:3114/todos dans votre navigateur pour afficher la liste des tâches restantes. pgx renvoie les valeurs timestamptz dans votre fuseau horaire local. La réponse se présente ainsi :
Arrêtez le serveur avec Ctrl+C. Il affiche Shutting down dans les logs et ferme le pool. Pour afficher les données dans la console, ouvrez console SQL dans la barre latérale gauche de votre service, développez guide_go puis public, et cliquez sur la table todos. La table schema_migrations, juste à côté, contient la version de migration. La table todos contient la ligne restante :

Utiliser PgBouncer

Pour faire transiter les requêtes de l’application par le PgBouncer intégré, sélectionnez via PgBouncer dans la fenêtre modale Connect, remplacez le port de DATABASE_URL par 6432, puis ajoutez default_query_exec_mode=cache_describe. Laissez MIGRATE_URL sur le port 5432 :
pgx lit default_query_exec_mode depuis l’URL et ne l’envoie pas au serveur. Aucune modification du code n’est nécessaire, et le même certificat de CA fonctionne. Le journal de démarrage affiche désormais TLS: none, car pg_stat_ssl décrit la connexion de PgBouncer lui-même à Postgres. La connexion de votre application à PgBouncer reste bien vérifiée : un certificat de CA incorrect provoque le même échec.
N’utilisez pas le mode de requête par défaut de pgx avec PgBouncerPar défaut, pgx prépare chaque requête sous forme de statement nommé, une fois par connexion, et réutilise les types de colonnes obtenus en retour. PgBouncer prépare ces statements sur la connexion Postgres, quelle qu’elle soit, qui exécute la requête, et les partage entre les clients. Après une migration qui modifie le type d’une colonne, ou qui ajoute une colonne à une table interrogée avec SELECT *, les requêtes passant par PgBouncer peuvent continuer d’échouer avec cached plan must not change result type, même après un redémarrage de l’application, ou bien, à partir de pgx 5.9, renvoyer des valeurs erronées sans aucune erreur. Par exemple, une colonne bigint convertie en double precision est renvoyée sous la forme des bits bruts de la valeur float64, par exemple 4631178160564600832 pour 42.5.cache_describe ne met en cache que les types de paramètres et de colonnes, exécute chaque requête sous forme de statement non nommé en un seul aller-retour et décode les lignes à l’aide des types de colonnes que le serveur renvoie avec chaque résultat : il n’est donc pas concerné. exec fonctionne également. N’utilisez pas describe_exec avec PgBouncer : ce mode découpe chaque requête en deux allers-retours, que PgBouncer peut acheminer vers des connexions Postgres différentes. Inutile de définir statement_cache_capacity=0.
PgBouncer fonctionne en mode pool de connexions par transaction ; chaque transaction peut donc s’exécuter sur une connexion Postgres différente :
  • Les transactions ouvertes avec pool.Begin ou pgx.BeginFunc fonctionnent normalement.
  • Les paramètres de session définis avec SET ne restent pas attachés à votre connexion et peuvent se propager à d’autres clients. Utilisez plutôt SET LOCAL à l’intérieur d’une transaction. PgBouncer rejette également search_path dans l’URL de connexion avec l’erreur unsupported startup parameter: search_path ; préfixez plutôt les noms de tables par leur schéma.
  • Les commandes SQL PREPARE et EXECUTE, LISTEN ainsi que les verrous consultatifs de session ne fonctionnent pas.

Étapes suivantes

  • Connexion : chaînes de connexion, PgBouncer et TLS
  • Paramètres : paramètres Postgres et PgBouncer, comme max_connections
  • Répliques en lecture : acheminer les requêtes en lecture seule vers une réplique avec un second pgxpool.Pool
  • Sécurité : listes d’accès IP et réseau privé