findIdsByQuery() can return duplicate message IDs when UID input is chunked
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 1k
- Forks
- 348
- Avg merge
- 12h 28m
- Merged PRs (30d)
- 91
Description
Steps to reproduce
- Use a mailbox with more than 1000 locally known messages.
- Perform a filtered mailbox sync so that
SyncServicecallsMessageMapper::findIdsByQuery()with a large UID array. - Instrument the number of UIDs passed to
findIdsByQuery()and the number of message IDs returned. - 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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