Skip to content

remote:drush sql-dump truncates large output due to OutputFormatter overhead #2804

@dpagini

Description

@dpagini

Summary

terminus remote:drush <site.env> -- sql-dump silently truncates output for large databases (~886 MB). The dump ends mid-INSERT with no -- Dump completed marker and exit code 0. Small databases (~1.5 MB) work fine.

This is a regression introduced in Terminus v4 via the upgrade to symfony/console 6.x.

Root Cause

SSHBaseCommand::getOutputCallback() writes SSH output through $output->write($buffer) using the default OUTPUT_NORMAL mode. This routes every chunk of SQL data through OutputFormatter::format(), which regex-parses the data looking for console-style tags (<info>, <error>, etc.).

Symfony Console 6.x introduced a ~27× performance regression in OutputFormatter::formatAndWrap() (see upstream issue). The slowdown creates backpressure through the SSH pipe, throttling the remote drush sql-dump process until it's killed by server-side execution limits—resulting in a silently truncated dump.

Two Issues

1. Performance regression (v4 only)

The symfony/console 6.x formatter is ~27× slower on HTML-rich text. For an 886 MB dump:

Terminus v3 (Console 5.4) Terminus v4 (Console 6.4)
Formatter time per chunk 7.8ms 212ms
Projected total ~3.6 min ~99 min
Dump result ✅ Complete (886 MB) ❌ Truncated (~848 KB)

2. Latent data corruption (v3 and v4)

Both versions silently strip Symfony Console style tags from piped output. If any database content contains literal <info>, <error>, <comment>, <question>, or <fg=...> strings, they are removed from the dump:

Input:  INSERT INTO node__body VALUES ('<info>Important notice</info>');
Output: INSERT INTO node__body VALUES ('Important notice');

This is rare in practice but means dumps are not byte-accurate even when they complete.

Suggested Fix

In src/Commands/Remote/SSHBaseCommand.php, the getOutputCallback() method should use OUTPUT_RAW:

private function getOutputCallback()
{
    $output = $this->output();
    $stderr = $this->stderr();

    return function ($type, $buffer) use ($output, $stderr) {
        if (Process::ERR === $type) {
            $stderr->write($buffer);
        } else {
            $output->write($buffer, false, OutputInterface::OUTPUT_RAW);
        }
    };
}

OUTPUT_RAW bypasses OutputFormatter::format() entirely, which is correct for streaming raw command output. The formatter is designed for styled console messages, not for piping data.

This fixes both issues: the performance regression (no formatting overhead) and the data corruption (no tag stripping).

Reproduction

# Large database (fails with v4)
terminus remote:drush <site.env> -- sql-dump --structure-tables-list=cache,cache_* | wc -c

# Compare: same command via Terminus v3 produces ~886MB, v4 produces ~848KB

Environment

  • Terminus v4.1.4 (symfony/console v6.4.32)
  • Terminus v3.6.1 (symfony/console v5.4.43) — works correctly
  • PHP 8.x

Disclaimer: Bug report heavily written by AI...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions