Skip to main content
Django est un framework web Python doté d’un ORM et d’un système de migrations intégrés, qui communique avec PostgreSQL via le driver psycopg 3. Dans ce guide, vous allez connecter un nouveau projet Django à ClickHouse Managed Postgres via PgBouncer, créer une table todos à l’aide d’une migration, puis traiter des requêtes de création, de lecture, de mise à jour et de suppression à partir d’une petite vue JSON. Toutes les connexions utilisent TLS avec une vérification complète des certificats.

Prérequis

  • Python 3.12 ou version ultérieure, requis par Django 6. Ce guide a été testé avec Python 3.13, Django 6.1.1 et psycopg 3.3.6.
  • 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 présentation 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. La fenêtre modale affiche votre nom d’utilisateur, votre mot de passe, le serveur et le port, ainsi qu’un sélecteur Directly / via PgBouncer. Dans ce guide, Django se connecte via le PgBouncer intégré, sur le port 6432. En production, une application Django exécute généralement plusieurs processus worker répartis sur plusieurs machines, et chacun ouvre ses propres connexions. PgBouncer fonctionne en mode pool de connexions par transaction, ce qui permet à tous ces processus de partager un nombre réduit de connexions Postgres. Le pool de connexions par transaction impose de configurer quelques paramètres dans Django ; ce guide les inclut et les explique dans la section Paramètres PgBouncer pour Django. Sélectionnez via PgBouncer pour afficher les informations de connexion au pool. Le port passe à 6432 et la chaîne de connexion inclut sslmode=verify-full. Dans la fenêtre modale, cliquez sur Download CA certificate. Vous pouvez également le télécharger depuis Settings → CA Certificate. Ce certificat étant propre à votre instance, le driver peut s’en servir pour vérifier qu’il communique bien avec votre serveur.

Configurer le projet

Créez un répertoire de projet doté d’un environnement virtuel, installez Django et psycopg 3, puis créez un projet Django contenant une application todos :
psycopg[binary] inclut une version récente de libpq : vous n’avez donc pas besoin d’installer PostgreSQL en local.
Vous ajoutez PostgreSQL à une application existante ?Ignorez les commandes ci-dessus. Dans l’environnement virtuel de votre projet, exécutez pip install "psycopg[binary]", puis passez directement à la section Configurer la connexion, qui remplace le paramètre SQLite DATABASES. Pour copier vos données SQLite :
  • Avant de modifier DATABASES, exécutez python manage.py dumpdata --natural-foreign --natural-primary -e contenttypes -e auth.permission -o data.json.
  • Une fois python manage.py migrate exécuté, lancez python manage.py loaddata data.json.

Configurer la connexion

Déplacez le certificat de CA que vous avez téléchargé dans le répertoire du projet, à côté de manage.py, 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 indiquées dans la fenêtre modale Connect :
Définissez l’hôte et le mot de passe comme variables d’environnement afin de ne pas les inclure dans votre code :
Ouvrez mysite/settings.py, ajoutez import os sous from pathlib import Path, puis remplacez le paramètre DATABASES par :
mysite/settings.py
Django transmet les clés de OPTIONS à psycopg, qui les relaie à son tour à libpq. Lorsque sslmode est défini sur verify-full, libpq vérifie que le certificat du serveur est signé par l’autorité de certification (CA) de votre service et qu’il correspond au nom d’hôte. BASE_DIR rend le chemin du certificat absolu : le paramètre fonctionne donc quel que soit le répertoire depuis lequel vous démarrez Django.
Si sslrootcert pointe vers la mauvaise CA, la connexion échoue avec l’erreur SSL error: certificate verify failed. Sans sslrootcert, elle échoue avec root certificate file ".../.postgresql/root.crt" does not exist, car libpq se rabat alors sur l’emplacement par défaut de sa CA. Si vous vous connectez par adresse IP plutôt que par nom d’hôte, elle échoue avec server certificate for "..." does not match host name.
Si votre application lit une DATABASE_URL avec dj-database-url, celui-ci transmet à OPTIONS les valeurs sslmode et sslrootcert issues de la chaîne de requête de l’URL. Appelez dj_database_url.config(conn_max_age=600, conn_health_checks=True, disable_server_side_cursors=True) pour obtenir les mêmes paramètres. Un chemin sslrootcert relatif dans l’URL est résolu par rapport au répertoire depuis lequel vous démarrez Django.

Créer un modèle et exécuter les migrations

Ajoutez l’application todos à INSTALLED_APPS dans mysite/settings.py :
mysite/settings.py
Définissez un modèle Todo dans todos/models.py :
todos/models.py
Créez une migration pour le modèle, puis appliquez toutes les migrations :
Outre todos_todo, migrate crée les tables des applications intégrées de Django, telles que auth_user et django_session, et enregistre les migrations appliquées dans django_migrations. Pour une application existante, ne créez pas le modèle todos et exécutez uniquement python manage.py migrate, qui crée toutes les tables de votre application dans la nouvelle base de données. migrate ne prend pas de verrou consultatif au niveau de la session ; il fonctionne donc via PgBouncer. En revanche, il n’empêche pas les exécutions simultanées : lancez donc les migrations depuis un seul endroit, par exemple une étape de déploiement unique.

Ajouter un endpoint JSON

Remplacez todos/views.py par deux vues qui permettent de créer, lister, mettre à jour et supprimer des tâches à l’aide de l’ORM Django :
todos/views.py
Remplacez le contenu de mysite/urls.py pour router /todos et /todos/<id> vers les vues :
mysite/urls.py

Exécuter et vérifier

Démarrez le serveur de développement :
Dans un second terminal, créez deux tâches, marquez la première comme terminée, puis supprimez la seconde :
La requête PATCH renvoie la ligne mise à jour :
Ouvrez http://localhost:3111/todos dans votre navigateur pour afficher la liste des tâches restantes. La réponse se présente comme suit :
Pour afficher les données dans la console, ouvrez console SQL dans la barre latérale gauche de votre service, développez guide_django puis public, et cliquez sur la table todos_todo. La table contient la ligne restante :

Paramètres PgBouncer pour Django

PgBouncer fonctionne en mode pool de connexions par transaction : chaque transaction, ou chaque statement hors transaction, peut s’exécuter sur une connexion Postgres différente. Voici les conséquences sur les paramètres ci-dessus et sur d’autres fonctionnalités de Django :
  • DISABLE_SERVER_SIDE_CURSORS : QuerySet.iterator lit les lignes par fragments au moyen d’un curseur côté serveur. Hors transaction, Django déclare le curseur WITH HOLD et récupère chaque fragment séparément ; via PgBouncer, une récupération peut donc atterrir sur une connexion Postgres qui ne possède pas le curseur et échouer avec cursor "_django_curs_..." does not exist. Cela ne se produit que lorsque d’autres clients utilisent le pool en même temps, si bien que le problème passe souvent inaperçu en développement. Lorsque ce paramètre est activé, .iterator() utilise un curseur classique et psycopg charge l’intégralité du résultat en mémoire. Pour lire une très grande table en streaming, exécutez ce code via la connexion directe sur le port 5432, avec ce paramètre désactivé.
  • CONN_MAX_AGE : par défaut (0), Django ouvre une nouvelle connexion pour chaque requête. L’ouverture d’une connexion TLS nécessite plusieurs allers-retours réseau, via PgBouncer comme en direct ; réutiliser les connexions accélère donc nettement les requêtes. Lors d’un test depuis un ordinateur portable avec quatre workers Gunicorn, définir CONN_MAX_AGE sur 600 a fait passer le temps de requête médian d’environ 670 ms à 95 ms. Via PgBouncer, une connexion Django ouverte ne monopolise pas de connexion Postgres entre les transactions ; les connexions persistantes n’épuisent donc pas max_connections. Avec CONN_HEALTH_CHECKS, Django vérifie une connexion réutilisée au début de chaque requête et se reconnecte si elle a été fermée.
  • Pool de connexions : le pool intégré de Django ("pool": True dans OPTIONS, qui nécessite pip install "psycopg[binary,pool]") offre un gain similaire et fonctionne également via PgBouncer. Il exige que CONN_MAX_AGE soit défini sur 0 et maintient au moins quatre connexions ouvertes par processus. Avec PgBouncer, CONN_MAX_AGE suffit pour la plupart des applications ; envisagez le pool pour les serveurs multithreads ou ASGI.
  • Requêtes préparées : aucun paramètre n’est nécessaire. Par défaut, Django définit le prepare_threshold de psycopg sur None et lie les paramètres de requête côté client ; il ne crée donc pas de requêtes préparées côté serveur. Si vous activez "server_side_binding": True et définissez "prepare_threshold" dans OPTIONS, les requêtes préparées fonctionnent aussi via PgBouncer, car psycopg[binary] embarque libpq 18 et libère les statements à l’aide d’un message Close au niveau du protocole. Avec un psycopg compilé avec libpq 16 ou antérieur, la libération d’un statement au sein d’une transaction échoue avec prepared statement "_pg3_0" does not exist. Pour vérifier votre version, exécutez python -c "import psycopg; print(psycopg.pq.version())". Il vous faut 170000 ou une version ultérieure.
  • État de session : un statement SET, tel que SET search_path, ne s’applique qu’à la connexion Postgres qui l’a exécuté ; les requêtes suivantes risquent donc de ne pas le voir, alors que d’autres clients peuvent en hériter. PgBouncer rejette le paramètre Django "options": "-c search_path=..." avec unsupported startup parameter in options: search_path. Définissez plutôt des valeurs par défaut au niveau de la base de données, par exemple avec ALTER DATABASE guide_django SET search_path = myschema, public;. N’utilisez pas l’option assume_role, qui exécute SET ROLE. La gestion native de TIME_ZONE par Django et l’option isolation_level fonctionnent via PgBouncer.
Pour vous connecter plutôt en direct, définissez PORT sur 5432 et supprimez DISABLE_SERVER_SIDE_CURSORS. Chaque worker Django conserve alors sa propre connexion Postgres ; veillez donc à ce que le nombre total de workers reste inférieur à max_connections.

Étapes suivantes