Problem/Motivation
If a node has a lot of redirects to it (in my case it was 15,000), there is significant burden placed on the underlying server which can cause random timeouts/out of memory issues on the node edit page.
This was a bit hard to track down. The issue first presented as a PARAGRAPHS field not being able to remove items or update field values, which I assumed was a PHP memory issue. I overrode the memory_limit to -1 but the issue was still occurring. When i looked into the dev console I saw An AJAX error would pop up whenever any changes were made, to the tune of:
ResponseText: Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away:
This lead me to tinkering with the max_allowed_packet
setting in mysql (
nice reference β
). I was able to circumvent the AJAX error and get edits working again by increasing this variable, but determining what value to set it at was a bit of "whack a mole" as the amount of resources needed varied depending on which field and what was changing.
It was after this I decided to take a look at the DOM and noticed over 15k rows in the URL Redirects table. The underlying code is here in a form alter:
function redirect_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
/** @var \Drupal\node\NodeInterface $node */
$node = $form_state->getFormObject()->getEntity();
if (!$node->isNew() && \Drupal::currentUser()->hasPermission('administer redirects')) {
$nid = $node->id();
// Find redirects to this node.
$redirects = \Drupal::service('redirect.repository')
->findByDestinationUri(["internal:/node/$nid", "entity:node/$nid"]);
// Assemble the rows for the table.
$rows = [];
/** @var \Drupal\Core\Entity\EntityListBuilder $list_builder */
$list_builder = \Drupal::service('entity_type.manager')->getListBuilder('redirect');
/** @var \Drupal\redirect\Entity\Redirect[] $redirects */
foreach ($redirects as $redirect) {
$row = [];
$path = $redirect->getSourcePathWithQuery();
$row['path'] = [
'class' => ['redirect-table__path'],
'data' => ['#plain_text' => $path],
'title' => $path,
];
$row['operations'] = [
'data' => [
'#type' => 'operations',
'#links' => $list_builder->getOperations($redirect),
],
];
$rows[] = $row;
}
You can see there is no check or consideration for quantity of redirects, it simply loads all available redirects and puts them into rows for the table. Moreover, there is no logic to determine if the URL Redirect table is even present or not. For example, if you move the URL Redirects field to DISABLED in the Form Display settings, this code still runs and is detrimental to performance.
Steps to reproduce
- Create a node with a few fields (title, body, image field)
- Generate 20k redirects to this node (not sure of exact amount yet)
- Edit the node created in step 1 and note how long it takes to load
- Repeat step 2
- Edit the node created in step 1 and note how long it takes to load, at this point it will most likely OOM
here is a sample drush command to generate redirects for a given node, for testing:
<?php
namespace Drupal\<your_module>\Commands;
use Drupal\redirect\Entity\Redirect;
use Drush\Commands\DrushCommands;
/**
* A drush command file.
*
* @package Drupal\<your_module>\Commands
*/
class CreateMassRedirectCommands extends DrushCommands {
/**
* Creates a bunch of redirects.
*
* @command mass-redirects:create
* @aliases mr-c
*/
public function createMassRedirects() {
$target_node = "internal:/node/16";
$iterations = 40000;
for ($i = 0; $i < $iterations; $i++) {
$random_src = $this->generateRandomRedirectSrc();
$redirect = Redirect::create(
[
'redirect_source' => $random_src,
'redirect_redirect' => $target_node,
'language' => 'und',
'status_code' => '301',
]
)->save();
if ($redirect) {
$this->output()->writeln("Redirect created from $random_src to $target_node.");
}
else {
$this->output()->writeln("Redirect failed to create for some reason.");
}
}
}
private function generateRandomRedirectSrc() {
$unique = uniqid("autoredirect-");
$random = rand(0,10000);
return $unique . '/' . $random;
}
}
Proposed resolution
If there are more than 10 redirects for a given node, provide a link to the actual redirects configuration page at /admin/config/search/redirect.
Remaining tasks
- Confirm/validate issue on vanilla install
- Add more logic in the form_alter to limit results if there are many
- Add more logic in the form_alter to not run if URL Redirects field is not present on form display
User interface changes
API changes
Data model changes