Skip to content

Tech: ajoute la possibilité de configurer un statement_timeout - #6323

Merged
xavfernandez merged 1 commit into
masterfrom
xfernandez/statement_timeout
Jun 24, 2025
Merged

Tech: ajoute la possibilité de configurer un statement_timeout#6323
xavfernandez merged 1 commit into
masterfrom
xfernandez/statement_timeout

Conversation

@xavfernandez

Copy link
Copy Markdown
Contributor

🤔 Pourquoi ?

Pour éviter que des locks en cascade ne bloquent toute la production.

🍰 Comment ?

Décrivez en quelques mots la solution retenue et mise en oeuvre, les difficultés ou problèmes rencontrés. Attirez l'attention sur les décisions d'architecture ou de conception importantes.

🚨 À vérifier

  • Mettre à jour le CHANGELOG_breaking_changes.md ?
  • Ajouter l'étiquette « Bug » ?

🏝️ Comment tester ?

Les instructions pour reproduire le problème, les profils de test, le parcours spécifique à utiliser, etc. Si vous disposez d'une recette jetable, mettre l'URL pour tester dans cette partie.

💻 Captures d'écran

@xavfernandez xavfernandez self-assigned this Jun 12, 2025
@xavfernandez xavfernandez added the ajouté Ajouté dans le changelog. label Jun 12, 2025
Comment thread config/settings/base.py Outdated
"OPTIONS": {
"connect_timeout": 5,
**(
{"options": f"-c statement_timeout={int(db_statement_timeout)}"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On voudrais pas plutôt utiliser lock_timeout ? Car là toute requête un peu longue va se faire tuer ce qui peux être un peu chiant au niveau des migrations ou des scripts, surtout avec le mode maintenance, et ça permettrais de pouvoir spécifier la valeur par défaut pour tout les environnements et avoir l'env var que pour éventuellement débrayer.

Je suppose que tu prévois qu'on utilise que WSGI_DB_STATEMENT_TIMEOUT pour justement ne pas avoir ce genre de problème, et même si c'est plus élégant je trouve ça un peu bizarre d'avoir 2 options mais une seul d'utilisée.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utilise[r] que WSGI_DB_STATEMENT_TIMEOUT

Je pense qu’on n’a pas trop le choix, puisque la configuration de la connection est dans les settings, qui sont communs à toutes nos instances de django.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pour moi, statement timeout reste une bonne idée, surtout si on reste sur l’idée d’un statement timeout généreux à 15s. Un lock_timeout serait une sécurité supplémentaire qui me semble judicieuse, et on pourrait le descendre à 3-5s ?

@xavfernandez xavfernandez Jun 12, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nouvelle mouture pour détecter directement si on tourne dans uwsgi ou non.

Je pourrais donc éventuellement dropper DB_STATEMENT_TIMEOUT et n'avoir qu'un setting mais je le trouve pratique pour tester le comportement (vs lancer l'app avec uwsgi - UWSGI_DB_STATEMENT_TIMEOUT=1 uwsgi --http :9090 --wsgi-file config/wsgi.py --master --processes 4 --threads 2).
Et on pourrait aussi en fait avoir les deux en mettant un DB_STATEMENT_TIMEOUT à 60_000 pour nos cron/huey/migrations, sachant qu'on a rarement des requêtes qui durent aussi longtemps et que ça nous aurait (peut-être) évité le crash du dead lock de la migration des diagnostics (https://github.com/gip-inclusion/les-emplois/pull/6162/files).

Après statement_timeout vs lock_timeout: je suis plutôt pour éviter tout ce qui est trop long sur www.
Dans les faits ça sera des locks dans 80% des cas car on n'a (a priori 🤞 ) pas de grosses requêtes qui prennent plus de 7 secondes.

On pourrait effectivement combiner les 2 avec un statement_timeout à 10_000 et un lock_timeout à 5_000 (avec leurs déclinaisons UWSGI_DB_ / DB_).

Et effectivement, ça aurait du sens de mettre des valeurs par défaut (en plus, ça simplifie un peu le code).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pour moi c’est bien, tu fais une mouture supplémentaire avec lock_timeout ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est fait.

@xavfernandez
xavfernandez force-pushed the xfernandez/statement_timeout branch 2 times, most recently from e5cfd62 to 00ec4ee Compare June 12, 2025 14:25
Comment thread config/settings/base.py
Comment thread config/settings/base.py Outdated
uwsgi_db_statement_timeout = None
else:
uwsgi_db_statement_timeout = os.environ.get("UWSGI_DB_STATEMENT_TIMEOUT", 10_000)
db_statement_timeout = int(uwsgi_db_statement_timeout or os.getenv("DB_STATEMENT_TIMEOUT", 60_000))

@francoisfreitag francoisfreitag Jun 12, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a déjà des statements plus long (voir les logs de la DB dans clever). J’ai facilement trouvé 85s.
Edit : Et même 123s en cherchant un peu plus.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effectivement: j'ai mis 0 comme valeur par défaut (équivalent à pas de timeout).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J’aurais bien mis un statement_timeout = 300_000 par défaut, histoire d’avoir une baseline. On pourra travailler à la réduire ensuite ?

@xavfernandez
xavfernandez force-pushed the xfernandez/statement_timeout branch 2 times, most recently from 659b955 to 04fcfae Compare June 12, 2025 21:17
@celine-m-s celine-m-s added tech and removed ajouté Ajouté dans le changelog. labels Jun 13, 2025
@xavfernandez
xavfernandez force-pushed the xfernandez/statement_timeout branch from 04fcfae to 3a50f00 Compare June 18, 2025 08:11
Comment thread config/settings/base.py Outdated
Comment on lines +208 to +215
except ImportError:
uwsgi_db_statement_timeout = None
uwsgi_db_lock_timeout = None
else:
uwsgi_db_statement_timeout = os.environ.get("UWSGI_DB_STATEMENT_TIMEOUT", 10_000)
uwsgi_db_lock_timeout = os.environ.get("UWSGI_DB_LOCK_TIMEOUT", 5_000)
db_statement_timeout = int(uwsgi_db_statement_timeout or os.getenv("DB_STATEMENT_TIMEOUT", 0))
db_lock_timeout = int(uwsgi_db_lock_timeout or os.getenv("DB_LOCK_TIMEOUT", 0))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je simplifierais la logique :

Suggested change
except ImportError:
uwsgi_db_statement_timeout = None
uwsgi_db_lock_timeout = None
else:
uwsgi_db_statement_timeout = os.environ.get("UWSGI_DB_STATEMENT_TIMEOUT", 10_000)
uwsgi_db_lock_timeout = os.environ.get("UWSGI_DB_LOCK_TIMEOUT", 5_000)
db_statement_timeout = int(uwsgi_db_statement_timeout or os.getenv("DB_STATEMENT_TIMEOUT", 0))
db_lock_timeout = int(uwsgi_db_lock_timeout or os.getenv("DB_LOCK_TIMEOUT", 0))
except ImportError:
db_statement_timeout = int(uwsgi_db_statement_timeout or os.getenv("DB_STATEMENT_TIMEOUT", 0))
db_lock_timeout = int(uwsgi_db_lock_timeout or os.getenv("DB_LOCK_TIMEOUT", 0))
else:
db_statement_timeout = os.environ.get("UWSGI_DB_STATEMENT_TIMEOUT", 10_000)
db_lock_timeout = os.environ.get("UWSGI_DB_LOCK_TIMEOUT", 5_000)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mais ta version empêche les confs DB_ de s'appliquer quand on tourne en uwsgi alors que je m'attendrais à avoir une conf générale DB_xxx éventuellement surchargée par UWSGI_DB_ si défini.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dans la version d'avant qu'il y a un défaut pour les USWGI_ on ne prend pas les DB_ dans tout les cas, à moins de mettre les USWGI_ à vide ce qui peux faire bizarre puisque 0 veux dire pas de timeout.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effectivement ma version actuelle n'est pas mieux 😅

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bon je vois 2 options:

  • rester sur l'équivalent du code actuel/la proposition de Romain avec:
try:
    import uwsgi
except ImportError:
    db_statement_timeout = os.environ.get("NOT_UWSGI_DB_STATEMENT_TIMEOUT", 300_000)
else:
    db_statement_timeout = os.environ.get("UWSGI_DB_STATEMENT_TIMEOUT", 10_000)

(le nom NOT_UWSGI_DB_STATEMENT_TIMEOUT n'est pas très joli mais a le mérite d'être très clair)

  • aller vers ce que j'avais en tête initialement:
try:
    import uwsgi
except ImportError:
    uwsgi_db_statement_timeout = None
else:
    uwsgi_db_statement_timeout = os.environ.get("UWSGI_DB_STATEMENT_TIMEOUT")
db_statement_timeout = int(
    uwsgi_db_statement_timeout if uwsgi_db_statement_timeout is not None else os.getenv("DB_STATEMENT_TIMEOUT", 300_000)
)

(et donc ne pas avoir de valeur par défaut pour UWSGI_DB_STATEMENT_TIMEOUT )

(le code est simplifié mais il faut évidemment imaginer avoir le code équivalent pour le lock_timeout)

@francoisfreitag francoisfreitag Jun 23, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En voyant les deux, je préfère la solution 1., pour permettre de désactiver le statement timeout pour uWSGI.
Autrement, faudrait définir UWSGI_DB_STATEMENT_TIMEOUT=0 et DB_STATEMENT_TIMEOUT=0.
Avoir deux settings séparés rend la configuration plus claire.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dans la solution 2 tu n'as que UWSGI_DB_STATEMENT_TIMEOUT=0 à définir pour la désactivation vu que ça teste is not None et pas si c'est falsy.

J'ai proposé 1 car c'était la logique que j'avais comprise et donc je me suis dit qu'on voulais 2 manettes complètement distincte et pas une manette spécialisation de la précédente (la métaphore claqué au sol 😁), qui pour le coup est ce qui me semble plus naturelle et logique d'un point de vue configuration et administration.
Bon après je dit ça mais à la fin les deux seront configurés dans le même fichier car je doute qu'on veuille avoir la même valeur et donc ne garder que DB_STATEMENT_TIMEOUT, au final le fonctionnement sera celui de la solution 1...

Et je renommerais bien UWSGI_DB_STATEMENT_TIMEOUT en WWW_DB_STATEMENT_TIMEOUT, comme ça l'intuition est claire et agnostique ;).

@xavfernandez
xavfernandez force-pushed the xfernandez/statement_timeout branch from 3a50f00 to 8e82870 Compare June 23, 2025 15:13
Comment thread config/settings/base.py Outdated
@xavfernandez
xavfernandez force-pushed the xfernandez/statement_timeout branch from 8e82870 to 58a67a2 Compare June 24, 2025 06:46
@xavfernandez

xavfernandez commented Jun 24, 2025

Copy link
Copy Markdown
Contributor Author

@rsebille @francoisfreitag Dernière ( 🤞 ) version ?

@xavfernandez
xavfernandez added this pull request to the merge queue Jun 24, 2025
Merged via the queue into master with commit ef0fc4c Jun 24, 2025
@xavfernandez
xavfernandez deleted the xfernandez/statement_timeout branch June 24, 2025 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants