Skip to content

Make updateSiblings and resetChildrenIndexBy fully async - #426

Open
cancan101 wants to merge 8 commits into
pimcore:2026.xfrom
cancan101:claude/github-issue-389-fOm4d
Open

Make updateSiblings and resetChildrenIndexBy fully async#426
cancan101 wants to merge 8 commits into
pimcore:2026.xfrom
cancan101:claude/github-issue-389-fOm4d

Conversation

@cancan101

Copy link
Copy Markdown
Contributor

Changes in this pull request

Resolves #389

Additional info

  • DataObjectIndexUpdateSubscriber::updateDataObject() and DocumentIndexUpdateSubscriber::updateDocument() were calling IndexElementIndexService::updateSiblings() and resetChildrenIndexBy() synchronously, making blocking calls to the search provider (e.g. OpenSearch/Elasticsearch) during HTTP requests
  • Introduced UpdateSiblingsMessage and UpdateSiblingsHandler to dispatch sibling/children index updates via the Symfony Messenger bus, so they are processed asynchronously through the existing pimcore_generic_data_index_queue transport
  • Removed the direct IndexElementIndexServiceInterface dependency from both event subscribers, replacing it with MessageBusInterface to dispatch the new message

This eliminates the synchronous search-provider roundtrips that caused significant slowdowns during object updates, especially for large folders.

Test plan

  • Verify data object updates dispatch UpdateSiblingsMessage to the queue instead of blocking
  • Verify document updates dispatch UpdateSiblingsMessage to the queue instead of blocking
  • Verify sibling index values are correctly updated after async processing
  • Verify resetChildrenIndexBy is triggered for data objects with OBJECT_CHILDREN_SORT_BY_INDEX sort mode
  • Verify no regressions in add/delete operations (unchanged paths)
  • Confirm message routing via config/pimcore/messenger.yaml

cancan101 added 2 commits May 25, 2026 15:49
The updateDataObject and updateDocument event subscribers were calling
IndexElementIndexService::updateSiblings() and resetChildrenIndexBy()
synchronously, which made blocking calls to the search provider during
HTTP requests. This caused significant slowdowns for large folders.

Dispatch an UpdateSiblingsMessage via the message bus so that sibling
and children index updates are processed asynchronously through the
existing queue infrastructure, consistent with how updateData() works.
Tests cover:
- UpdateSiblingsHandler: verifies service method dispatch based on
  element type, sort-by-index flag, resetChildrenIndexBy flag, and
  null element handling
- DataObjectIndexUpdateSubscriber: verifies UpdateSiblingsMessage is
  dispatched with correct parameters
- DocumentIndexUpdateSubscriber: same verification for documents
@sonarqubecloud

Copy link
Copy Markdown

@cancan101

Copy link
Copy Markdown
Contributor Author

Follow up PR: cancan101#2

@mcop1 mcop1 left a comment

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.

Hello @cancan101 ,
thanks for the pr, could you merge the current 2026.x again? This should fix the failing tests.

I noticed a few things:

  • resetChildrenIndexBy() fills the shared BulkOperationService buffer but never
    calls commit() (unlike updateSiblings(), see IndexElementIndexService.php:77).
  • I guess we need to consult SynchronousProcessingServiceInterface as well here, because CLI and tests enable synchronous mode and expect direct results
  • UpdateSiblingsMessage gets dispatched on every save, ignoring the isInstalled guard in updateData

How do you want to handle the follow up? Merge this pr first, then rebase the follow up to 2026.x in pimcore/gdi?

if ($event->getObject()->getChildrenSortBy() === AbstractObject::OBJECT_CHILDREN_SORT_BY_INDEX) {
$this->indexElementIndexService->resetChildrenIndexBy($event->getObject());
}
$this->messageBus->dispatch(

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.

Move the isInstalled guard up here? Or at least check it here as well?

&& $element instanceof AbstractObject
&& $element->getChildrenSortBy() === AbstractObject::OBJECT_CHILDREN_SORT_BY_INDEX
) {
$this->indexElementIndexService->resetChildrenIndexBy($element);

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.

commit() missing here, either add it to message, or probably even at the end of resetChildrenIndexBy method itself.

claude added 3 commits July 9, 2026 22:39
- Commit the bulk buffer at the end of resetChildrenIndexBy() so children
  index resets are actually flushed to the search index (previously the
  buffer was filled via addUpdate() but never committed).
- Respect synchronous processing in the update subscribers: dispatch
  UpdateSiblingsMessage with a SYNC TransportNamesStamp when
  SynchronousProcessingService is enabled, so CLI commands and tests get
  direct, synchronous results (mirrors QueueMessagesDispatcher).
- Guard the message dispatch behind Installer::isInstalled() in
  updateDataObject()/updateDocument() so no message is dispatched when the
  bundle is not installed, matching the existing guard in updateData().
- Extend the subscriber unit tests to cover the synchronous-dispatch and
  not-installed paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017t757oNTEGGJYPTdnTaMsA
@mcop1

mcop1 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Hello @cancan101 ,

just wanted to ask if you are still working on it or if the pr is ready for review again? Besides the failing tests.

Installer and QueueMessagesDispatcher are final, so PHPUnit cannot double
them and every new subscriber test errored out. Type-hint the interfaces
instead:

- both subscribers now take Pimcore's InstallerInterface; the concrete
  installer is wired explicitly since that interface is too generic to
  autowire
- add QueueMessagesDispatcherInterface and alias it to the existing service
- IndexQueueServiceInterface now returns itself instead of the concrete
  IndexQueueService, so the fluent chain can be doubled

Also drop an unused import in UpdateSiblingsHandler.
Copilot AI review requested due to automatic review settings July 27, 2026 21:06

Copilot AI left a comment

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.

Pull request overview

Verdict: Needs changes. The PR moves sibling indexing to Messenger, but transaction-safety and message validation remain unresolved.

Changes:

  • Adds asynchronous sibling-update messages and handlers.
  • Routes subscriber updates through Messenger with synchronous-mode support.
  • Adds interfaces, service wiring, bulk commits, and unit tests.

Review assessment:

  • Root cause: Removes normal request-time provider calls (DataObjectIndexUpdateSubscriber.php:73-84, DocumentIndexUpdateSubscriber.php:69-80).
  • Call sites/boundary: Both existing callers are covered, but direct dispatch bypasses the transaction protection in IndexQueueService.php:68-82.
  • BC: No public API break found; changed contracts are internal.
  • Tests: Handler and dispatch branches are covered, but transaction rollback/race behavior is not.
  • Documentation: No changelog or upgrade note was added.
  • Risk: Non-Doctrine transports can process uncommitted updates; the message also accepts unsupported element types (UpdateSiblingsMessage.php:23).

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Unit/MessageHandler/UpdateSiblingsHandlerTest.php Tests handler branches.
tests/Unit/EventSubscriber/DocumentIndexUpdateSubscriberTest.php Tests document dispatch.
tests/Unit/EventSubscriber/DataObjectIndexUpdateSubscriberTest.php Tests object dispatch.
src/Service/SearchIndex/IndexQueueServiceInterface.php Generalizes fluent returns.
src/Service/SearchIndex/IndexQueue/QueueMessagesDispatcherInterface.php Adds dispatcher contract.
src/Service/SearchIndex/IndexQueue/QueueMessagesDispatcher.php Implements new contract.
src/Service/SearchIndex/IndexElementIndexService.php Commits child-index updates.
src/MessageHandler/UpdateSiblingsHandler.php Processes sibling messages.
src/Message/UpdateSiblingsMessage.php Defines asynchronous payload.
src/EventSubscriber/DocumentIndexUpdateSubscriber.php Dispatches document updates.
src/EventSubscriber/DataObjectIndexUpdateSubscriber.php Dispatches object updates.
config/services/search/index.yaml Wires dispatcher interface.
config/services.yaml Configures subscriber dependencies.
config/pimcore/messenger.yaml Routes the new message.

Comment on lines +73 to +77
$this->messageBus->dispatch(
new UpdateSiblingsMessage(
$event->getObject()->getId(),
ElementType::DATA_OBJECT->value,
true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is pre-existing behaviour rather than something introduced here. Before this PR, updateDataObject() called IndexElementIndexService::updateSiblings() synchronously and inline, with no transaction guard at all, so the same pre-commit/rollback window already existed — without a queue in between.

On a default install this change actually improves things: Pimcore's default transport prefix is doctrine://default?queue_name= (CoreBundle config/pimcore/default.yaml), so the queued message is inserted in the same DB transaction and is rolled back together with the object.

The remaining gap applies to installs that point PIMCORE_MESSENGER_TRANSPORT_DSN_PREFIX at AMQP/Redis, plus the synchronous-mode path. Closing that properly means modelling sibling updates as generic_data_index_queue entries — the guard in IndexQueueService.php:68-82 falls back to that DB table, not to Messenger — which is a larger change than this PR. Happy to look at it as a follow-up.

Comment on lines +69 to +73
$this->messageBus->dispatch(
new UpdateSiblingsMessage(
$event->getDocument()->getId(),
ElementType::DOCUMENT->value,
false

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is pre-existing behaviour rather than something introduced here. Before this PR, updateDocument() called IndexElementIndexService::updateSiblings() synchronously and inline, with no transaction guard at all, so the same pre-commit/rollback window already existed — without a queue in between.

On a default install this change actually improves things: Pimcore's default transport prefix is doctrine://default?queue_name= (CoreBundle config/pimcore/default.yaml), so the queued message is inserted in the same DB transaction and is rolled back together with the object.

The remaining gap applies to installs that point PIMCORE_MESSENGER_TRANSPORT_DSN_PREFIX at AMQP/Redis, plus the synchronous-mode path. Closing that properly means modelling sibling updates as generic_data_index_queue entries — the guard in IndexQueueService.php:68-82 falls back to that DB table, not to Messenger — which is a larger change than this PR. Happy to look at it as a follow-up.

Comment thread src/Message/UpdateSiblingsMessage.php Outdated
{
public function __construct(
private int $elementId,
private string $elementType,
The message took a plain string, so an unsupported type such as 'asset'
resolved to an Asset and blew up in updateSiblings(). Use the enum as
EnqueueRelatedIdsMessage does, and skip anything that is not an object or
a document in the handler.
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

updateDataObject is only partially async

6 participants