Generated with the help of an LLM.

AI was used to debug and profile this issue.

Problem/Motivation

ConfigImporterBatch::process() runs once per batch call. After each call it merges $config_importer->getErrors() into $context['results']['errors']:

$config_importer = $context['sandbox']['config_importer'];
$config_importer->doSyncStep($sync_step, $context);
if ($errors = $config_importer->getErrors()) {
  if (!isset($context['results']['errors'])) {
    $context['results']['errors'] = [];
  }
  $context['results']['errors'] = array_merge($errors, $context['results']['errors']);
}

The importer is kept in the sandbox for the whole sync step, and getErrors() returns every error logged since the step started, not only the ones from the current call. So when a step needs several batch calls, like processConfigurations, each call adds all of the step's earlier errors to the results again. An error logged early in the step ends up in the results once for every remaining call. ConfigImporterBatch::finish() then passes every copy to both the messenger and the config_sync logger.

The number of copies grows with the square of the number of batch calls, so an import with a few hundred errors can produce hundreds of thousands of messages and become much slower.

On our site, a drush si --existing-config that had 274 actual errors (see #3545595 for their cause) printed 554,285 error lines, about 184,762 errors after accounting for each error being printed three times. With the proposed fix, the same install printed each of the 274 errors once (822 lines), and the install time dropped from about 29 minutes to about 11 minutes.

Steps to reproduce

  1. Set up a config import where the processConfigurations step logs errors and needs more than one batch call. For example, the steps in #3545595 log a "Deleted and replaced configuration entity" error for every language override that a module ships in config/install/language/xx. With enough such overrides, the step needs many batch calls.
  2. Run the import, either through the UI or with drush si --existing-config.
  3. Each error is shown and logged many times. The errors logged early in the step are repeated the most.

Proposed resolution

Keep track in the sandbox of how many of the importer's errors have already been added to the results, and only add the new ones:

diff --git a/core/lib/Drupal/Core/Config/Importer/ConfigImporterBatch.php b/core/lib/Drupal/Core/Config/Importer/ConfigImporterBatch.php
index c401c3a..a9aedbe 100644
--- a/core/lib/Drupal/Core/Config/Importer/ConfigImporterBatch.php
+++ b/core/lib/Drupal/Core/Config/Importer/ConfigImporterBatch.php
@@ -25,16 +25,23 @@ class ConfigImporterBatch {
   public static function process(ConfigImporter $config_importer, $sync_step, &$context) {
     if (!isset($context['sandbox']['config_importer'])) {
       $context['sandbox']['config_importer'] = $config_importer;
+      // If the same importer is used for all the steps, the errors of the
+      // earlier steps have already been added to the results.
+      $context['sandbox']['error_count'] = count($config_importer->getErrors());
     }
 
     $config_importer = $context['sandbox']['config_importer'];
     $config_importer->doSyncStep($sync_step, $context);
-    if ($errors = $config_importer->getErrors()) {
+    // The importer keeps the errors of the earlier calls, so only add the
+    // errors logged since the previous call.
+    $errors = $config_importer->getErrors();
+    if ($new_errors = array_slice($errors, $context['sandbox']['error_count'])) {
       if (!isset($context['results']['errors'])) {
         $context['results']['errors'] = [];
       }
-      $context['results']['errors'] = array_merge($errors, $context['results']['errors']);
+      $context['results']['errors'] = array_merge($new_errors, $context['results']['errors']);
     }
+    $context['sandbox']['error_count'] = count($errors);
   }

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3633776

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

tuutti created an issue. See original summary.

tuutti’s picture

Status: Active » Needs review