BUILDING TO SCALE
FR | EN
Parler de votre projet

Comment rattraper des millions de lignes dans une base Rails sans interruption de service

Rattraper une table de quarante millions de lignes n'est pas le même problème que d'en rattraper une de quarante mille. Ce qui change, et comment nous le régulons en toute sécurité.

Publié le 18 juin 2023 · 4 min de lecture

Remplir une nouvelle colonne sur une table de quarante millions de lignes n'est pas le même problème que le faire sur une table de quarante mille. En dessous d'une certaine taille, une mise à jour en masse unique fonctionne très bien. Au-dessus, la même ligne de code peut verrouiller la table, dépasser le statement timeout ou gonfler le journal de transactions au point de remplir le disque.

Toujours par lots

Invoice.where(currency: nil).in_batches(of: 2_000) do |batch|
  batch.update_all(currency: "EUR")
  sleep 0.05 # laisser les réplicas et l'autovacuum suivre
end

Chaque lot est sa propre transaction : en cas d'échec en cours de route, vous disposez d'un point de reprise au lieu d'une opération tout ou rien couvrant toute la table. Le petit sleep entre les lots n'est pas de la paresse : il laisse au retard de réplication et à l'autovacuum le temps de suivre le volume d'écriture au lieu de prendre un retard définitif.

À exécuter en dehors du cycle de requêtes et de déploiement

Une tâche rake ou un job en arrière-plan, jamais une migration exécutée pendant le déploiement. Une migration qui bloque un déploiement pendant vingt minutes le temps du rattrapage est une panne que l'on s'inflige soi-même ; un job qui exécute la même logique en arrière-plan, supervisé et reprenable en cas d'échec, n'en est pas une.

Surveillez la base de données, pas seulement le job

Le retard de réplication, l'activité de l'autovacuum et les I/O disque sont les métriques qui disent réellement si un backfill peut continuer à son rythme actuel, pas le journal de progression du job lui-même. Sur toute table qui reçoit du trafic de production actif, nous régulons dynamiquement la taille des lots et l'intervalle de sleep en fonction du retard de réplication, en ralentissant automatiquement dès que la base montre des signes de décrochage.

Les backfills qui alimentent un index demandent une attention particulière

Remplir une nouvelle colonne qui sera immédiatement indexée double le coût d'écriture de chaque lot : la mise à jour de la ligne, puis la maintenance de l'index qui en découle. Quand c'est possible, nous remplissons d'abord la colonne, puis nous construisons l'index de manière concurrente ensuite, plutôt que de faire payer le coût de l'index à chaque lot dès le départ.