Migrations
- Leçon 10
- 50 min
- Module 3 : Accéder aux données depuis une API
Leçon 10 sur 16 · Bases de données et stockage pour les applications d'IAMigrations
1. Choisir le stockage selon la charge de travail
2. Concevoir un schéma relationnel
3. Accéder aux données depuis une API
4. Améliorer la performance des requêtes
5. Fichiers, vecteurs et cycle de vie
Ce que vous saurez faire
Modifiez la base de données d’une API en cours d’exécution sans surprises. Écrivez un fichier numéroté par changement, appliquez-le une seule fois et ne le modifiez jamais. Faites un essai sur beaucoup de données. Aucun changement ne doit bloquer longtemps une table occupée.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Les fichiers de migration numérotés et la table
schema_migrations, présentés dans De SQLite à PostgreSQL. Si vous avez suivi CI/CD pour les systèmes d’IA, Migrations sûres les présentait plus en détail; ce n’est pas nécessaire ici. - Les migrations
005et006, présentées dans Contraintes d’intégrité. - Le pool de connexions (pool) et ses connexions, présentés dans Gestion des connexions.
Ce qu’il vous faut sur votre ordinateur
- Le projet après Gestion des connexions :
python -m pytestaffiche74 passed(74 tests réussis). - Les données volumineuses chargées dans votre base de données, tirées de l’exercice guidé de cette leçon. Deux terminaux.
Après cette leçon, votre projet correspond à l’instantané end-of-m03. Le rapport qui garde un verrou, la migration modifiée et les fichiers temporaires sont des cas construits. Testé sur macOS (Apple silicon); les commandes Windows n’ont pas été testées. Les durées ont été mesurées sur un ordinateur Apple M4 sans autre activité. Vos durées seront différentes; la tendance restera la même.
Des travaux sur une rue achalandée
Section intitulée « Des travaux sur une rue achalandée »Une ville répare ses routes avec des ordres de travail numérotés. Elle exécute chaque ordre une seule fois et ne réécrit jamais un ordre terminé. Si la réparation était incorrecte, la ville écrit un nouvel ordre. Ainsi, chacun peut lire la liste et connaître l’état actuel de la route.
La même réparation peut être facile ou difficile. Sur une route d’essai tranquille, fermer une voie pendant une heure ne dérange personne. Sur la rue principale à l’heure de pointe, une fermeture de deux minutes crée une file d’un kilomètre. Pire encore, une voiture qui voulait seulement tourner à droite attend aussi, car elle est coincée derrière la file.
Un changement de base de données, c’est un chantier sur la rue qu’emprunte l’API.
Les migrations jusqu’ici
Section intitulée « Les migrations jusqu’ici »Une migration est un fichier SQL numéroté qui modifie la base de données, appliqué par ticket_api/migrate.py. Le projet a repris cet outil d’exécution du cours sur la CI/CD. L’outil applique les fichiers dans l’ordre de leurs noms, inscrit chacun dans schema_migrations et exécute chacun dans sa propre transaction. Votre base de données en contient six : 001 à 004 de la configuration initiale, puis 005 et 006 du module 2.
Ces règles donnent un ordre et un historique. Elles ne répondent pas aux trois questions que se pose maintenant le service de soutien. Que se passe-t-il si quelqu’un modifie un fichier déjà exécuté? Combien de temps prend un changement sur 300 000 billets de soutien? Et quel effet a un changement sur l’API qui lit la table en même temps?
Le problème : rapide sur peu de données, bloquant sur beaucoup de données
Section intitulée « Le problème : rapide sur peu de données, bloquant sur beaucoup de données »La migration 006 ajoute deux colonnes à tickets et les remplit, dans une seule transaction. Sur les données peu volumineuses, elle a pris 0,01 secondes. Mei l’a testée de nouveau sur les données volumineuses, pendant qu’un lecteur demandait un billet toutes les 10 ms :
- Données peu volumineuses
- Données volumineuses
- Attente de lecture (volumineuses)
Description textuelle du graphique
Trois barres par migration. Les deux premières indiquent les secondes sur les données peu volumineuses et volumineuses. La troisième montre l’attente maximale d’une lecture par clé pendant l’exécution sur les données volumineuses. 005 (contraintes) : 0,02 s sur peu de données, 1,16 s sur beaucoup de données. Les lectures ont attendu 0,88 s. 006 (deux colonnes et leur remplissage) : 0,01 s sur peu de données, 1,83 s sur beaucoup de données. Les lectures de tickets ont attendu 1,81 s. 007 (le coût en numeric) : 0,02 s sur peu de données, 0,37 s sur beaucoup de données. Les lectures de ai_runs ont attendu 0,35 s. 008 (trois index, créés avec CONCURRENTLY dans le module 4) : 0,02 s sur peu de données, 0,97 s sur beaucoup de données. Les lectures ont attendu 0 s. Les étiquettes arrondissent à une décimale; les valeurs inférieures à 0,05 s n’ont pas d’étiquette.
Ce que cela nous apprend. Sur les données peu volumineuses, chaque migration semble instantanée : les petites barres sont trop courtes pour être visibles. Sur les données volumineuses, 006 a pris 1,83 secondes. Pendant 1,81 de ces secondes, aucune requête ne pouvait lire un seul billet. La table est aussi passée de 75 Mo à 152 Mo. 008 a aussi pris presque une seconde, mais personne n’a attendu : le problème n’est pas la durée, c’est le blocage.
Pourquoi les lectures ont-elles attendu? Chaque instruction prend un verrou sur les tables qu’elle utilise. Ce verrou indique ce que les autres peuvent faire avec la table en même temps. Un SELECT normal prend le verrou le plus faible, qui permet presque tout. ALTER TABLE … ADD COLUMN prend le plus fort, ACCESS EXCLUSIVE (accès exclusif), qui ne permet rien, même pas une lecture. PostgreSQL garde chaque verrou jusqu’à la fin de la transaction. Dans 006, le UPDATE des 300 000 lignes s’exécute dans la même transaction. Le verrou le plus fort reste donc en place pendant tout le remplissage.
Et pourquoi la table a-t-elle doublé de taille? PostgreSQL ne modifie jamais une ligne sur place. Un UPDATE écrit une nouvelle version de chaque ligne. L’ancienne version reste jusqu’à ce qu’un nettoyage appelé VACUUM réutilise son espace. Les 300 000 lignes mises à jour ont créé 300 000 nouvelles versions.
L’idée : faire un essai sur des données représentatives
Section intitulée « L’idée : faire un essai sur des données représentatives »Pour éviter les surprises, faites un essai. Avant d’exécuter une migration sur la vraie base de données, exécutez-la sur une copie contenant des données semblables aux vraies. Il faut les mêmes tables, la même taille et le même mélange de valeurs. Ce sont des données représentatives. Les données volumineuses du cours sont les données représentatives du service de soutien.
scripts/rehearse.py le fait en une commande. Il crée une base de données distincte, <your database>_rehearsal. Il applique les migrations précédant la première à tester, charge les données volumineuses, puis applique les autres une à une. Pendant chaque migration, un lecteur demande une ligne par clé dans chaque table, toutes les 10 ms. L’attente maximale montre combien de temps la migration a bloqué les lectures normales. Votre propre base de données ne change pas. Voici l’exécution de l’auteur, à partir de 005 :
Loading the large data into tickets_rehearsal (migrations up to 004_help_desk_tables) ...migration seconds longest read wait (s): tickets messages ai_runs attachments005_integrity_constraints 1.29 0.93 0.70 0.17 0.47006_ticket_message_counts 2.11 2.08 0.00 0.00 0.01007_ai_run_cost_numeric 0.44 0.00 0.00 0.41 0.00Il s’agit d’une seule exécution; ses chiffres diffèrent donc un peu des médianes du graphique. La tendance est ce qui compte : 006 bloque tickets, et 007 bloque ai_runs. 005 bloque plusieurs tables pendant moins d’une seconde. En effet, SET NOT NULL et les nouvelles vérifications lisent chaque ligne sous un verrou fort.
Écrire le prochain changement de façon sûre
Section intitulée « Écrire le prochain changement de façon sûre »006 a déjà été exécutée sur chaque base de données du cours; elle reste donc telle quelle. Modifier une migration appliquée est la chose qu’un outil d’exécution de migrations doit interdire (voir les nouvelles règles). La leçon concerne le prochain changement de ce genre. Divisez-le en petites étapes pour qu’aucune étape ne garde un verrou fort longtemps :
Description textuelle du diagramme
À gauche, 006 : la colonne et son remplissage forment une seule transaction. Le verrou le plus fort dure donc aussi longtemps que le UPDATE complet. À droite, l’ordre sûr. Ajoutez d’abord la colonne sans valeur par défaut ni règle. Cela modifie seulement la description de la table et prend quelques millisecondes. Remplissez-la ensuite par lots. Chaque lot forme une courte transaction distincte et prend seulement des verrous de ligne, qui ne bloquent pas les lectures. Enfin, ajoutez la règle quand chaque ligne a une valeur.
Ce modèle s’appelle extension et contraction. Commencez par l’extension : ajoutez la nouvelle colonne, dont rien ne dépend encore. Remplissez-la ensuite. Terminez par la contraction : resserrez le schéma, ici avec la règle, quand chaque ligne est prête. Migrations sûres, dans le cours sur la CI/CD, utilise ce modèle pour renommer une colonne; ici, il protège une grande table. Les étapes prennent plus de temps au total; la tâche À votre tour le mesure. Pour la dernière étape, PostgreSQL peut ajouter une vérification sans long blocage. ADD CONSTRAINT … NOT VALID prend un verrou bref et vérifie seulement les nouvelles lignes. VALIDATE CONSTRAINT vérifie ensuite les anciennes lignes pendant que les lectures et les écritures continuent.
Larkfield : 007, l’argent en numeric
Section intitulée « Larkfield : 007, l’argent en numeric »Priya additionne le coût des exécutions d’IA. L’ancien outil stockait chaque coût en real, un nombre à virgule flottante d’environ 7 chiffres significatifs, conservé en binaire. Beaucoup de valeurs décimales ne peuvent pas être stockées exactement en binaire. La valeur 0.002432 est stockée sous la forme 0.0024319998919963837. L’auteur a exécuté la même somme six fois sur les données volumineuses, avant 007 :
SELECT sum(cost_usd), sum(cost_usd::numeric) FROM ai_runs;95.059525|95.059678795.05952|95.059678795.059494|95.059678795.05954|95.059678795.05959|95.059678795.05959|95.0596787La colonne de gauche contient la somme des real. Elle change d’une exécution à l’autre. PostgreSQL additionne les lignes en parallèle, dans un ordre différent chaque fois, et chaque addition arrondit le résultat. La colonne de droite contient la somme exacte, 95.0596787 dollars, chaque fois. Soyons honnêtes sur l’ampleur : l’écart est d’au plus 0,0002 dollar sur 449 734 exécutions. Personne ne perd d’argent ici. Le problème, c’est qu’un rapport donne un total différent chaque fois. Et le coût stocké n’est pas le coût qui a été écrit. Cela ne représente pas correctement l’argent.
La migration 007 change donc le type. numeric(12, 8) stocke jusqu’à 12 chiffres, dont 8 après la virgule, exactement, en décimal :
-- Money is not a floating-point number. The old tool stored the cost of each AI-- run as real (about 7 significant digits, in binary), so sums drift and some-- values cannot be stored exactly. numeric(12, 8) stores the dollars exactly.-- This rewrites the whole table and blocks it while it runs: time it on the-- large data before you run it on a busy database.ALTER TABLE ai_runs ALTER COLUMN cost_usd TYPE numeric(12, 8);L’essai indiquait 0,37 secondes sur les données volumineuses, avec les lectures de ai_runs bloquées pendant 0,35 secondes. C’est acceptable pour le service de soutien, à un moment tranquille. Après 007, la somme est 95.05967870, chaque fois.
La file d’attente des verrous
Section intitulée « La file d’attente des verrous »Mei exécute 007 sur une copie des données volumineuses, et quelque chose d’étrange se produit. Le rapport de Priya garde une transaction ouverte sur ai_runs : il a lu la table sans encore valider la transaction. C’est permis : un verrou de lecture et d’autres lectures partagent la table. Mais 007 a besoin du verrou le plus fort; elle attend donc le rapport. Puis chaque nouvelle lecture de ai_runs attend aussi, derrière 007. PostgreSQL traite les demandes de verrou dans l’ordre. Une lecture ne peut donc pas dépasser une migration en attente. C’est la voiture qui veut tourner, coincée derrière la file.
Description textuelle du diagramme
Le rapport et la lecture ne font que lire; ils pourraient donc partager la table. Mais la migration se trouve entre eux dans la file et a besoin de la table pour elle seule. La lecture attend donc la migration, qui attend le rapport. Sans limite, la lecture a attendu 5,9 secondes, puis a échoué. Avec un délai maximal d’attente de verrou de 2 secondes, la migration a abandonné à 2,51 s et quitté la file. La lecture a obtenu sa ligne après 2,0 secondes, sans erreur.
Cette image présente deux leçons.
Un délai maximal d’attente de verrou limite les dégâts. lock_timeout indique à PostgreSQL combien de temps une instruction peut attendre un verrou. Le nouveau migrate.py le définit pour chaque migration à partir de MIGRATION_LOCK_TIMEOUT, avec 5s par défaut. Une migration qui attend plus longtemps s’arrête avec un message clair et ne change rien. Relancez-la à un moment plus tranquille. Une migration arrêtée coûte peu; une API arrêtée coûte cher.
Un changement de type brise les requêtes préparées par une application en cours d’exécution. psycopg prépare une requête sur le serveur après 5 exécutions sur une même connexion. PostgreSQL garde ensuite son plan, avec le type de chaque colonne du résultat. Après 007, le plan indique real et la colonne indique numeric. L’auteur a vérifié la suite dans un test construit. Une seule connexion de longue durée échouait à chaque exécution avec cached plan must not change result type (le plan en cache ne doit pas changer le type du résultat), jusqu’à sa reconnexion. L’API du projet, avec son pool, échouait une fois par connexion du pool qui avait préparé la requête. Elle fonctionnait ensuite de nouveau.
ADD COLUMN produit le même effet sur un SELECT * préparé. C’est pourquoi le SELECT * FROM tickets de la liste des billets a échoué une fois après l’ajout d’une colonne, dans le même test. Après une migration qui change ce qu’une requête retourne, redémarrez donc l’API ou renouvelez les connexions du pool. Mieux encore, nommez vos colonnes plutôt que d’utiliser SELECT *; le module 4 le fait.
Les nouvelles règles de l’outil d’exécution
Section intitulée « Les nouvelles règles de l’outil d’exécution »Le nouveau ticket_api/migrate.py ajoute trois règles aux anciennes.
Une migration appliquée ne change jamais. L’outil stocke maintenant une somme de contrôle de chaque fichier dans schema_migrations, une empreinte SHA-256 de ses octets. Si un fichier appliqué change, rien ne s’exécute :
Migration stopped: 005_integrity_constraints.sql was changed after it was applied. Put it back as it was, and write the change as a new migration.Pourquoi être si strict? Un fichier modifié s’exécute seulement sur les nouvelles bases de données. Les anciennes gardent l’ancienne version; les deux sortes de bases divergent donc sans que personne le remarque. Ce n’est pas théorique. Pendant la création du projet du cours, l’auteur a modifié 012_erasure.sql après son application. Cette règle a arrêté l’exécution suivante avec le même message. La solution était une nouvelle migration.
Une migration n’attend pas indéfiniment. MIGRATION_LOCK_TIMEOUT, comme expliqué plus haut.
Certaines instructions ne peuvent pas s’exécuter dans une transaction. CREATE INDEX CONCURRENTLY crée un index sans bloquer les écritures, et PostgreSQL l’interdit dans une transaction. Un fichier dont la première ligne est -- migrate: no-transaction s’exécute hors transaction, une instruction à la fois. Le module 4 en a besoin pour ses index. Si la création concurrente d’un index échoue à mi-chemin, elle peut laisser un index marqué comme invalide. L’outil vérifie ce cas et s’arrête en indiquant le nom de l’index.
Beaucoup d’équipes utilisent un outil pour cela : Alembic avec SQLAlchemy, ou Flyway. Ces outils enregistrent les versions et les sommes de contrôle de la même façon. Ce cours garde son outil d’exécution de 150 lignes pour que vous puissiez lire chaque règle.
Exercice guidé : 007 et le nouvel exécuteur
Section intitulée « Exercice guidé : 007 et le nouvel exécuteur »-
Dans
~/projects/ticket-api, avec l’environnement virtuel actif, copiez les quatre fichiers de cette leçon depuis l’instantanéend-of-m03:Fenêtre de terminal S=../nextia-learning-labs/C20/snapshots/end-of-m03for f in ticket_api/migrate.py migrations/007_ai_run_cost_numeric.sql scripts/rehearse.py tests/test_migrations.py; docp "$S/$f" "$f"doneFenêtre de terminal $S = "..\nextia-learning-labs\C20\snapshots\end-of-m03"foreach ($f in 'ticket_api\migrate.py', 'migrations\007_ai_run_cost_numeric.sql', 'scripts\rehearse.py', 'tests\test_migrations.py') { Copy-Item "$S\$f" $f } -
Vérifiez ce qui est en attente :
python -m ticket_api.migrate --status. À sa première exécution, le nouvel exécuteur enregistre aussi les sommes de contrôle des six fichiers appliqués. -
Faites une répétition à partir de
005sur les données volumineuses :python -m scripts.rehearse --from 005. Elle prend environ une demi-minute et ne modifie pas votre base de données. -
Additionnez les coûts avant le changement :
docker compose exec db psql -U tickets -d tickets -c "SELECT sum(cost_usd), sum(cost_usd::numeric) FROM ai_runs;". Exécutez cette commande deux ou trois fois. -
Appliquez
007:python -m ticket_api.migrate. Puis exécutez de nouveau la somme de l’étape 4. -
Exécutez les tests :
python -m pytest. Vous devriez voir79 passed(79 tests réussis). Votre projet correspond maintenant à l’instantanéend-of-m03. -
Essayez la règle de somme de contrôle. Ajoutez une ligne à un fichier appliqué et exécutez les migrations :
Fenêtre de terminal echo "-- a small edit" >> migrations/005_integrity_constraints.sqlpython -m ticket_api.migratePuis restaurez le fichier depuis l’instantané :
cp "$S/migrations/005_integrity_constraints.sql" migrations/(Windows :Copy-Item "$S\migrations\005_integrity_constraints.sql" migrations\). Exécutez de nouveaupython -m ticket_api.migrate. -
Essayez la règle sans transaction avec un fichier temporaire. Créez
migrations/900_index.sqlavec une ligne,CREATE INDEX CONCURRENTLY tickets_subject_idx ON tickets (subject);, et exécutezpython -m ticket_api.migrate. Puis supprimez le fichier.
Afficher le résultat attendu
L’étape 2 affiche applied (appliqué) pour 001 à 006 et pending 007_ai_run_cost_numeric (007_ai_run_cost_numeric en attente). L’étape 3 affiche un tableau comme celui ci-dessus. L’étape 4 affiche chaque fois la somme exacte 95.0596787 dans la colonne de droite. La colonne de gauche varie un peu entre les exécutions. L’étape 5 affiche applied 007_ai_run_cost_numeric (007_ai_run_cost_numeric appliqué) et Database is up to date (7 migrations). (la base de données est à jour, 7 migrations). La somme est alors 95.05967870. L’étape 7 affiche le message Migration stopped: 005_integrity_constraints.sql was changed after it was applied. … (migration arrêtée : 005_integrity_constraints.sql a été modifié après son application). Après la restauration du fichier, elle affiche Database is up to date (7 migrations). L’étape 8 affiche :
Migration stopped: 900_index.sql failed: CREATE INDEX CONCURRENTLY cannot run inside a transaction blockIl ne reste rien de 900, car la transaction échouée a été annulée. Si python -m ticket_api.migrate --status affiche encore un fichier 900, vous ne l’avez pas supprimé.
À vous de jouer : ajouter une colonne sans bloquer
Section intitulée « À vous de jouer : ajouter une colonne sans bloquer »Changez une seule chose : créez la prochaine colonne de façon sûre. Le service de soutien veut subject_length, le nombre de caractères de chaque objet. Écrivez deux migrations : 900_ticket_subject_length.sql ajoute la colonne, et 901_backfill_subject_length.sql la remplit par lots de 50 000 lignes. Chaque lot a sa propre transaction. Faites une répétition sur les données volumineuses avec python -m scripts.rehearse --from 900. Faites d’abord une prédiction : combien de temps les lectures de tickets attendront-elles, par rapport à 006?
Afficher une solution et le résultat de l’auteur
-- Expand: a new column that may be empty. No default and no NOT NULL, so-- PostgreSQL changes only the table's description, not its 300,000 rows.ALTER TABLE tickets ADD COLUMN subject_length integer;-- migrate: no-transaction-- Backfill in batches: each UPDATE is its own short transaction, so no lock-- is held for long. A statement that finds no empty row changes nothing.UPDATE tickets SET subject_length = length(subject)WHERE ticket_id IN (SELECT ticket_id FROM tickets WHERE subject_length IS NULL LIMIT 50000);La première ligne de 901 indique à l’exécuteur d’envoyer chaque instruction séparément. Chaque lot est donc validé séparément. Sept lots couvrent 300 000 lignes; le septième ne trouve rien. La répétition de l’auteur :
migration seconds longest read wait (s): tickets messages ai_runs attachments900_ticket_subject_length 0.01 0.00 0.00 0.00 0.00901_backfill_subject_length 4.69 0.01 0.01 0.00 0.01Les lectures de tickets ont attendu 0,01 s, contre environ 2 s pour 006. Le remplissage a pris plus de temps au total. Chaque lot cherche à nouveau les lignes vides sur un ordinateur occupé. Voilà le compromis : plus de temps au total, aucun blocage. Puis supprimez les deux fichiers. Ils servent à l’exercice; ne les exécutez jamais avec python -m ticket_api.migrate sur votre base de données. La base de données de répétition a été supprimée à la fin.
Une migration qui attend derrière une transaction ouverte
Section intitulée « Une migration qui attend derrière une transaction ouverte »Erreur courante
Une migration attend un verrou détenu par une session oubliée
Symptôme (simulé) : python -m ticket_api.migrate s’arrête après quelques secondes avec un message de verrou, sans rien modifier. Reproduisez-le avec deux terminaux et une migration temporaire. Dans le terminal 1, ouvrez psql et laissez une transaction ouverte :
docker compose exec db psql -U tickets -d ticketsBEGIN;SELECT count(*) FROM ai_runs;Dans le terminal 2, créez migrations/900_reviewed.sql avec la ligne ALTER TABLE ai_runs ADD COLUMN reviewed boolean;. Puis exécutez les migrations avec un délai limite court :
MIGRATION_LOCK_TIMEOUT=2s python -m ticket_api.migrate$env:MIGRATION_LOCK_TIMEOUT = "2s"; python -m ticket_api.migrate; Remove-Item Env:MIGRATION_LOCK_TIMEOUTMigration stopped: 900_reviewed.sql waited more than 2s for a lock. Another transaction is using the table. Try again when it is quiet.Cause : la transaction ouverte dans le terminal 1 détient un verrou sur ai_runs. La migration a besoin du verrou le plus fort, alors elle attend. Sans la limite, chaque lecture de ai_runs depuis l’API attendrait derrière elle.
Diagnostic : trouvez la session qui verrouille la table : SELECT pid, state, now() - xact_start AS open_for, query FROM pg_stat_activity WHERE state LIKE 'idle in transaction%';. Une session qui reste idle in transaction (inactive dans une transaction) pendant plusieurs minutes provient généralement d’une session psql, d’un rapport ou d’une connexion non libérée.
Correction :
- Dans le terminal 1, terminez la transaction :
ROLLBACK;, puis\q. - Supprimez le fichier temporaire
migrations/900_reviewed.sql, pour qu’il ne soit jamais exécuté.python -m ticket_api.migrate --statusdoit afficher seulement001à007. - En situation réelle, trouvez qui détient le verrou, attendez une période calme et relancez la migration. N’augmentez pas le délai limite jusqu’à ce qu’elle passe : cela ne fait que déplacer la file d’attente vers l’API.
Pour un service d’IA : les tâches d’analyse gardent souvent des transactions ouvertes longtemps. C’est le cas du rapport de Priya ou d’une évaluation sur la table des exécutions d’IA. Planifiez les changements de schéma en dehors de ces tâches et gardez un délai d’attente de verrou court. Ainsi, une migration échoue rapidement au lieu de bloquer l’API.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Un collègue trouve une faute dans un commentaire de 005_integrity_constraints.sql et la corrige dans le fichier. Que se passe-t-il au prochain déploiement, et que devrait-il faire à la place?
Afficher la réponse
Le prochain python -m ticket_api.migrate s’arrête avant d’appliquer quoi que ce soit : 005_integrity_constraints.sql was changed after it was applied (005_integrity_constraints.sql a été modifié après son application). La somme de contrôle du fichier ne correspond plus à celle de schema_migrations. Restaurez le fichier exactement comme il était et gardez la faute; un commentaire ne modifie pas la base de données. Un vrai changement va dans une nouvelle migration, comme 008_…, pour que chaque base de données le reçoive dans le même ordre.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Faites une prédiction : la migration 006 a pris 0,01 s sur les petites données. Pourquoi un test sur ces données ne révèle-t-il pas ses 1,8 secondes de lectures bloquées? Que fait la répétition différemment?
Afficher la réponse
La durée de 006 augmente avec le nombre de lignes que son UPDATE réécrit. Pour 200 billets, elle prend des millisecondes; pour 300 000, des secondes. Le verrou est le même dans les deux cas. Mais sur 200 lignes, il dure trop peu de temps pour que quelqu’un le remarque. scripts.rehearse exécute les migrations sur une copie contenant les données volumineuses. Pendant chaque migration, un lecteur demande une ligne toutes les 10 ms. La répétition montre donc la durée et le temps d’attente des lectures normales.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Avec MIGRATION_LOCK_TIMEOUT=2s, la migration 007 s'est arrêtée sans changer le type du coût. Était-ce un échec? Que devrait faire l'équipe ensuite?
Afficher la réponse
C’était le résultat sûr. Sans la limite, les lectures de ai_runs attendaient 5,9 secondes, puis échouaient. Avec la limite, la migration abandonnait après ses 2 secondes d’attente. Les lectures attendaient au plus 2,0 secondes, sans erreur. Ensuite, trouvez la transaction qui verrouillait la table (pg_stat_activity, idle in transaction) et relancez 007 quand elle sera terminée. Après la réussite de 007, redémarrez l’API ou renouvelez ses connexions. Le type de la colonne a changé alors que l’API avait déjà préparé des requêtes.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Une migration est un fichier SQL numéroté, appliqué une fois, dans l’ordre, dans sa propre transaction. L’exécuteur stocke maintenant aussi une somme de contrôle. Toute modification d’un fichier déjà appliqué arrête tout.
- Les petites données cachent le coût.
006a pris 0,01 s sur 200 billets et 1,83 s sur 300 000. Elle a bloqué chaque lecture deticketspendant 1,81 s. En effet,ALTER TABLEgarde le verrou le plus fort jusqu’à la fin de sa transaction. - Faites une répétition de chaque migration sur des données représentatives avec
scripts.rehearse. Ajoutez d’abord une colonne, remplissez-la par lots et ajoutez la règle en dernier. 007stocke le coût sous forme denumeric(12, 8). La somme enrealvariait entre les exécutions; la somme exacte est toujours de95.0596787dollars.- Une migration en attente fait attendre chaque lecture suivante : 5,9 s et une erreur sans limite, au plus 2,0 s avec
MIGRATION_LOCK_TIMEOUT=2s. Après un changement de type, redémarrez l’API ou renouvelez ses connexions. - Votre projet correspond à
end-of-m03:python -m pytestaffiche79 passed. Le module 4 accélère la liste lente, en commençant par son plan de requête.
Leçon suivante : Lire un plan de requête