Les écritures de données à haute fréquence, telles que les pipelines de journalisation ou les flux d'événements IoT, posent deux problèmes lorsque chaque écriture constitue une importation distincte : chacune entraîne sa propre surcharge transactionnelle (analyse SQL, génération de plan) et crée une nouvelle version de table, ce qui accroît la pression de compaction en arrière-plan. La fonctionnalité group commit résout ces deux problèmes en fusionnant côté serveur plusieurs instructions INSERT INTO VALUES, Stream Load ou HTTP Stream en une seule transaction interne. Cela réduit la surcharge d'E/S et améliore le débit d'écriture sans nécessiter de logique de traitement par lots côté client.
Fonctionnement
Le group commit n'est pas une méthode d'importation distincte. Il intercepte les requêtes INSERT INTO VALUES, Stream Load et HTTP Stream éligibles pour les regrouper en un seul commit interne. Le commit automatique se déclenche dès que l'un des seuils suivants est atteint :
L'intervalle de commit expire (par défaut : 10 secondes).
La taille cumulée des données atteint la limite (par défaut : 64 Mo).
Trois modes régissent ce comportement :
| Mode | Comportement | Cas d'usage |
|---|---|---|
off_mode |
Group commit désactivé. Les opérations INSERT INTO VALUES, Stream Load et HTTP Stream s'exécutent normalement. |
Le group commit n'est pas nécessaire. |
sync_mode |
Regroupe plusieurs importations en une seule transaction selon la charge et la propriété de table group_commit_interval. La réponse est renvoyée après le commit de la transaction. Les données sont visibles immédiatement. |
Écritures à forte concurrence nécessitant une visibilité immédiate des données. |
async_mode |
Écrit d'abord les données dans les journaux WAL (write-ahead logging), puis répond immédiatement. Le commit s'effectue de manière asynchrone selon la charge et group_commit_interval. Les données deviennent visibles après le commit. Bascule automatiquement en sync_mode lors de la détection de volumes de données importants. |
Écritures à haute fréquence où la faible latence d'écriture est prioritaire. |
Choix entre le mode synchrone et asynchrone :
Privilégiez
sync_modepour les scénarios à forte concurrence où les données doivent être visibles immédiatement après l'importation. Ce mode bloque l'exécution jusqu'au commit de la transaction ; le résultat retourné confirme ainsi que les données sont persistées et interrogeables sans délai.Optez pour
async_modelorsque la latence d'écriture constitue la priorité absolue. Le serveur accuse réception de l'importation dès l'écriture des données dans le WAL, sans attendre la fin du commit interne. En cas d'échec de ce commit, les journaux WAL permettent de récupérer les données. Celles-ci ne sont pas visibles immédiatement après le retour de l'importation.
Création de la table d'exemple
Les exemples de cette rubrique utilisent la table suivante :
CREATE TABLE `dt` (
`id` int(11) NOT NULL,
`name` varchar(50) NULL,
`score` int(11) NULL
) ENGINE=OLAP
DUPLICATE KEY(`id`)
DISTRIBUTED BY HASH(`id`) BUCKETS 1;
Importation de données via JDBC
ApsaraDB for SelectDB prend en charge la fonctionnalité de requête préparée de MySQL via Java Database Connectivity (JDBC). L'utilisation de requêtes préparées permet de mettre en cache les instructions SQL et leurs plans d'importation dans la mémoire au niveau de la session, réduisant ainsi la surcharge CPU lors d'insertions répétées.
-
Ajoutez la dépendance du connecteur MySQL à votre projet :
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> -
Construisez l'URL JDBC avec l'activation des requêtes préparées côté serveur :
jdbc:mysql://selectdb-cn-****.selectdbfe.rds.aliyuncs.com:9030/db?useServerPrepStmts=true -
Définissez la variable de session
group_commit. Utilisez l'une des approches suivantes :Ajoutez-la à l'URL JDBC : ``
jdbc:mysql://selectdb-cn-****.selectdbfe.rds.aliyuncs.com:9030/db?useServerPrepStmts=true&sessionVariables=group_commit=async_mode``Exécutez une instruction SQL lors de la connexion : ``
java try (Statement statement = conn.createStatement()) { statement.execute("SET group_commit = async_mode;"); }``
-
Utilisez une requête préparée pour insérer des lignes :
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver"; private static final String URL_PATTERN = "jdbc:mysql://%s:%d/%s?useServerPrepStmts=true"; private static final String HOST = "selectdb-cn-****.selectdbfe.rds.aliyuncs.com"; private static final int PORT = 9030; private static final String DB = "db"; private static final String TBL = "dt"; private static final String USER = "admin"; private static final String PASSWD = "***"; private static final int INSERT_BATCH_SIZE = 10; public static void main(String[] args) { groupCommitInsert(); //groupCommitInsertBatch } private static void groupCommitInsert() throws Exception { Class.forName(JDBC_DRIVER); try (Connection conn = DriverManager.getConnection(String.format(URL_PATTERN, HOST, PORT, DB), USER, PASSWD)) { // set session variable 'group_commit' try (Statement statement = conn.createStatement()) { statement.execute("SET group_commit = async_mode;"); } String query = "INSERT INTO " + TBL + " VALUES(?, ?, ?)"; try (PreparedStatement stmt = conn.prepareStatement(query)) { for (int i = 0; i < INSERT_BATCH_SIZE; i++) { stmt.setInt(1, i); stmt.setString(2, "name" + i); stmt.setInt(3, i + 10); int result = stmt.executeUpdate(); System.out.println("rows: " + result); } } } catch (Exception e) { e.printStackTrace(); } } private static void groupCommitInsertBatch() throws Exception { Class.forName(JDBC_DRIVER); // add rewriteBatchedStatements=true and cachePrepStmts=true in JDBC url // set session variables by sessionVariables=group_commit=async_mode in JDBC url try (Connection conn = DriverManager.getConnection( String.format(URL_PATTERN + "&rewriteBatchedStatements=true&cachePrepStmts=true&sessionVariables=group_commit=async_mode", HOST, PORT, DB), USER, PASSWD)) { String query = "INSERT INTO " + TBL + " VALUES(?, ?, ?)"; try (PreparedStatement stmt = conn.prepareStatement(query)) { for (int j = 0; j < 5; j++) { // 10 rows per insert for (int i = 0; i < INSERT_BATCH_SIZE; i++) { stmt.setInt(1, i); stmt.setString(2, "name" + i); stmt.setInt(3, i + 10); stmt.addBatch(); } int[] result = stmt.executeBatch(); } } } catch (Exception e) { e.printStackTrace(); } }
Importation de données avec INSERT INTO
Activez le group commit en définissant la variable de session group_commit avant d'exécuter des instructions INSERT INTO.
Mode asynchrone — les données sont regroupées et validées en arrière-plan :
-- Enable group commit in async mode. The default value is off_mode.
mysql> SET group_commit = async_mode;
-- The returned label starts with "group_commit", confirming group commit is active.
mysql> INSERT INTO dt VALUES(1, 'Bob', 90), (2, 'Alice', 99);
Query OK, 2 rows affected (0.05 sec)
{'label':'group_commit_a145ce07f1c972fc-bd2c54597052a9ad', 'status':'PREPARE', 'txnId':'181508'}
-- Consecutive inserts sharing the same label and txnId are batched into one import job.
mysql> INSERT INTO dt(id, name) VALUES(3, 'John');
Query OK, 1 row affected (0.01 sec)
{'label':'group_commit_a145ce07f1c972fc-bd2c54597052a9ad', 'status':'PREPARE', 'txnId':'181508'}
-- Data is not visible immediately after the import returns.
mysql> SELECT * FROM dt;
Empty SET (0.01 sec)
-- After ~10 seconds (controlled by group_commit_interval), data becomes visible.
mysql> SELECT * FROM dt;
+------+-------+-------+
| id | name | score |
+------+-------+-------+
| 1 | Bob | 90 |
| 2 | Alice | 99 |
| 3 | John | NULL |
+------+-------+-------+
3 rows in set (0.02 sec)
Mode synchrone — la réponse n'est renvoyée qu'après le commit de la transaction ; les données sont immédiatement visibles :
-- Enable group commit in sync mode.
mysql> SET group_commit = sync_mode;
-- The commit interval is controlled by group_commit_interval. The call blocks until the transaction commits.
mysql> INSERT INTO dt VALUES(4, 'Bob', 90), (5, 'Alice', 99);
Query OK, 2 rows affected (10.06 sec)
{'label':'group_commit_d84ab96c09b60587_ec455a33cb0e9e87', 'status':'PREPARE', 'txnId':'3007', 'query_id':'fc6b94085d704a94-a69bfc9a202e66e2'}
-- Data is visible immediately.
mysql> SELECT * FROM dt;
+------+-------+-------+
| id | name | score |
+------+-------+-------+
| 1 | Bob | 90 |
| 2 | Alice | 99 |
| 3 | John | NULL |
| 4 | Bob | 90 |
| 5 | Alice | 99 |
+------+-------+-------+
5 rows in set (0.03 sec)
Désactivation du group commit :
mysql> SET group_commit = off_mode;
Importation de données avec Stream Load
Pour les pipelines basés sur des journaux ou HTTP, transmettez l'en-tête group_commit afin d'activer le group commit sur les requêtes Stream Load. Pour plus d'informations sur Stream Load, consultez Stream Load.
-
Créez un fichier nommé
data.csv:6,Amy,60 7,Ross,98 -
Exécutez l'importation avec l'en-tête correspondant au mode souhaité : Mode asynchrone :
# Pass group_commit:async_mode as a request header. curl --location-trusted -u {user}:{passwd} -T data.csv \ -H "group_commit:async_mode" \ -H "column_separator:," \ http://{selectdbHost}:{selectdbHttpPort}/api/db/dt/_stream_loadRéponse attendue :
{ "TxnId": 7009, "Label": "group_commit_c84d2099208436ab_96e33fda01eddba8", "Comment": "", "GroupCommit": true, "Status": "Success", "Message": "OK", "NumberTotalRows": 2, "NumberLoadedRows": 2, "NumberFilteredRows": 0, "NumberUnselectedRows": 0, "LoadBytes": 19, "LoadTimeMs": 35, "StreamLoadPutTimeMs": 5, "ReadDataTimeMs": 0, "WriteDataTimeMs": 26 }Mode synchrone :
# Pass group_commit:sync_mode as a request header. curl --location-trusted -u {user}:{passwd} -T data.csv \ -H "group_commit:sync_mode" \ -H "column_separator:," \ http://{selectdbHost}:{selectdbHttpPort}/api/db/dt/_stream_loadRéponse attendue :
{ "TxnId": 3009, "Label": "group_commit_d941bf17f6efcc80_ccf4afdde9881293", "Comment": "", "GroupCommit": true, "Status": "Success", "Message": "OK", "NumberTotalRows": 2, "NumberLoadedRows": 2, "NumberFilteredRows": 0, "NumberUnselectedRows": 0, "LoadBytes": 19, "LoadTimeMs": 10044, "StreamLoadPutTimeMs": 4, "ReadDataTimeMs": 0, "WriteDataTimeMs": 10038 }La présence de
"GroupCommit": truedans la réponse confirme que le group commit est actif. Le libellé commence toujours pargroup_commit.
Configuration des seuils de commit automatique
Ajustez l'intervalle de commit ou les seuils de taille des données par table à l'aide de ALTER TABLE.
Intervalle de commit
L'intervalle de commit par défaut est de 10 secondes.
-- Change the commit interval to 2 seconds.
ALTER TABLE dt SET ("group_commit_interval_ms" = "2000");
Compromis à considérer :
| Paramètre | Avantages | Inconvénients |
|---|---|---|
| Intervalle court (ex. : 2 secondes) | Latence réduite pour la visibilité des données | Commits plus fréquents, croissance accélérée des versions, pression de compaction en arrière-plan plus élevée |
| Intervalle long (ex. : 30 secondes) | Lots de commits plus importants, surcharge système réduite | Latence accrue pour la visibilité des données |
Définissez l'intervalle en fonction de la latence tolérable par votre application entre une écriture et le moment où les données deviennent interrogeables. Si votre système subit une forte pression de compaction, augmentez cet intervalle.
Seuil de taille des données
Le seuil de taille des données pour le commit automatique est fixé à 64 Mo par défaut.
-- Change the data size threshold to 128 MB.
ALTER TABLE dt SET ("group_commit_data_bytes" = "134217728");
Limites
Dégradation de INSERT INTO VALUES
Lorsque le group commit est activé, les instructions INSERT INTO VALUES suivantes sont automatiquement rétrogradées vers le mode sans group commit :
Écriture au sein d'une transaction explicite :
BEGIN;INSERT INTO VALUES;COMMITSpécification d'un libellé :
INSERT INTO dt WITH LABEL {label} VALUESPrésence d'une expression dans VALUES : par exemple,
INSERT INTO dt VALUES (1 + 100)Écriture utilisant la mise à jour par colonne
La table cible ne prend pas en charge les modifications légères de schéma
Dégradation de Stream Load et HTTP Stream
Les tâches Stream Load et HTTP Stream suivantes sont automatiquement rétrogradées vers le mode sans group commit :
Spécification d'un libellé via
-H "label:my_label"Utilisation du mode de validation en deux phases (2PC)
Écriture utilisant la mise à jour par colonne
La table cible ne prend pas en charge les modifications légères de schéma
Modèle Unique Key
Le group commit ne garantit pas l'ordre des commits avec le modèle Unique Key. Pour assurer la cohérence des données, utilisez le group commit conjointement avec une colonne de séquence.
Prise en charge de max_filter_ratio
En mode d'importation standard, filter_ratio détermine la validation en fonction du ratio de lignes échouées par rapport au total. En mode group commit, les importations provenant de plusieurs clients sont fusionnées en une seule importation interne et validées comme une unité unique.
Le group commit prend partiellement en charge la sémantique de max_filter_ratio : celle-ci ne s'applique que si le nombre total de lignes importées ne dépasse pas la valeur de l'élément de configuration backend (BE) group_commit_memory_rows_for_max_filter_ratio. La valeur par défaut est 10000.
Comportement du WAL en mode asynchrone
En async_mode, chaque importation est d'abord écrite dans les journaux WAL :
Si le commit interne réussit, les journaux WAL sont supprimés immédiatement.
Si le commit interne échoue, les journaux WAL servent à récupérer les données.
Le système bascule automatiquement de async_mode vers sync_mode dans les situations suivantes afin de préserver l'espace disque :
Les données importées occupent plus de 80 % d'un répertoire WAL unique.
Une tâche Stream Load fragmentée est soumise avec une taille totale de données inconnue.
Le volume de données est faible, mais l'espace disque disponible est insuffisant.
Modifications de schéma
Si une modification lourde de schéma se trouve dans sa phase finale de modification des métadonnées, le système rejette les nouveaux group commits pour garantir la compatibilité des journaux WAL avec le schéma de la table. Les clients concernés reçoivent l'exception suivante :
insert table ${table_name} is blocked on schema change
Réessayez l'importation côté client lorsque cette exception survient.
Les modifications légères de schéma (ajout ou suppression de colonnes, changement de longueur VARCHAR, renommage de colonnes) ne bloquent pas les group commits. Toutes les autres modifications de schéma sont considérées comme lourdes.