mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 01:54:10 +00:00
perf(imap): make the ghost reaper free to run when there is nothing to reap
The reaper runs with the other self-repairs on every mailbox sync, and the answer is "nothing" on every install that has run it once — so the cost that matters is the asking, repeated per mailbox per poll forever, over the largest table here. A partial index over epoch-dated rows makes that a probe of a relation that is normally empty, and a cheap literal existence check in front of the eight-way predicate is what actually hits it. The literal is deliberate: a bound parameter cannot be proven to imply a partial index's condition at plan time, which would have left the index built and unused. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8aeca04f0b
commit
580e6a5bc6
2 files changed
+65
No files matched your search
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes "are there any ghost rows left" free to ask.
|
||||||
|
*
|
||||||
|
* GhostMessageReaper runs with the other self-repairs on every mailbox sync,
|
||||||
|
* looking for the epoch-dated corpses a failed IMAP fetch used to leave behind
|
||||||
|
* (see MessageSyncer::isUsableFetch). The answer is no on every install that has
|
||||||
|
* run it once — which, after the first sync, is every install — so the cost that
|
||||||
|
* matters is not the cleanup but the asking, repeated per mailbox per poll
|
||||||
|
* forever.
|
||||||
|
*
|
||||||
|
* Without an index that is a sequential scan of the message table each time, on
|
||||||
|
* a table that is the largest one here by a wide margin. A partial index makes
|
||||||
|
* it an index probe of a relation that is normally empty: nothing real is dated
|
||||||
|
* 1970, so this indexes the rows that should not exist and nothing else. On a
|
||||||
|
* healthy database it occupies essentially no space.
|
||||||
|
*
|
||||||
|
* The predicate is a literal rather than a parameter so the planner can match
|
||||||
|
* it against the reaper's own literal probe — a bound parameter cannot be
|
||||||
|
* proven to imply a partial index's condition at plan time, which would have
|
||||||
|
* left the index built and unused.
|
||||||
|
*/
|
||||||
|
final class Version20260811160000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Partial index over epoch-dated message rows, so the ghost reaper costs nothing when there is nothing to reap';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql(
|
||||||
|
"CREATE INDEX idx_message_epoch_ghost ON message (id) WHERE received_at < TIMESTAMP '1971-01-01'",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP INDEX idx_message_epoch_ghost');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,22 @@ readonly class GhostMessageReaper
|
|||||||
*/
|
*/
|
||||||
public function reap(): int
|
public function reap(): int
|
||||||
{
|
{
|
||||||
|
// Asked before the real query, and this is the only reason the reaper
|
||||||
|
// can afford to run on every mailbox sync. The full predicate is eight
|
||||||
|
// conditions over the largest table here; this is one probe of a partial
|
||||||
|
// index that is empty on any install that has already been cleaned —
|
||||||
|
// which is every install, after the first run. See
|
||||||
|
// Version20260811160000, and note the literal: a bound parameter cannot
|
||||||
|
// be proven to imply the index's condition, so the index would be built
|
||||||
|
// and then not used.
|
||||||
|
$suspects = $this->em->getConnection()->fetchOne(
|
||||||
|
"SELECT 1 FROM message WHERE received_at < TIMESTAMP '1971-01-01' LIMIT 1",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (false === $suspects) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
$ghosts = $this->messages->findEpochGhosts(self::REAP_BATCH);
|
$ghosts = $this->messages->findEpochGhosts(self::REAP_BATCH);
|
||||||
|
|
||||||
if ([] === $ghosts) {
|
if ([] === $ghosts) {
|
||||||
|
|||||||
Reference in new issue
Block a user