Skip to content

perf: Enhance objects using constant sql queries count to get rid of N+1 queries - #2857

Open
Koc wants to merge 1 commit into
mainfrom
feature/optimize-enhance-tables
Open

perf: Enhance objects using constant sql queries count to get rid of N+1 queries#2857
Koc wants to merge 1 commit into
mainfrom
feature/optimize-enhance-tables

Conversation

@Koc

@Koc Koc commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

In our organization we operate a large amount of tables/views, so there are performance bottleneck here:

PERFORMANCE: Controller OCA\\Tables\\Controller\\Api1Controller::index executed 1883 queries in 20.1 seconds
PERFORMANCE: Controller OCA\\Tables\\Controller\\TableController::index executed 1887 queries in 16.6 seconds
  • Replaced TableService::enhanceTable() in a loop with a single call of the enhanceTables()
  • Replaced ViewService::enhanceView() in a loop with a single call of theenhanceViews()
  • Added batch helpers in mappers/services and UserHelper for row/column counts, share counts and owner display names.

🖼️ Screenshots

Here profiler results for GET /apps/tables/table endpoint for 18 tables and 9 views:

🏚️ Before 🏡 After
121 queries 29 queries
image image

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔙 Backport requests are created or not needed: /backport to stableX.X
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@Koc
Koc force-pushed the feature/optimize-enhance-tables branch 2 times, most recently from 9b6bd68 to 4033598 Compare July 29, 2026 00:28
@Koc
Koc marked this pull request as ready for review July 29, 2026 18:08
@Koc
Koc requested review from blizzz and enjeck as code owners July 29, 2026 18:08
@Koc Koc changed the title perf: Call enhanceTables for a multiple tables at once to get rid of N+1 queries perf: Enhance objects for a multiple tables at once to get rid of N+1 queries Jul 29, 2026
@Koc Koc changed the title perf: Enhance objects for a multiple tables at once to get rid of N+1 queries perf: Enhance objects using constant sql queries count to get rid of N+1 queries Jul 29, 2026
@Koc
Koc force-pushed the feature/optimize-enhance-tables branch 2 times, most recently from 29cbb47 to 44e315b Compare July 29, 2026 21:57
Comment thread lib/Db/ShareMapper.php
Comment thread lib/Service/ViewService.php Outdated
Comment thread lib/Db/RowSleeveMapper.php Outdated
Comment thread lib/Controller/Api1Controller.php Outdated
@Koc
Koc force-pushed the feature/optimize-enhance-tables branch 2 times, most recently from 2686136 to e9b83fb Compare August 9, 2026 14:30
@Koc
Koc requested a review from enjeck August 9, 2026 14:46
@samin-z
samin-z self-requested a review August 10, 2026 15:48
@Koc
Koc force-pushed the feature/optimize-enhance-tables branch from e9b83fb to b286459 Compare August 24, 2026 00:51
Comment thread lib/Db/ShareMapper.php
Comment on lines +133 to +136
if ($sender !== '') {
$qb->andWhere($qb->expr()->eq('sender', $qb->createNamedParameter($sender, IQueryBuilder::PARAM_STR)));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be moved to plural findAllSharesForNodes only (which is what the count methods need)? This adds sender filtering to the shared singular method, which previously ignored $sender and returned all shares for a node. now, it changes every existing caller that passes a user id.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a downstream effect of the findAllSharesForNode change above, the list all shares" endpoint now returns only shares created by the current user. An owner will no longer see shares created by co-managers on their own table or view, so they cannot review or revoke them here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for sharecontroller.php

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have a test for deleteAllForContext here. Since this now only iterates shares whose sender equals the current user, so for a context with shares from more than one sender the context_navigation entries for the others are not cleaned up before the bulk deleteByNode, leaving orphaned rows???

try {
return new DataResponse($this->viewService->formatViews($this->viewService->findAll($this->tableService->find($tableId))));
$table = $this->tableService->find($tableId);
return new DataResponse($this->viewService->formatViews($table->getViews() ?? []));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initially, we return 403 for read-only users . Now, read-only user gets 200 with an empty list, since enhanceTables only loads views for owned or managed tables. So we need to mofify the annotation?

Comment thread lib/Db/ColumnMapper.php
Comment on lines +176 to +188
$counts = array_fill_keys($tableIds, 0);
foreach (array_chunk($tableIds, 1000 - 1) as $tableIdsChunk) {
$qb = $this->db->getQueryBuilder();
$qb->select('table_id', $qb->func()->count('*', 'counter'))
->from($this->table)
->where($qb->expr()->in('table_id', $qb->createNamedParameter($tableIdsChunk, IQueryBuilder::PARAM_INT_ARRAY)))
->groupBy('table_id');

$result = $qb->executeQuery();
while ($row = $result->fetch()) {
$counts[(int)$row['table_id']] = (int)$row['counter'];
}
$result->closeCursor();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without try...catch, a \OCP\DB\Exception now propagates?

Comment thread lib/Db/ViewMapper.php
$qb->select('v.*', 't.ownership')
->from($this->table, 'v')
->innerJoin('v', 'tables_tables', 't', 't.id = v.table_id')
->where($qb->expr()->in('v.table_id', $qb->createNamedParameter($tableIds, IQueryBuilder::PARAM_INT_ARRAY)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't we need same array_chunk here? Since every other batch method you added chunks the IN list at about 1000, and findMany does too, but this one passes all table ids in a single IN. What if the user has more than ~1000 tables, or the DB has a 1000 element IN limit (Oracle does, i think?)??

@Koc
Koc force-pushed the feature/optimize-enhance-tables branch from b286459 to 306f894 Compare August 27, 2026 23:53
@Koc
Koc force-pushed the feature/optimize-enhance-tables branch from 306f894 to f5cb044 Compare August 27, 2026 23:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants