nextcloud / nextcloud/mail

findIdsByQuery() can return duplicate message IDs when UID input is chunked

Open
#13,700 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

0. to triage bug
Dominant language
JavaScript
Stars
1k
Forks
348
Avg merge
12h 28m
Merged PRs (30d)
91

Description

Steps to reproduce
  1. Use a mailbox with more than 1000 locally known messages.
  2. Perform a filtered mailbox sync so that SyncService calls MessageMapper::findIdsByQuery() with a large UID array.
  3. Instrument the number of UIDs passed to findIdsByQuery() and the number of message IDs returned.
  4. Compare the returned IDs before and after deduplication.

In our production case:

changedUids = 40810
changedIds = 49010

After deduplicating the IDs returned from all UID chunks:

changedIds = 40810

Expected behavior

findIdsByQuery() should return each matching message ID only once.

Chunking the UID input into groups of 1000 should not change the logical result set or introduce duplicate message IDs.

Actual behavior

When $uids contains more than 1000 entries, findIdsByQuery() executes the same query once per UID chunk and concatenates the results:

if ($uids !== null) {
    return array_flat_map(function (array $chunk) use ($qb, $select) {
        $qb->setParameter('uids', $chunk, IQueryBuilder::PARAM_INT_ARRAY);

        return array_map(
            static fn (Message $message) => $message->getId(),
            $this->findEntities($select)
        );
    }, array_chunk($uids, 1000));
}

There is no deduplication when results from the chunks are combined.

In our case:

changedUids = 40810
changedIds before deduplication = 49010
changedIds after deduplication = 40810

This also caused unnecessary message hydration and additional PHP memory usage.

During testing, memory usage around this stage dropped approximately from:

~224 MB

to:

~190 MB

after deduplicating the result.

A tested workaround is:

if ($uids !== null) {
    $resultIds = [];

    foreach (array_chunk($uids, 1000) as $chunk) {
        $select->setParameter('uids', $chunk, IQueryBuilder::PARAM_INT_ARRAY);

        foreach ($this->findIds($select) as $id) {
            $resultIds[$id] = $id;
        }
    }

    return array_values($resultIds);
}
Mail app version

5.12.0

Nextcloud version

34.0.2

Mailserver or service

IMAP GMAIL

Operating system

Debian GNU/Linux 13 (trixie)

PHP engine version

Other

Nextcloud memory caching

memcache.local: APCu memcache.distributed: Redis memcache.locking: Redis

Web server

Apache (supported)

Database

PostgreSQL

Additional info
PHP: 8.5.10
Web server: Apache 2.4.68
OS: Debian GNU/Linux 13 (trixie)

Nextcloud memory caching:
- memcache.local: \OC\Memcache\APCu
- memcache.distributed: \OC\Memcache\Redis
- memcache.locking: \OC\Memcache\Redis

PHP memory_limit: 1024M

The issue was observed while debugging mailbox sync memory consumption on a mailbox with approximately 41,000 locally known messages.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at MessageMapper::findIdsByQuery(), which SyncService calls during filtered mailbox syncs with large UID arrays. Reproduce the behavior using more than 1000 UIDs and inspect how chunk results are combined. Done means each matching message ID is returned once, chunking does not change the logical result set, and the behavior is covered by an appropriate test.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend, databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.