Problem/Motivation

When attempting to save a node to a published transition using content moderation workflow and the node has a large amount of nested Paragraphs the save time will exceed the max execution time. This does not seem to happen if the node transition to a moderation state that is considered unpublished.

Steps to reproduce

  1. Enable content moderation and create a simple workflow: Unpublished, Draft, Published and assign it a node bundle.
  2. Assign this node bundle a set of Paragraph bundles that also have the ability to nest other Paragraphs in them.
  3. Create a node of this bundle type and create a large amount of Paragraphs (we have around 200 attached to a single node and some have children.)
  4. Transition the node to a Published moderation state. The save should fail because it exceeds the max execution time.(this is set to 15min)

Issue fork paragraphs-3258261

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

blu_regard created an issue. See original summary.

coufu’s picture

I'm experiencing a similar issue with Paragraphs. They seem to not be able to scale. I don't have as many as you (200 is a lot!), but I do have probably about 50. Then the problem is compounded with translations etc. Does anyone have any reading material around how to optimize sites that rely heavily on Paragraphs?

michaellenahan’s picture

StatusFileSize
new2.48 KB

The drupal sites I am working on have many nested paragraphs. One example with a total of 167 paragraphs is not unusual. When we save the node, entity_reference_revisions `EntityReferenceRevisionsItem::preSave()` sets `$needs_save = TRUE` for all these paragraphs, without any check on whether they have been changed or not.

I have proposed a patch for that in the entity_reference_revisions issue queue:
https://www.drupal.org/project/entity_reference_revisions/issues/3267490...

With that patch in place, two changes are needed in paragraphs module:

Change 1: Conditional `setNeedsSave(TRUE)` in `ParagraphsWidget::massageFormValues()`

Only call `setNeedsSave(TRUE)` for paragraphs in `edit` mode. Paragraphs in `closed` or `preview` mode don't have their form values extracted (no user changes are possible), so they don't need saving.

src/Plugin/Field/FieldWidget/ParagraphsWidget.php
src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php

// Before:
$paragraphs_entity->setNeedsSave(TRUE);

// After:
if ($widget_state['paragraphs'][$item['_original_delta']]['mode'] === 'edit') {
  $paragraphs_entity->setNeedsSave(TRUE);
}

Change 2: `Paragraph::onChange()` override for programmatic change detection

Adds an `onChange()` method to `Paragraph.php` that auto-sets `needsSave(TRUE)` whenever a field value changes programmatically (via `$paragraph->set()`, `$paragraph->get()->setValue()`, etc.). This ensures programmatic modifications are never silently dropped.

The `onChange()` implementation also:
- Propagates `needsSave(TRUE)` to the untranslated base entity (for translation support)
- Propagates `needsSave(TRUE)` to the immediate parent paragraph (for nested paragraph support)

src/Entity/Paragraph.php

public function onChange($name) {
  parent::onChange($name);
  if ($this->needsSave()) {
    return;
  }
  $this->setNeedsSave(TRUE);
  if (!$this->isDefaultTranslation()) {
    $this->getUntranslated()->setNeedsSave(TRUE);
  }
  $parent = $this->getParentEntity();
  if ($parent instanceof static) {
    $parent->setNeedsSave(TRUE);
  }
}
michaellenahan’s picture

Status: Active » Needs review

michaellenahan’s picture

I did some more testing. Patch and update to MR to follow.

michaellenahan’s picture

StatusFileSize
new4.62 KB
michaellenahan’s picture

michaellenahan’s picture

StatusFileSize
new6.55 KB
michaellenahan’s picture

The original patch (comment #3) handled the two main cases: form edits and programmatic changes. But during testing, we found that Drupal's internal machinery triggers onChange() in ways that look like real edits but aren't — particularly revision metadata fields being set during createRevision() in form build, which silently re-flagged every paragraph before the user even submitted the form.

We also found code paths that bypass the field API entirely (behavior settings), a stale form state value that masked a langcode propagation bug, and the drag-and-drop reorder function unconditionally flagging all paragraphs. Each of these was a way that needsSave(TRUE) was being set when it shouldn't be, or not being set when it should be — undermining the optimization or causing silent data loss.

Despite these changes, this current patch 3258261-10.patch (and the associated #3267490 patch), we are able to significantly (90%+) reduce save time.

Example timings for our 167-paragraph node are as follows:

Saving the node with no changes - previously: 968ms / 167 paragraph saves - now: 1 paragraph save
Drag and drop paragraphs - previously: 968ms / 167 saves - now: ~400ms / ~67 saves

The drag-and-drop saves are caused by AJAX form rebuild, we decided not to go down that rabbit-hole for now. Even still, 167 -> 67 saves is a significant improvement.