INSERT ... VALUES n’est pas le seul à pouvoir passer par la file d’attente d’insertion asynchrone : INSERT ... SELECT le peut aussi. Cette page décrit dans quels cas cela se produit, comment la partie écriture est comptabilisée et ce qui se passe lorsque la requête est annulée ou dépasse le délai imparti.
Éligibilité et routage
Une requêteINSERT ... SELECT passe par la file d’attente asynchrone uniquement si toutes les conditions suivantes sont réunies :
async_insert = 1.async_insert_select_as_async_insert = 1(valeur par défaut). C’est le paramètre qui détermine ce comportement : s’il est désactivé,INSERT ... SELECTreste synchrone, quelle que soit la valeur deasync_insert. Sicompatibilityest défini sur une version antérieure à 26.10, la valeur par défaut de ce paramètre passe à0; définir explicitementasync_insert_select_as_async_insert = 1réactive le traitement asynchrone.- L’intégralité du résultat du
SELECTtient dans un seul bloc dont la taille ne dépasse pasasync_insert_max_data_size. - La destination est une table de la famille
MergeTree(si elle est désignée par un alias, la vérification porte sur la table à laquelle cet alias renvoie).
- Le résultat occupe plusieurs blocs, dépasse
async_insert_max_data_sizeou est vide. - Un quorum non parallèle est utilisé :
insert_quorumest défini etinsert_quorum_parallel = 0. - Des vues matérialisées dépendent de la table de destination.
- La destination est une vue matérialisée. La vérification
MergeTreeporte sur la vue, et non sur sa table cible ; elle n’est donc pas éligible. - La destination est une fonction de table ou une table utilisant un moteur distant tel que
Distributed. - Le
SELECTlit la table dans laquelle l’insertion est effectuée (INSERT INTO t SELECT ... FROM t). insert_null_as_defaultdoit remplacer une valeurNULLprovenant d’une colonneNullableduSELECTpar une valeur par défaut dans une colonne cible nonNullable.- L’insertion est interne (rafraîchissement d’une vue matérialisée actualisable,
POPULATEouCREATE TABLE ... AS SELECT). Ces insertions sont toujours synchrones et ne tiennent compte ni deasync_insert_select_as_async_insertni deasync_insert.
implicit_transaction = 1), le traitement asynchrone n’est pas pris en charge et, par défaut, ClickHouse lève NOT_IMPLEMENTED au lieu de basculer vers le traitement synchrone. La requête ne bascule vers une insertion synchrone que si throw_on_unsupported_query_inside_transaction = 0.
Les décisions de routage prises en amont du traitement asynchrone prévalent : la partie SELECT peut donc, à elle seule, exclure ce traitement. Avec parallel_distributed_insert_select (activé par défaut), une requête INSERT ... SELECT qui lit une source sur un cluster telle que s3Cluster pour insérer les données dans une table répliquée suit le chemin de l’insertion distribuée et n’atteint jamais la file d’attente asynchrone. Il en va de même pour les répliques parallèles.
Mode de retour et attente
wait_for_async_insert fonctionne comme pour toute insertion asynchrone : 1 (valeur par défaut) attend le vidage ; 0 rend la main dès que le bloc est placé en file d’attente.
Pour aider un SELECT à produire le bloc unique qu’exige le traitement asynchrone, optimize_trivial_insert_select limite un INSERT INTO dest SELECT ... FROM source trivial entre deux tables distinctes à max_insert_threads threads de lecture. Avec max_insert_threads = 1, le SELECT produit généralement un seul bloc.
Comptabilisation des écritures et observability
Les écritures sont attribuées à la requête de vidage, et non à l’instructionINSERT ... SELECT initiale :
- La requête de vidage comptabilise les
ProfileEventsliés aux écritures (InsertedRows,InsertedBytes) et dispose de sa propre ligne danssystem.query_logpour ces écritures. La ligne de l’instructionINSERT ... SELECTinitiale indique0pour cesProfileEvents. Pour connaître les volumes écrits par la requête de vidage, consultez les lignes qui lui correspondent danssystem.query_logetsystem.asynchronous_insert_log. - Avec
wait_for_async_insert = 1(valeur par défaut), les valeurswritten_rowsetwritten_bytesde la requête (ainsi que l’en-tête HTTPX-ClickHouse-Summary) indiquent le nombre de lignes et d’octets acceptés depuis l’instructionSELECT, et non le total écrit par la requête de vidage. Si le vidage échoue, une erreur est néanmoins renvoyée au client. - Avec
wait_for_async_insert = 0, ces valeurs ne sont pas indiquées, car la requête se termine avant que le résultat du vidage soit disponible. - Le quota
WRITTEN_BYTESest décompté une seule fois, lors du vidage, et ce décompte n’est pas annulé si le vidage échoue.
Annulation et dépassements de délai
Le bloc est placé dans la file d’attente avant que ne commence l’attente du vidage : annuler la requête interrompt donc l’attente du client, mais n’empêche pas la file d’attente d’effectuer le vidage.KILL QUERY, ou l’atteinte demax_execution_timependant l’attente, renvoie une erreur au client, mais les lignes peuvent malgré tout être écrites. Sur ce chemin, une requête annulée ou ayant dépassé son délai ne prouve pas que rien n’a été écrit.- Avec
timeout_overflow_mode = 'break', il n’y a ni erreur ni retour anticipé : la requête attend le vidage au-delà demax_execution_time, jusqu’àwait_for_async_insert_timeout. Interrompre l’attente prématurément reviendrait à signaler un succès avant que l’écriture ne soit confirmée.
Quand les décisions sont figées
L’éligibilité au traitement asynchrone est déterminée une seule fois, au démarrage de la requête, avant la mise en file d’attente du bloc, et une vérification préalable des droits d’accès rejette d’emblée toute insertion non autorisée. Le fait qu’unINSERT ... SELECT emprunte ou non le traitement asynchrone dépend de la forme du bloc de résultat, et pas uniquement de async_insert.
La résolution de la table de destination et la vérification des autorisations s’effectuent ensuite au moment du vidage, comme pour toute insertion asynchrone : si une vue matérialisée est attachée à la table de destination, ou si un privilège est révoqué, entre la mise en file d’attente et le vidage, ce changement s’applique lors du vidage.