Vous pouvez joindre chaque élément de données en continu à une source de données externe de type table de dimension. Realtime Compute for Apache Flink utilise cette fonctionnalité pour enrichir les flux avec des données de dimension.
Vue d'ensemble de la jointure de table de dimension
Une jointure de table de dimension (également appelée Lookup Join) interroge une table de dimension externe au moment du traitement pour chaque élément de données du flux, et enrichit le flux principal avec les colonnes de dimension. Elle est couramment utilisée pour ajouter des informations de dictionnaire ou de dimension aux flux d'événements.
Points clés d'une jointure de table de dimension :
Syntaxe : Utilisez
FOR SYSTEM_TIME AS OF PROCTIME()pour indiquer que chaque ligne du flux principal interroge les données actuelles de la table de dimension au moment du traitement, et non un instantané.Stratégie de cache : Mettez en cache les données de dimension au niveau du connecteur afin de réduire la pression exercée sur le système externe.
Comportement de recherche : Utilisez un indice LOOKUP pour configurer le mode synchrone/asynchrone, la capacité du tampon, la stratégie de nouvelle tentative, etc.
Stratégie de jointure physique : Utilisez des indices tels que SHUFFLE_HASH pour contrôler le comportement de brassage et atténuer le déséquilibre des données.
Configuration au niveau du job : Utilisez la commande SET pour ajuster les paramètres globaux de la famille
table.exec.*.
Syntaxe de la jointure de table de dimension
SELECT column-names
FROM table1 [AS <alias1>]
[LEFT] JOIN table2 FOR SYSTEM_TIME AS OF PROCTIME() [AS <alias2>]
ON table1.column-name1 = table2.key-name1;
FOR SYSTEM_TIME AS OF PROCTIME()est obligatoire. Cette clause indique au moteur de joindre chaque ligne du flux principal à l'instantané de la table de dimension tel qu'il existe au moment actuel du traitement.La clause ON doit contenir une condition d'égalité sur une colonne que la table de dimension peut rechercher de manière aléatoire.
Dans la clause ON, vous pouvez appliquer des fonctions de conversion de type telles que CAST aux colonnes de la table source. Lorsque les types des colonnes source et de dimension ne correspondent pas, convertissez la colonne source pour qu'elle s'aligne sur le type de la colonne de dimension.
Limites et considérations
Une jointure de table de dimension prend uniquement en charge la jointure avec l'instantané actuel de la table de dimension.
Les tables de dimension prennent en charge uniquement INNER JOIN et LEFT JOIN. RIGHT JOIN et FULL JOIN ne sont pas pris en charge.
Pour une jointure un-à-un, assurez-vous que la condition de jointure contient un prédicat d'égalité sur une colonne unique dans la table de dimension.
Pour chaque élément de données en continu, la jointure ne voit que la dernière version de la table de dimension au moment du traitement. Une fois la jointure effectuée, les modifications ultérieures (insertion, mise à jour ou suppression) apportées aux données de dimension ne sont pas propagées aux lignes déjà jointes. Pour connaître le comportement spécifique à chaque connecteur, consultez la documentation correspondante du connecteur.
Stratégies de cache des tables de dimension
La plupart des connecteurs prennent en charge des stratégies de cache pour les jointures de tables de dimension. Le support varie légèrement selon les connecteurs ; reportez-vous à la documentation du connecteur correspondant pour plus de détails. Les stratégies de cache courantes sont les suivantes :
|
Stratégie |
Comportement |
|
None (par défaut) |
Aucun cache. |
|
LRU |
Met en cache un sous-ensemble de la table de dimension. Chaque ligne du flux source consulte d'abord le cache ; en cas d'échec, le moteur interroge la table de dimension physique. |
|
ALL |
Met en cache l'intégralité de la table de dimension. Avant le démarrage du job, le moteur charge toutes les lignes dans le cache ; chaque recherche ultérieure est servie depuis le cache, et un échec de cache signifie que la clé n'existe pas. Des options spécifiques au connecteur contrôlent les rechargements périodiques ou planifiés de la table de dimension. Cette stratégie convient aux petites tables de dimension qui ne nécessitent pas de mises à jour fréquentes et exigent un débit maximal. |
Choisissez entre la fraîcheur des données et les performances en fonction de vos exigences métier. Si la fraîcheur des données est critique, vous pouvez désactiver la mise en cache et lire directement depuis la table de dimension.
Lorsque la mise en cache est activée, combinez LRU avec un TTL (Time To Live) pour maintenir les données mises en cache raisonnablement fraîches. Le TTL peut être défini sur une valeur courte, par exemple de quelques secondes à quelques dizaines de secondes, afin que le cache soit régulièrement actualisé depuis la source.
Lorsque vous utilisez la stratégie ALL, surveillez attentivement la mémoire du nœud pour éviter les erreurs OOM (Out Of Memory).
Avec la stratégie ALL, le moteur charge les données de dimension de manière asynchrone ; vous devez donc augmenter la mémoire du nœud de jointure de dimension. En règle générale, augmentez-la du double de la taille des données de la table de dimension distante.
Optimisation d'une jointure de table de dimension
Catégories de paramètres d'optimisation et méthode de transmission
L'optimisation d'une jointure de table de dimension implique trois catégories de paramètres. Chaque catégorie possède sa propre méthode de transmission, et l'utilisation du mécanisme incorrect entraîne l'ignorance silencieuse de l'indice.
|
Catégorie |
Paramètres typiques |
Méthode de transmission |
Portée |
|
Options WITH du connecteur |
|
Définissez-les dans la clause |
Table unique |
|
Options de l'indice LOOKUP |
|
|
Opération de jointure unique |
|
TableConfig au niveau du job Flink |
Configurations globales dont les clés commencent par |
Exécutez |
Job entier |
Un indice
OPTIONS()remplace uniquement les options que le connecteur accepte dans la clause WITH. Il n'accepte pas les clés TableConfigtable.exec.*; les clés invalides sont ignorées silencieusement.Un indice
LOOKUP()reconnaît uniquement les clés fixes répertoriées dans le tableau ci-dessus. Il n'accepte pas les clés de configuration Flink complètes. Par exemple, pour ajuster le tampon de recherche asynchrone, vous devez écrire'capacity'='100'; écrire'table.exec.async-lookup.buffer-capacity'='100'n'a aucun effet.Pour modifier un paramètre global qui n'est exposé par aucun indice, utilisez
SET 'xxx' = 'yyy';.
Remplacer les options du connecteur avec l'indice OPTIONS
Un indice OPTIONS() vous permet de remplacer les options WITH d'une table directement dans une requête, sans modifier l'instruction CREATE TABLE. Il est couramment utilisé pour optimiser le comportement du cache.
SELECT t.id, t.name, w.phoneNumber
FROM kafka_input AS t
LEFT JOIN phoneNumber /*+ OPTIONS(
'lookup.cache' = 'PARTIAL',
'lookup.partial-cache.max-rows' = '1000'
) */ FOR SYSTEM_TIME AS OF PROCTIME() AS w
ON t.name = w.name;
Chaque clé à l'intérieur de OPTIONS doit être une option WITH effectivement prise en charge par le connecteur.
Les options lookup.cache et lookup.partial-cache.max-rows illustrées ci-dessus appartiennent à l'interface générique LookupCache de Flink (FLIP-221) et s'appliquent uniquement aux connecteurs plus récents qui implémentent cette interface (tels que Fluss et JDBC). Les stratégies None/LRU/ALL décrites dans la section « Stratégies de cache des tables de dimension » correspondent aux connecteurs hérités, qui exposent leur comportement de cache via différentes options WITH. Reportez-vous à la documentation du connecteur cible pour confirmer les options et stratégies de cache qu'il prend en charge, et évitez d'utiliser des paramètres non reconnus par le connecteur.
Configurer le comportement de recherche avec l'indice LOOKUP
L'indice LOOKUP se comporte de manière cohérente avec la version communautaire d'Apache Flink. Il configure le mode synchrone/asynchrone, les nouvelles tentatives et le comportement de brassage pour une opération de jointure unique. Pour plus de détails, consultez la page Apache Flink Lookup Hint.
L'indice LOOKUP est disponible uniquement dans VVR 8.0 et versions ultérieures.
L'option
'shuffle' = 'true'est disponible uniquement dans VVR 8.0.8 et versions ultérieures.Les alias sont pris en charge dans VVR 8.0 et versions ultérieures. Lorsque la table de dimension est référencée par un alias, l'indice doit utiliser cet alias.
Options prises en charge
|
Option |
Signification |
Valeur |
|
|
Nom ou alias de la table de dimension auquel l'indice s'applique. |
Nom de table ou chaîne d'alias. |
|
|
Indique s'il faut activer la recherche asynchrone. |
|
|
|
Ordre de sortie des recherches asynchrones. |
|
|
|
Capacité de la file d'attente du tampon pour les recherches asynchrones. |
Entier. |
|
|
Délai d'expiration pour une recherche asynchrone. |
Durée (par exemple, |
|
|
Condition qui déclenche une nouvelle tentative. |
Seule l'option |
|
|
Stratégie de nouvelle tentative. |
Seule l'option |
|
|
Intervalle fixe entre les nouvelles tentatives. |
Durée (par exemple, |
|
|
Nombre maximal de tentatives de nouvelle tentative. |
Entier. |
|
|
Indique s'il faut effectuer un brassage avant la jointure de la table de dimension. |
|
Comportement de l'option shuffle
L'option shuffle affecte la stratégie de brassage de la jointure de la table de dimension. Le comportement effectif dans chaque scénario est le suivant.
|
Scénario |
Stratégie de jointure |
|
|
La stratégie de brassage par défaut du moteur est utilisée. |
|
|
La stratégie de brassage par défaut du moteur est utilisée. |
|
|
SHUFFLE_HASH est utilisé par défaut. Pour plus de détails, consultez la section SHUFFLE_HASH ci-dessous. |
|
|
La stratégie de brassage personnalisée fournie par le connecteur est utilisée. |
Actuellement, seul le connecteur Streaming Lakehouse Paimon fournit une stratégie de brassage personnalisée. Lorsque les colonnes de jointure couvrent toutes les colonnes de bucket, le brassage est effectué en fonction du bucket.
Exemples de code
-- Apply the shuffle strategy only to dimension table dim1.
SELECT /*+ LOOKUP('table'='dim1', 'shuffle' = 'true') */ ...
FROM src AS T
LEFT JOIN dim1 FOR SYSTEM_TIME AS OF PROCTIME() ON T.a = dim1.a
LEFT JOIN dim2 FOR SYSTEM_TIME AS OF PROCTIME() ON T.b = dim2.b;
-- Apply the shuffle strategy to both dim1 and dim2.
SELECT /*+ LOOKUP('table'='dim1', 'shuffle' = 'true'), LOOKUP('table'='dim2', 'shuffle' = 'true') */ ...
FROM src AS T
LEFT JOIN dim1 FOR SYSTEM_TIME AS OF PROCTIME() ON T.a = dim1.a
LEFT JOIN dim2 FOR SYSTEM_TIME AS OF PROCTIME() ON T.b = dim2.b;
-- When dim1 is referenced by the alias D1, the hint must use the alias.
SELECT /*+ LOOKUP('table'='D1', 'shuffle' = 'true') */ ...
FROM src AS T
LEFT JOIN dim1 FOR SYSTEM_TIME AS OF PROCTIME() AS D1 ON T.a = D1.a
LEFT JOIN dim2 FOR SYSTEM_TIME AS OF PROCTIME() AS D2 ON T.b = D2.b;
-- Apply the shuffle strategy to both dim1 and dim2 using their aliases.
SELECT /*+ LOOKUP('table'='D1', 'shuffle' = 'true'), LOOKUP('table'='D2', 'shuffle' = 'true') */ ...
FROM src AS T
LEFT JOIN dim1 FOR SYSTEM_TIME AS OF PROCTIME() AS D1 ON T.a = D1.a
LEFT JOIN dim2 FOR SYSTEM_TIME AS OF PROCTIME() AS D2 ON T.b = D2.b;
Définir TableConfig au niveau du job avec SET
Les clés TableConfig table.exec.* et similaires sont des paramètres globaux au niveau du job. Elles doivent être transmises via SET ; ni l'indice LOOKUP ni l'indice OPTIONS ne les acceptent.
Clés TableConfig courantes pour la jointure de table de dimension :
|
Clé |
Signification |
Par défaut |
|
|
Capacité de la file d'attente du tampon pour les recherches asynchrones (valeur par défaut au niveau du job). |
100 |
|
|
Délai d'expiration pour les recherches asynchrones (valeur par défaut au niveau du job). |
3 min |
|
|
Ordre de sortie des recherches asynchrones (valeur par défaut au niveau du job). |
|
-- Set job-level defaults at the beginning of the job.
SET 'table.exec.async-lookup.buffer-capacity' = '3072';
SET 'table.exec.async-lookup.timeout' = '180s';
INSERT INTO sink_table
SELECT ...
FROM src_table
LEFT JOIN dim_table FOR SYSTEM_TIME AS OF PROCTIME() AS d
ON src_table.key = d.key;
Lorsque SET et un indice LOOKUP définissent le même paramètre, l'indice LOOKUP sur la jointure individuelle est prioritaire.
Contrôler le brassage avec les indices de stratégie de jointure
Les indices de stratégie de jointure contrôlent le comportement de brassage pour les jointures de tables de dimension. Les indices disponibles sont SHUFFLE_HASH, REPLICATED_SHUFFLE_HASH et SKEW. Le tableau ci-dessous indique quelle stratégie de jointure convient à chaque stratégie de cache.
|
Stratégie de cache |
SHUFFLE_HASH |
REPLICATED_SHUFFLE_HASH (équivalent à SKEW) |
|
None |
Cette stratégie n'est pas recommandée ; le flux principal engendre une surcharge réseau supplémentaire. |
Cette stratégie n'est pas recommandée ; le flux principal engendre une surcharge réseau supplémentaire. |
|
LRU |
Envisagez cette stratégie lorsque les E/S de recherche de dimension constituent le goulot d'étranglement. Lorsque le flux principal présente une localité temporelle sur la clé de jointure, cela améliore le taux de succès du cache, réduit les requêtes d'E/S et augmente le débit global. Important
Le flux principal engendre une surcharge réseau supplémentaire. Si le flux principal est déséquilibré sur la clé de jointure et que vous rencontrez un goulot d'étranglement de performance, envisagez plutôt REPLICATED_SHUFFLE_HASH. |
Envisagez cette stratégie lorsque les E/S de recherche de dimension constituent le goulot d'étranglement et que le flux principal est déséquilibré sur la clé de jointure. Lorsque le flux principal présente une localité temporelle sur la clé de jointure, cela améliore le taux de succès du cache, réduit les requêtes d'E/S et augmente le débit global. |
|
ALL |
Recommandé lorsque la mémoire de la table de dimension devient le goulot d'étranglement. L'utilisation de la mémoire peut descendre à 1 / parallélisme. Important
Le flux principal engendre une surcharge réseau supplémentaire. Si le flux principal est déséquilibré sur la clé de jointure et que vous rencontrez un goulot d'étranglement de performance, envisagez plutôt REPLICATED_SHUFFLE_HASH. |
Recommandé lorsque la mémoire de la table de dimension devient le goulot d'étranglement et que le flux principal est déséquilibré sur la clé de jointure. L'utilisation de la mémoire descend à nombre de buckets / parallélisme. |
L'option
shufflede l'indice LOOKUP couvre déjà ce que fait SHUFFLE_HASH. Lorsque les deux sont présents, l'optionshufflede l'indice LOOKUP est prioritaire.L'option
shufflede l'indice LOOKUP ne gère pas encore le déséquilibre des données. Lorsqu'elle est combinée avec REPLICATED_SHUFFLE_HASH ou SKEW, la stratégie spécifiée par REPLICATED_SHUFFLE_HASH ou SKEW est prioritaire.
SHUFFLE_HASH
Effet : L'utilisation d'une stratégie de hachage avec brassage dans une jointure de table de dimension brasse le flux principal par clé de jointure avant la jointure. Combinée à la stratégie de cache LRU, elle augmente le taux de succès du cache et réduit les requêtes d'E/S ; combinée à la stratégie de cache ALL, elle réduit l'utilisation de la mémoire. Un seul indice SHUFFLE_HASH peut spécifier plusieurs tables de dimension.
Limites : SHUFFLE_HASH réduit l'utilisation de la mémoire, mais les données en amont doivent être brassées par clé de jointure, ce qui introduit une surcharge réseau supplémentaire. Évitez-le dans les deux cas suivants :
Le flux principal est fortement déséquilibré sur la clé de jointure. L'utilisation de SHUFFLE_HASH ferait du nœud de jointure un goulot d'étranglement, provoquant une contre-pression sévère pour les jobs en continu ou des queues longues pour les jobs par lots. Utilisez plutôt REPLICATED_SHUFFLE_HASH.
La table de dimension est petite et la stratégie de cache ALL ne présente aucun goulot d'étranglement mémoire. Dans ce cas, la mémoire économisée par SHUFFLE_HASH ne justifie pas la surcharge réseau supplémentaire.
Exemple de code :
-- Enable SHUFFLE_HASH for dim1 only.
SELECT /*+ SHUFFLE_HASH(dim1) */ ...
-- Enable SHUFFLE_HASH for both dim1 and dim2.
SELECT /*+ SHUFFLE_HASH(dim1, dim2) */ ...
-- When dim1 is referenced by the alias D1, the hint must use the alias.
SELECT /*+ SHUFFLE_HASH(D1) */ ...
REPLICATED_SHUFFLE_HASH
Effet : REPLICATED_SHUFFLE_HASH se comporte essentiellement de la même manière que SHUFFLE_HASH, sauf qu'il distribue aléatoirement les lignes du flux principal ayant la même clé sur N instances parallèles. Cela permet de résoudre le goulot d'étranglement de performance causé par le déséquilibre des données. Un seul indice REPLICATED_SHUFFLE_HASH peut spécifier plusieurs tables de dimension.
Limites :
Configurez le nombre de buckets pour les données déséquilibrées avec
table.exec.skew-join.replicate-num(par défaut : 16). Sa valeur ne peut pas dépasser le parallélisme du nœud de jointure de dimension. Pour plus de détails, consultez la section Optimisation SQL au niveau du job.Les flux de mise à jour ne sont pas pris en charge. L'utilisation de cet indice lorsque le flux principal est un flux de mise à jour génère une erreur.
Exemple de code :
SELECT /*+ REPLICATED_SHUFFLE_HASH(dim1) */ ...
SKEW
Effet : Lorsque la table spécifiée est connue pour être déséquilibrée, l'optimiseur applique la stratégie de hachage avec brassage répliqué. SKEW est un sucre syntaxique ; il est implémenté en interne avec un hachage de brassage répliqué.
Limites :
Chaque indice SKEW ne peut spécifier qu'une seule table.
Le nom de la table doit faire référence à la table principale présentant le déséquilibre, et non à la table de dimension.
Les flux de mise à jour ne sont pas pris en charge. L'utilisation de cet indice lorsque le flux principal est un flux de mise à jour génère une erreur.
Exemple de code :
SELECT /*+ SKEW(src) */ ...
Exemples
Exemple 1 : Une jointure de table de dimension basique
La forme la plus basique : enrichir un flux Kafka avec une table de dimension MySQL. Aucun indice d'optimisation n'est utilisé.
CREATE TEMPORARY TABLE kafka_input (
id BIGINT,
name VARCHAR,
age BIGINT
) WITH (
'connector' = 'kafka',
'topic' = '<yourTopic>',
'properties.bootstrap.servers' = '<yourKafkaBrokers>',
'properties.group.id' = '<yourKafkaConsumerGroupId>',
'format' = 'csv'
);
CREATE TEMPORARY TABLE phoneNumber (
name VARCHAR,
phoneNumber BIGINT,
PRIMARY KEY (name) NOT ENFORCED
) WITH (
'connector' = 'mysql',
'hostname' = '<yourHostname>',
'port' = '3306',
'username' = '<yourUsername>',
'password' = '<yourPassword>',
'database-name' = '<yourDatabaseName>',
'table-name' = '<yourTableName>'
);
CREATE TEMPORARY TABLE result_infor (
id BIGINT,
phoneNumber BIGINT,
name VARCHAR
) WITH (
'connector' = 'blackhole'
);
INSERT INTO result_infor
SELECT
t.id,
w.phoneNumber,
t.name
FROM kafka_input AS t
JOIN phoneNumber FOR SYSTEM_TIME AS OF PROCTIME() AS w
ON t.name = w.name;
Exemple 2 : Activer le cache avec l'indice OPTIONS et configurer la recherche asynchrone avec l'indice LOOKUP
Scénario d'optimisation : le QPS de la table de dimension est insuffisant. Vous souhaitez activer le cache partiel sur la table de dimension et activer la recherche asynchrone avec une file d'attente de tampon plus grande.
INSERT INTO user_behavior_wide
SELECT /*+ LOOKUP('table' = 't2', 'async' = 'true', 'capacity' = '3072') */
t1.member_id AS member_id,
t2.tag AS tag
FROM user_behavior_datagen AS t1
LEFT JOIN fluss.fluss.user_active_info /*+ OPTIONS(
'lookup.cache' = 'PARTIAL',
'lookup.partial-cache.max-rows' = '1000'
) */
FOR SYSTEM_TIME AS OF PROCTIME() AS t2
ON t1.member_id = t2.member_id;
Options du connecteur (
lookup.cache,lookup.partial-cache.max-rows) sont transmises via un indiceOPTIONS()placé juste après la référence de la table de dimension ; cela remplace les options WITH correspondantes au moment de la requête.Comportement de recherche (
async,capacity) est configuré via un indiceLOOKUP()placé après SELECT. OPTIONS et LOOKUP sont deux types d'indices différents et doivent être écrits séparément ; les combiner désactive silencieusement l'indice.
Exemple 3 : Optimiser le tampon de recherche asynchrone au niveau du job avec SET
Scénario d'optimisation : plusieurs jointures de tables de dimension dans le même job nécessitent un tampon de recherche asynchrone plus grand. Vous souhaitez que la modification s'applique globalement et activer l'asynchrone via l'indice LOOKUP.
SET 'table.exec.async-lookup.buffer-capacity' = '3072';
SET 'table.exec.async-lookup.timeout' = '180s';
INSERT INTO user_behavior_wide
SELECT /*+ LOOKUP('table' = 't2', 'async' = 'true') */
t1.member_id,
t2.tag
FROM user_behavior_datagen AS t1
LEFT JOIN fluss.fluss.user_active_info
FOR SYSTEM_TIME AS OF PROCTIME() AS t2
ON t1.member_id = t2.member_id;
table.exec.async-lookup.buffer-capacityet les paramètres similaires sont des clés TableConfig au niveau du job et ne peuvent être définis qu'avec SET. Les placer à l'intérieur de OPTIONS ou LOOKUP n'a aucun effet.Lorsque l'indice LOOKUP ne spécifie pas
capacity, la valeur globale issue de SET est utilisée. Si les deux sont spécifiés, l'indice LOOKUP est prioritaire sur cette jointure individuelle.