Optimize binaries insert

This commit is contained in:
DariusIII
2026-04-28 15:06:56 +02:00
parent 6b3a35424b
commit c2c99e37df
2 changed files with 50 additions and 26 deletions
+16 -23
View File
@@ -116,10 +116,15 @@ final class BinaryHandler
int $fileNumber,
int $partSize
): int {
$affected = DB::affectingStatement(
'INSERT OR IGNORE INTO binaries (binaryhash, name, collections_id, totalparts, currentparts, filenumber, partsize) VALUES (?, ?, ?, ?, 1, ?, ?)',
[$hash, $name, $collectionId, $totalParts, $fileNumber, $partSize]
);
$affected = DB::table('binaries')->insertOrIgnore([
'binaryhash' => $hash,
'name' => $name,
'collections_id' => $collectionId,
'totalparts' => $totalParts,
'currentparts' => 1,
'filenumber' => $fileNumber,
'partsize' => $partSize,
]);
if ($affected > 0 && ($lastId = (int) DB::connection()->getPdo()->lastInsertId()) > 0) {
$this->insertedBinaryIds[$lastId] = true;
@@ -214,33 +219,21 @@ final class BinaryHandler
private function flushUpdatesMysql(array $updates, int $chunkSize): bool
{
foreach (array_chunk($updates, $chunkSize) as $chunk) {
// Build a CASE statement for batch UPDATE instead of INSERT...ON DUPLICATE KEY
// This avoids FK constraint violations when collections have been deleted
$ids = [];
$partsizeCases = [];
$currentpartsCases = [];
$selects = [];
$bindings = [];
foreach ($chunk as $row) {
$ids[] = $row['id'];
$partsizeCases[] = 'WHEN id = ? THEN partsize + ?';
$currentpartsCases[] = 'WHEN id = ? THEN currentparts + ?';
$selects[] = 'SELECT ? AS id, ? AS partsize, ? AS currentparts';
$bindings[] = $row['id'];
$bindings[] = $row['partsize'];
}
foreach ($chunk as $row) {
$bindings[] = $row['id'];
$bindings[] = $row['currentparts'];
}
$idPlaceholders = implode(',', array_fill(0, count($ids), '?'));
$bindings = array_merge($bindings, $ids);
$sql = 'UPDATE binaries SET '
.'partsize = CASE '.implode(' ', $partsizeCases).' ELSE partsize END, '
.'currentparts = CASE '.implode(' ', $currentpartsCases).' ELSE currentparts END '
.'WHERE id IN ('.$idPlaceholders.')';
$sql = 'UPDATE binaries b INNER JOIN ('
.implode(' UNION ALL ', $selects)
.') u ON u.id = b.id '
.'SET b.partsize = b.partsize + u.partsize, '
.'b.currentparts = b.currentparts + u.currentparts';
DB::statement($sql, $bindings);
}
+34 -3
View File
@@ -68,6 +68,34 @@ class BinariesStorageInternalsTest extends TestCase
$this->assertSame(1, DB::table('parts')->count());
}
public function test_binary_handler_flushes_cached_article_aggregate_updates(): void
{
DB::statement('CREATE TABLE binaries (
id INTEGER PRIMARY KEY,
binaryhash BLOB,
name VARCHAR(255),
collections_id INT,
totalparts INT,
currentparts INT,
filenumber INT,
partsize INT,
UNIQUE(binaryhash, collections_id)
)');
$handler = new BinaryHandler;
$first = $this->parsedHeader(251, 1, 'Aggregate.Release', 100);
$second = $this->parsedHeader(252, 2, 'Aggregate.Release', 50);
$binaryId = $handler->getOrCreateBinary($first, 1, 1, 0);
$this->assertNotNull($binaryId);
$this->assertSame($binaryId, $handler->getOrCreateBinary($second, 1, 1, 0));
$this->assertTrue($handler->flushUpdates());
$binary = DB::table('binaries')->where('id', $binaryId)->first();
$this->assertSame(2, (int) $binary->currentparts);
$this->assertSame(150, (int) $binary->partsize);
}
public function test_sqlite_rollback_cleanup_keeps_unrelated_parts_with_same_article_number(): void
{
DB::statement('CREATE TABLE collections (id INTEGER PRIMARY KEY, collectionhash VARCHAR(40), noise VARCHAR(64))');
@@ -102,7 +130,10 @@ class BinariesStorageInternalsTest extends TestCase
$collectionHandler = new CollectionHandler(new class extends CollectionsCleaningService
{
public function __construct() {}
public function __construct()
{
parent::__construct();
}
public function collectionsCleaner(string $subject, string $groupName = ''): array
{
@@ -160,13 +191,13 @@ class BinariesStorageInternalsTest extends TestCase
subject VARCHAR(255),
fromname VARCHAR(255),
date DATETIME NULL,
xref TEXT DEFAULT "",
xref TEXT DEFAULT \'\',
groups_id INT,
totalfiles INT,
collectionhash VARCHAR(40) UNIQUE,
collection_regexes_id INT,
dateadded DATETIME NULL,
noise VARCHAR(64) DEFAULT ""
noise VARCHAR(64) DEFAULT \'\'
)');
DB::statement('CREATE TABLE binaries (