Aller au contenu
English

De SQLite à PostgreSQL

  • Leçon 1
  • 45 min
  • Module 1 : Choisir le stockage selon la charge de travail

Ce que vous saurez faire

Passez du fichier SQLite du cours SQL à un serveur PostgreSQL sur votre ordinateur. Chargez les données du service de soutien de Larkfield et découvrez les vérifications du serveur que le fichier ne faisait pas.

Avant de commencerCe qu’il faut savoir et avoir sous la mainEnviron 45 minutes

Ce que vous devez connaître

Ce qu’il vous faut sur votre ordinateur

  • Python 3.12 ou une version ultérieure.
  • Docker Desktop, ou l’autorisation d’installer PostgreSQL. La leçon Installer et vérifier du cours L’essentiel de Docker et du nuage pour les développeurs en IA montre l’installation de Docker. Vous n’avez pas besoin du reste de ce cours.
  • Environ 3 Go d’espace disque libre : l’image PostgreSQL occupe 686 Mo. Les données volumineuses de la prochaine leçon nécessitent environ 1 Go. Aucun compte, aucune clé et rien à payer.

Dans cette leçon, vous créez un dossier de projet, ~/projects/ticket-api, à partir de l’instantané start. Si vous avez terminé le cours CI/CD, tests et mises en production sûres, gardez votre ancien dossier. Ce cours part d’une copie avec de nouvelles données et de nouveaux fichiers.

Imaginez deux façons de tenir les registres d’un petit magasin.

La première est un cahier sur votre bureau. Vous l’ouvrez, vous y écrivez, puis vous le fermez. Personne ne vérifie ce que vous écrivez. Si vous écrivez « abc » dans la colonne du prix, le cahier l’accepte. Et quand le cahier est dans votre sac, personne d’autre ne peut le lire.

La deuxième est un bureau des registres avec un commis. Vous ne touchez pas vous-même au registre. Vous allez au comptoir, vous dites qui vous êtes et vous faites votre demande. Le commis vérifie chaque entrée : un prix doit être un nombre et une date doit exister. Plusieurs personnes peuvent faire une demande en même temps, et le commis les traite dans l’ordre. Mais le bureau doit être ouvert. Si le commis est absent, personne ne reçoit de réponse.

SQLite est le cahier. PostgreSQL est le commis.

Dans le cours SQL, vos données tenaient dans un seul fichier, larkfield.db. Votre programme l’ouvrait directement avec la bibliothèque sqlite3, à l’intérieur du programme. C’était le bon outil : une personne, un fichier, rien à installer.

Vous connaissez l’équipe de Larkfield grâce aux cours SQL et API. Mei, l’ingénieure de données, dirige la conception du stockage, et Amira crée l’API de billets. Priya compare des modèles d’IA à partir de leurs registres, et Grace dirige les agents de soutien. Omar est le gestionnaire de produit, et Tomás est un ingénieur en IA qui aime les raccourcis. Sam pose les questions que vous avez peut-être aussi.

Le service de soutien veut maintenant que son API de billets conserve tout : clients, billets, messages, fichiers, exécutions d’IA et vecteurs. L’API, les tests, les rapports de Priya et les écrans des agents utilisent les mêmes données en même temps. Et les données doivent rester correctes.

Voyez ce que SQLite fait avec une valeur incorrecte. Ouvrez l’interpréteur SQLite de Python avec un nouveau fichier d’exercice. Sous Windows, tapez python au lieu de python3 :

Fenêtre de terminal
python3 -m sqlite3 try.db

Tapez ces trois instructions à l’invite sqlite> :

CREATE TABLE tickets (ticket_id TEXT PRIMARY KEY, priority INTEGER, created_at TIMESTAMP);
INSERT INTO tickets VALUES ('T-30002', 'abc', '2026-02-30 09:00');
SELECT ticket_id, priority, typeof(priority), created_at FROM tickets;
('T-30002', 'abc', 'text', '2026-02-30 09:00')

La colonne indique INTEGER (entier), mais SQLite a stocké le texte abc. La date est le 30 février, un jour qui n’existe pas, et SQLite l’a aussi stockée. Il n’y a eu ni erreur ni avertissement. Le prochain programme qui additionne les priorités ou trie les dates obtient une réponse incorrecte, sans que personne sache pourquoi. Tapez .quit pour quitter, puis supprimez try.db.

SQLite n’est pas défectueux. Ses créateurs l’ont conçu pour être souple, comme le montrait la comparaison du cours SQL. Mais un système de registres partagé a besoin d’un commis qui dit non.

Un serveur de base de données est un programme qui fonctionne en continu et possède les données. Les autres programmes n’ouvrent pas les fichiers de données. Ils se connectent au serveur, s’identifient et envoient du SQL. Le serveur vérifie chaque instruction, l’exécute et renvoie le résultat.

Un programme qui se connecte est un client. L’API de billets est un client. Les tests aussi, ainsi que psql, le client de terminal de PostgreSQL.

PostgreSQL est un serveur de base de données gratuit et à code source ouvert. Son nom vient d’un ancien projet, Ingres : « post-Ingres », suivi de SQL.

Deux cases. À gauche, SQLite : un fichier. Votre programme q.py utilise la bibliothèque sqlite3, qui est à l'intérieur du programme. La bibliothèque lit et écrit le fichier larkfield.db. À droite, PostgreSQL : un serveur. Trois clients, pytest, psql et ticket-api avec psycopg, ont chacun une connexion au serveur PostgreSQL, un programme qui fonctionne en continu. Seul le serveur lit et écrit son répertoire de données, qui se trouve dans un volume Docker.
SQLite est une bibliothèque dans votre programme qui écrit dans un seul fichier. PostgreSQL est un programme distinct : chaque client s'y connecte, et seul le serveur accède aux données.
Description textuelle du diagramme

Avec SQLite, la base de données est un fichier. Le code qui le lit s’exécute dans votre propre programme. Avec PostgreSQL, la base de données est un processus serveur. L’API de billets, psql et les tests sont trois clients distincts. Chacun ouvre une connexion au serveur, qui fait toutes les lectures et écritures dans son répertoire de données.

Un client a besoin de cinq renseignements pour joindre le serveur. Ils tiennent sur une ligne, la chaîne de connexion. Voici celle du fichier .env.example du projet :

postgresql://tickets:change-me-local-only@127.0.0.1:5432/tickets
Partie Valeur ici Signification
schéma postgresql:// C’est un serveur PostgreSQL
utilisateur tickets Qui vous êtes. Le serveur vérifie ce que cet utilisateur peut faire
mot de passe change-me-local-only Prouve que vous êtes cet utilisateur. Vous le changez
hôte 127.0.0.1 Quel ordinateur exécute le serveur : celui-ci
port 5432 Quelle porte sur cet ordinateur. 5432 est le port habituel de PostgreSQL
base de données tickets Un serveur peut conserver plusieurs bases de données. Celle-ci est tickets

Le projet lit la chaîne dans la variable DATABASE_URL. Elle contient le mot de passe, donc elle est secrète. Elle se trouve dans un fichier nommé .env, que Git ignore.

Vous allez maintenant reproduire l’installation de Larkfield sur votre ordinateur. Le projet, ticket-api, est l’API de billets des cours précédents. L’instantané start ajoute les données du service de soutien, ses tables et un programme de chargement. Vous avez deux chemins possibles : avec Docker ou sans Docker.

Un parcours de gauche à droite. Récupérez ticket-api à partir de l'instantané start. Créez .env avec le mot de passe et DATABASE_URL. Docker? Si oui, docker compose up -d db démarre PostgreSQL 18.6 avec pgvector 0.8.7. Sinon, installez PostgreSQL et pgvector, puis créez un utilisateur et une base de données. Les deux chemins mènent à un environnement virtuel et à pip install. Ensuite, python -m scripts.load applique 4 migrations et charge 200 billets. Enfin, python -m pytest affiche 34 passed.
Le parcours d'installation de ce cours. Seule l'étape qui démarre PostgreSQL dépend de Docker.
Description textuelle du diagramme

Les étapes, dans l’ordre : récupérez le projet, écrivez .env et démarrez PostgreSQL. Avec Docker, une seule commande suffit. Sans Docker, il faut un programme d’installation, l’extension pgvector, un utilisateur et une base de données. Ensuite, créez l’environnement virtuel, chargez les données et exécutez les tests. Le chargement affiche 4 migrations et 200 billets. Les tests affichent 34 tests réussis.

  1. Récupérez les fichiers du cours. Si vous avez Git, clonez le dépôt d’exercices une seule fois, dans ~/projects :

    Fenêtre de terminal
    cd ~/projects
    git clone https://github.com/Nextia-Intelligence/nextia-learning-labs.git

    Sans Git, téléchargez le fichier ZIP du dépôt et décompressez-le dans ~/projects. Son dossier s’appelle alors nextia-learning-labs-main. Utilisez ce nom à la prochaine étape.

  2. Copiez l’instantané start dans un nouveau dossier ticket-api, puis ouvrez ce dossier dans le terminal. Si un dossier ticket-api existe déjà, renommez-le d’abord, par exemple en ticket-api-cicd.

    Fenêtre de terminal
    cp -R nextia-learning-labs/C20/snapshots/start ticket-api
    cd ticket-api
    cp .env.example .env
  3. Ouvrez le dossier dans VS Code. Ouvrez .env et remplacez change-me-local-only par votre propre mot de passe aux trois endroits. La première ligne, POSTGRES_PASSWORD, définit le mot de passe au premier démarrage du serveur. Les deux autres lignes l’utilisent pour se connecter.

  4. Démarrez PostgreSQL. Choisissez votre chemin.

    Démarrez Docker Desktop. Démarrez ensuite uniquement le service de base de données du fichier compose.yaml du projet, puis vérifiez-le :

    Fenêtre de terminal
    docker compose up -d db
    docker compose ps

    La première fois, Docker télécharge l’image pgvector/pgvector:0.8.7-pg18-trixie. Elle contient PostgreSQL 18.6 avec l’extension pgvector, qui stocke les vecteurs. Relancez docker compose ps jusqu’à ce que la colonne STATUS (état) affiche Up … (healthy) (en marche et fonctionnel). La colonne PORTS (ports) affiche 127.0.0.1:5432->5432/tcp : seuls les programmes sur votre ordinateur peuvent joindre le serveur.

  5. Créez l’environnement virtuel et installez les paquets :

    Fenêtre de terminal
    python3 -m venv .venv
    source .venv/bin/activate
    python -m pip install -r requirements.txt

    Les paquets viennent du cours CI/CD, tests et mises en production sûres : FastAPI 0.142.2 et psycopg 3.3.6, la bibliothèque cliente PostgreSQL pour Python.

  6. Chargez les données :

    Fenêtre de terminal
    python -m scripts.load
    applied 001_create_classifications
    applied 002_add_score
    applied 003_backfill_score
    applied 004_help_desk_tables
    Database is up to date (4 migrations).
    Loaded the small data in 0.1 s:
    customers 40
    tickets 200
    messages 334
    attachments 43
    documents 36
    ai_runs 294
    ticket_embeddings 200
  7. Exécutez les tests :

    Fenêtre de terminal
    python -m pytest

    La dernière ligne est 34 passed (34 tests réussis). Les tests créent leur propre base de données, tickets_test, et suppriment tout son contenu. Ils ne touchent jamais à tickets.

Le programme de chargement a d’abord appliqué quatre migrations : des fichiers SQL numérotés dans migrations/, appliqués une fois chacun, dans l’ordre. Les trois premières viennent des cours précédents : elles ont créé l’historique de classification. 004_help_desk_tables.sql est nouveau. Il crée une table par type de registre, chacune avec une clé primaire et rien d’autre pour le moment. Le module 2 ajoute les règles. Le programme a ensuite copié les fichiers CSV de data/small/ dans les tables et affiché le nombre d’entrées.

Toutes les données sont inventées pour ce cours. Aucun vrai client n’a écrit ces billets, et un programme dessine les photos et les reçus. Les clients C-0001 à C-0040 ont les identifiants des premiers clients du cours SQL, avec de nouveaux noms. Le petit jeu de données contient 200 billets du 25 septembre au 8 octobre 2026. Dans le cours, « aujourd’hui » est le vendredi 9 octobre 2026.

Le programme de chargement protège les données, comme le faisait get_data.py dans le cours SQL. Exécutez-le de nouveau :

Fenêtre de terminal
python -m scripts.load
The help-desk tables already have data. Run again with --reset to delete it and load again.

python -m scripts.load --reset supprime les données du service de soutien et les charge de nouveau. Utilisez cette commande quand vos données d’exercice sont en mauvais état.

Les durées de ce module proviennent d’un seul ordinateur : macOS 26.6 sur un Apple M4 avec 16 Go. Docker Desktop (version 4.81.0) disposait de 8 Go et de 10 processeurs. Il exécutait PostgreSQL 18.6 et pgvector 0.8.7 dans un conteneur. Vos durées seront différentes, mais pas le nombre d’entrées. Chaque étape du cours a été testée uniquement sous macOS (Apple silicon). Les commandes Windows et Linux ont été écrites, mais pas exécutées.

psql est le client de terminal de PostgreSQL. Avec Docker, il est déjà dans le conteneur de la base de données. Vous n’avez donc rien à ajouter :

Fenêtre de terminal
docker compose exec db psql -U tickets -d tickets

Sans Docker, donnez votre chaîne de connexion à psql : psql "postgresql://tickets:your-password@127.0.0.1:5432/tickets". L’invite est tickets=#. Une commande qui commence par une barre oblique inversée appartient à psql, pas à SQL. \dt énumère les tables :

List of tables
Schema | Name | Type | Owner
--------+-------------------+-------+---------
public | ai_runs | table | tickets
public | attachments | table | tickets
public | classifications | table | tickets
public | customers | table | tickets
public | documents | table | tickets
public | messages | table | tickets
public | schema_migrations | table | tickets
public | ticket_embeddings | table | tickets
public | tickets | table | tickets
(9 rows)

schema_migrations enregistre les migrations exécutées. \d tickets montre une table, avec un type pour chaque colonne :

Table "public.tickets"
Column | Type | Collation | Nullable | Default
-------------+--------------------------+-----------+----------+---------
ticket_id | text | | not null |
customer_id | text | | |
subject | text | | |
body | text | | |
channel | text | | |
team | text | | |
priority | smallint | | |
status | text | | |
created_at | timestamp with time zone | | |
updated_at | timestamp with time zone | | |
closed_at | timestamp with time zone | | |
Indexes:
"tickets_pkey" PRIMARY KEY, btree (ticket_id)

SQL fonctionne comme dans le cours SQL. Terminez chaque instruction par ; :

SELECT status, count(*) FROM tickets GROUP BY status;
status | count
----------+-------
closed | 42
open | 39
pending | 27
resolved | 92
(4 rows)

Une table est nouvelle pour vous : ticket_embeddings. Elle conserve un vecteur pour chaque billet : 384 nombres qu’un modèle de plongement (embedding) a produits à partir du texte du billet. Les billets aux textes semblables obtiennent des vecteurs proches les uns des autres. L’opérateur <=> de pgvector mesure cette distance. Cette requête trie les billets selon leur distance par rapport à T-30002, « Charged twice for order LK-182074 » (commande LK-182074 facturée deux fois), et garde les quatre premiers :

SELECT ticket_id, subject FROM tickets ORDER BY (SELECT embedding FROM ticket_embeddings WHERE ticket_id = 'T-30002') <=> (SELECT embedding FROM ticket_embeddings e WHERE e.ticket_id = tickets.ticket_id) LIMIT 4;
ticket_id | subject
-----------+-----------------------------------
T-30002 | Charged twice for order LK-182074
T-30050 | Charged twice for order LK-710804
T-30168 | Charged twice for order LK-919420
T-30064 | Charged twice for order LK-167846
(4 rows)

T-30002 arrive en premier, car sa distance par rapport à lui-même est 0. Les trois suivants présentent le même problème pour d’autres commandes. Vous n’avez pas encore besoin de comprendre la requête. La leçon Catégories de stockage explique les vecteurs, et le module 5 accélère cette recherche. Restez dans psql pour la prochaine section.

Donnez maintenant au commis les mêmes valeurs incorrectes que SQLite a acceptées. Dans psql :

UPDATE tickets SET priority = 'abc' WHERE ticket_id = 'T-30002';
UPDATE tickets SET created_at = '2026-02-30 09:00' WHERE ticket_id = 'T-30002';
SELECT ticket_id, priority, created_at FROM tickets WHERE ticket_id = 'T-30002';
ERROR: invalid input syntax for type smallint: "abc"
LINE 1: UPDATE tickets SET priority = 'abc' WHERE ticket_id = 'T-300...
^
ERROR: date/time field value out of range: "2026-02-30 09:00"
LINE 1: UPDATE tickets SET created_at = '2026-02-30 09:00' WHERE tic...
^
ticket_id | priority | created_at
-----------+----------+------------------------
T-30002 | 1 | 2026-09-25 08:20:19+00
(1 row)

Les deux modifications ont échoué, et la ligne n’a pas changé. Le type d’une colonne est une règle que PostgreSQL vérifie à chaque écriture. Tapez \q pour quitter psql.

Trois autres différences comptent dans ce cours :

  • Les clés étrangères sont toujours vérifiées. Dans SQLite, une clé étrangère ne fait rien tant que vous n’exécutez pas PRAGMA foreign_keys = ON. Dans PostgreSQL, le serveur vérifie une clé étrangère déclarée à chaque écriture. Les tables du service de soutien n’ont pas encore de clés étrangères. Quand le module 2 les ajoute, PostgreSQL trouve trois messages dont le billet n’existe pas.
  • Plusieurs clients à la fois. Le serveur reçoit des connexions de l’API, des tests et de psql en même temps. Par défaut, il autorise 100 connexions. Le module 3 les partage grâce à un pool de connexions (pool).
  • Le serveur doit fonctionner. Un fichier est présent dès que votre programme l’est. Un serveur est un programme, et un programme peut s’arrêter. L’erreur fréquente ci-dessous montre le message d’erreur.

SQLite reste un bon outil. Utilisez-le pour un seul programme et un seul utilisateur : un carnet, un script, une application mobile ou un test. Il ne nécessite aucune installation, et copier la base de données revient à copier un seul fichier.

Utilisez un serveur de base de données quand plusieurs programmes partagent les données ou que celles-ci doivent respecter des règles strictes. Utilisez-le aussi quand vous avez besoin de ses fonctions supplémentaires. Ici, c’est pgvector pour les vecteurs, puis, plus tard, plusieurs connexions à la fois.

Un serveur a des coûts, et vous venez d’en payer certains :

  • Quelque chose à exécuter. Le serveur utilise de la mémoire et doit être démarré. Docker l’exécute dans un conteneur. Sans Docker, il s’exécute comme un service de votre système.
  • Quelque chose à protéger. Un serveur a des utilisateurs et des mots de passe. Le mot de passe est un secret dans .env, et le port est ouvert uniquement à votre ordinateur.
  • Quelque chose à sauvegarder. Les données forment un répertoire dans lequel le serveur écrit en continu. Vous ne pouvez pas le copier comme un seul fichier pendant que le serveur fonctionne. Le module 5 crée une vraie sauvegarde et prouve qu’elle peut être restaurée.

Erreur courante

docker compose s'arrête : POSTGRES_PASSWORD manque

Symptôme : docker compose up -d db affiche ceci, et rien ne démarre :

Error while interpolating services.api.environment.DATABASE_URL: required variable POSTGRES_PASSWORD is missing a value: Set POSTGRES_PASSWORD in .env

Le nom du service dans le message peut varier, par exemple services.migrate.

Cause : le dossier ne contient pas de fichier .env, ou ce fichier ne contient pas POSTGRES_PASSWORD. compose.yaml refuse de démarrer une base de données sans mot de passe.

Solution : exécutez docker compose dans le dossier du projet. Copiez .env.example vers .env (étape 2), définissez votre mot de passe (étape 3) et redémarrez le service.

Erreur courante

Le serveur ne fonctionne pas

Symptôme : python -m scripts.load affiche ceci :

Cannot connect to the database: connection failed: connection to server at "127.0.0.1", port 5432 failed: could not receive data from server: Connection refused
Is PostgreSQL running? With Docker: docker compose up -d db

Les mots après failed: (échec) peuvent varier d’un ordinateur à l’autre. python -m pytest donne 29 passed, 5 skipped (29 tests réussis, 5 ignorés) : sans serveur, les tests de base de données sont ignorés.

Cause : rien ne répond à 127.0.0.1:5432. Docker Desktop est fermé, le conteneur s’est arrêté ou l’ordinateur a redémarré.

Diagnostic : exécutez docker compose ps. Si db manque ou que son état n’est pas healthy (fonctionnel), le serveur ne fonctionne pas. Sans Docker, vérifiez le service PostgreSQL de votre système.

Solution : démarrez Docker Desktop, puis exécutez docker compose up -d db. Attendez l’état healthy, puis relancez la commande. Vos données sont toujours là : elles se trouvent dans un volume Docker, pas dans le conteneur.

Réinitialisation. python -m scripts.load --reset recharge les données du service de soutien. docker compose down --volumes supprime toute la base de données. Ensuite, exécutez docker compose up -d db et python -m scripts.load. docker compose down sans --volumes arrête le serveur et conserve les données.

Vérifiez votre compréhension

Dans vos mots : quelle est la différence entre SQLite et un serveur PostgreSQL? Utilisez les mots « client », « connexion » et « fichier ».

Afficher la réponse

SQLite est une bibliothèque dans votre programme. La base de données est un seul fichier que votre programme lit et modifie directement. PostgreSQL est un programme distinct, un serveur, qui possède les données. Les autres programmes sont des clients : ils ouvrent une connexion au serveur, s’identifient et envoient du SQL. Seul le serveur lit et modifie ses fichiers de données, et il vérifie chaque instruction.

Vérifiez votre compréhension

Votre DATABASE_URL est postgresql://tickets:pw@127.0.0.1:5433/tickets, mais le serveur écoute sur le port 5432. Prédisez ce que python -m scripts.load affiche et nommez la partie à corriger.

Afficher la réponse

La connexion échoue : Cannot connect to the database: connection failed: connection to server at "127.0.0.1", port 5433 failed: … (impossible de se connecter à la base de données : la connexion au serveur a échoué). Puis vient l’indice Is PostgreSQL running? (PostgreSQL est-il en cours d’exécution?). Le serveur fonctionne, mais derrière une autre porte : le port. Corrigez le port dans .env pour utiliser 5432. Si TEST_DATABASE_URL contient la même erreur, les tests de la base de données sont ignorés.

Vérifiez votre compréhension

Vérification pratique : montrez que votre configuration fonctionne. Quelles sont les deux commandes à exécuter, et que doivent-elles afficher?

Afficher la réponse

python -m scripts.load --reset affiche Database is up to date (4 migrations). (la base de données est à jour, avec 4 migrations). Elle affiche aussi les nombres : customers 40, tickets 200, messages 334, attachments 43, documents 36, ai_runs 294, ticket_embeddings 200. python -m pytest se termine par 34 passed (34 tests réussis).

Études de cas de ce cours

À la fin du cours, vous appliquez ce que vous avez appris à un projet réaliste, des données jusqu’à la recommandation. Terminez-en au moins une pour obtenir le certificat; les autres sont facultatives.

Récapitulatif

  • SQLite est un fichier qu’une bibliothèque dans votre programme lit. PostgreSQL est un serveur de base de données : les clients se connectent avec une chaîne de connexion (utilisateur, mot de passe, hôte, port, base de données). Le serveur vérifie chaque instruction.
  • Le projet ~/projects/ticket-api utilise PostgreSQL 18.6 avec pgvector 0.8.7, dans Docker (docker compose up -d db) ou avec un programme d’installation.
  • python -m scripts.load applique 4 migrations et charge les données fictives du service de soutien : 200 billets de soutien, 334 messages, 43 fichiers, 36 documents, 294 exécutions d’IA et 200 vecteurs. python -m pytest affiche 34 passed.
  • PostgreSQL a refusé le texte abc comme priorité et le 30 février comme date. SQLite a stocké les deux.
  • Un serveur a un coût : il doit fonctionner, il a des mots de passe et il faut de vraies sauvegardes.

Ensuite, vous chargez le grand jeu de données de 300 000 billets. Vous décrivez comment le service de soutien utilise chaque type de données.

Leçon suivante : Inventaire des modes d'accès