Problem/Motivation
When a taxonomy vocabulary contains too many terms, it is absolutely impossible to reach some pages like taxonomy overview, even if their content is paginated, because of huge memory usage.
Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 20480 bytes) in /var/www/html/web/core/lib/Drupal/Core/Cache/DatabaseBackend.php on line 167
This is caused by the taxonomy_get_tree function for huge taxonomies, but for medium sized taxonomies, this is also caused by the fact that we never clean unused variables.
For information, with a 72000 terms taxonomy (what is not so many for big websites) :
$start = memory_get_usage(TRUE);
$tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vid); // 72000 terms
$used_memory = memory_get_usage(TRUE) - $start;
echo $used_memory; // ~ 242 MB
Proposed resolution
Unset loadTree result when it is not needed anymore. This will not save the life of huge websites, but this will definetely help medium ones to stay usables.
Refactor the implementation to use less memory in general and especially with pagination.
Remaining tasks
Post the patch
User interface changes
None
API changes
None
Beta phase evaluation
| Issue category | Bug because it can cause fatal errors on very common features |
|---|---|
| Issue priority | Major because that's a bug |
| Prioritized changes | The main goal of this issue is performance. (By clearing that unused variables we can free up a lot of memory) |
| Disruption | Not disruptive for core, contributed and custom modules because the variables are cleared while neither the core or a hook uses them. |
| Comment | File | Size | Author |
|---|---|---|---|
| #94 | 2024-12-12_09-23-17.png | 35.44 KB | robgreeniowa |
Issue fork drupal-2183565
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
Comment #1
duaelfrHere is the patch.
I hope it will help someone.
This patch is part of the #1day1patch initiative.
Comment #2
marcingy commentedYou can work around this with modules such as taxonomy manager. Downgrading to normal.
Comment #3
duaelfrFirst, we are talking about D8 so we cannot rely on contrib modules that have not been upgraded for the moment.
Second, it is a Core issue. People would not understand why it is happening and why we can only say them to look for contrib modules which will lower their website performances to give them a core feature that is not working.
Third, this patch is really simple and a core maintainer would only need 20 minutes to review.
Finally, the official documentation about issues' priority tends to say that this IS major.
Comment #4
xjm1: drupal-8.x-reduce_taxonomy_memory_consumption-2183565-1.patch queued for re-testing.
Comment #6
jibranComment #7
jcnventuraRe-rolling. Interdiff didn't make sense as most files have different paths now.
Comment #8
jcnventuraComment #9
duaelfrThank you for reviving this issue and rerolling that patch.
Comment #10
jcnventuraDiscovered a few more cases where the results of the loadTree can be cleaned. I think the results of this patch are probably only useful for those using the forum module and several thousand taxonomy terms.
Comment #11
lauriiiComment #12
duaelfrAdded beta evaluation.
@lauriii I'd need some help to profile this issue. Do you have some resources about profiling D8 or can you guide me through that? I've already used xhprof a bit but I'm not really comfortable with it.
Comment #13
lauriiiThere is quite an extensive documentation available about profiling here: https://www.drupal.org/contributor-tasks/profiling . If you have any further questions please feel free to ping me on IRC! :)
Comment #16
duaelfrStarting the reroll now.
Comment #17
duaelfrDone!
Comment #20
melvinlouwerse commentedI rerolled the patch so it applies to 8.2.x-dev so i will add it here. For me however, i still have memory issues even after this patch.
Comment #21
duaelfrThank you!
It's a minor improvement, sadly we can't do magic here unless we make a deep and hard refactor.
Comment #24
dillix commentedI think we need to reroll patch for 8.4.x-dev, then I can test on vocabulary with 40k terms.
Comment #25
melvinlouwerse commentedAdding reroll for 8.4.x-dev, doubt it will work for a vocabulary with 40k items but is good to try.
Comment #26
mstef commentedStill exhausting 256MB on a vocabulary overview page containing 15000 terms using patch from #25.
Comment #28
marvil07 commentedA re-roll to follow core changes.
Comment #29
steven.wichers commentedThese unset calls aren't likely to help anyone much. PHP is already going to free that memory when the $tree variable de-scopes. The unset will help in the scenario where you are just at the edge of your memory limit and the next thing within the same function that defined the $tree variable pushes you over.
On a site with 10714 terms
Without the patch:
Total Incl. MemUse (bytes): 235,988,560 bytes
Total Incl. PeakMemUse (bytes): 242,675,400 bytes
With the patch:
Total Incl. MemUse (bytes): 236,111,760 bytes
Total Incl. PeakMemUse (bytes): 242,864,088 bytes
Comment #31
toprak commentedPatches are still not useful. I think there is no solution yet.
Does anyone have a different approach?
Comment #34
schnydszch commentedHi! I'm on Drupal 8.8.1 Php version 7.3 Mariadb 5.5.5-10.0.38-MariaDB-0ubuntu0.16.04.1 Apache web server. I imported 23,855 taxonomy terms. It seems I'm getting same error. I applied the patches in comment #28 and #25 it seems the files in the patch are not the same with Drupal installation.
Below is what I'm getting when applying the patch:
git apply -v 2183565-28.patch
Checking patch core/modules/forum/src/Form/ForumForm.php...
Hunk #1 succeeded at 144 (offset 1 line).
Hunk #2 succeeded at 156 (offset 1 line).
Checking patch core/modules/forum/src/ForumManager.php...
Hunk #1 succeeded at 438 (offset 22 lines).
Checking patch core/modules/node/tests/src/Functional/NodeAccessPagerTest.php...
error: while searching for:
$tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vid, 0, 1);
$tid = reset($tree)->tid;
$this->assertTrue($tid, 'General discussion term is found in the forum vocabulary.');
// Create 30 nodes.
for ($i = 0; $i < 30; $i++) {
error: patch failed: core/modules/node/tests/src/Functional/NodeAccessPagerTest.php:77
error: core/modules/node/tests/src/Functional/NodeAccessPagerTest.php: patch does not apply
Checking patch core/modules/taxonomy/src/Form/OverviewTerms.php...
error: while searching for:
$current_page[$key] = $term;
} while (isset($tree[++$tree_index]));
// Because we didn't use a pager query, set the necessary pager variables.
$total_entries = $before_entries + $page_entries + $after_entries;
$pager_total_items[0] = $total_entries;
error: patch failed: core/modules/taxonomy/src/Form/OverviewTerms.php:200
error: core/modules/taxonomy/src/Form/OverviewTerms.php: patch does not apply
Checking patch core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php...
error: while searching for:
foreach ($terms as $term) {
$options[$vocabulary->id()][$term->id()] = str_repeat('-', $term->depth) . Html::escape($this->entityManager->getTranslationFromContext($term)->label());
}
}
}
}
error: patch failed: core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php:65
error: core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php: patch does not apply
Checking patch core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php...
error: while searching for:
$choice->option = [$term->id() => str_repeat('-', $term->depth) . \Drupal::entityManager()->getTranslationFromContext($term)->label()];
$options[] = $choice;
}
}
}
else {
error: patch failed: core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php:185
error: core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php: patch does not apply
Checking patch core/modules/taxonomy/src/TermForm.php...
Hunk #1 succeeded at 50 (offset 2 lines).
Hunk #2 succeeded at 65 (offset 2 lines).
Checking patch core/modules/taxonomy/src/Tests/TermTest.php...
error: core/modules/taxonomy/src/Tests/TermTest.php: No such file or directory
Checking patch core/modules/taxonomy/taxonomy.module...
error: while searching for:
$hierarchy = VocabularyInterface::HIERARCHY_SINGLE;
}
}
if ($hierarchy != $vocabulary->getHierarchy()) {
$vocabulary->setHierarchy($hierarchy);
$vocabulary->save();
error: patch failed: core/modules/taxonomy/taxonomy.module:193
error: core/modules/taxonomy/taxonomy.module: patch does not apply
Checking patch core/modules/taxonomy/tests/src/Kernel/TermKernelTest.php...
Hunk #1 succeeded at 109 (offset -4 lines).
Hunk #2 succeeded at 123 (offset -4 lines).
Thanks in advance!
Comment #35
super_romeo commentedSame for D9.2
Loads all entities of vocabulary (no pager value used), but why?
Comment #36
super_romeo commentedComment #37
sweetchuckPatch #28 can not be applied to core 9.2.7 and to the latest 9.2.x (1a5cc0d9ab68d8c7b22143c4642a691365171cd1 2021-10-25 23:15:55 +0100). Which makes sense because patch #28 was created for 8.5.x.
Comment #38
yogeshmpawarComment #39
sweetchuckThanks for the reroll.
I think the
unset()not going to solve this memory problem. PHP's garbage collector does a better job than we can do manually. I think the unset also pointless in tests files where there is only a few amount of terms (less than 10).I have a vocabulary with >34000 terms without hierarchy and I can't view the
/admin/structure/taxonomy/manage/my_voc_01/overviewpage with memory limit 512MB.The attached patch solves the problem, but I am not sure if this is the right direction.
However the original code also uses expressions like these:
$term->depthand$term->parentsComment #42
grndlvl commentedNot sure why the "PHP 7.3 & MySQL 5.7" failed. I retested using PHP 7.4and it passed fine.
I agree with Sweetchuck I don't see unset adding much benefit to the patch. This appears to lower the load in manual testing.
Comment #43
g_miric commentedThis creates other sort issues:
1. Create a vocabulary that has parent/child terms.
- If you go to the overview page you will not be able to see the tree just the flat list
- If you try to update the tree and save it (on the overview page), you will get a bunch of notices.
Comment #45
grndlvl commentedRe-roll to address issues in 43
Comment #46
cesarmiquel commentedI can confirm the patch #45 solves the issue. I just applied this patch on our dev instance of a large newspaper site and it works great! Kudos! Not only does the page load (right now we are not able to look at the taxonomy overview page because we used to run out of memory) but it loads and works super fast. This was tested on the taxonomy overview for a large site with over 56000 tags. +1 on this.
Comment #47
cesarmiquel commentedTested the patch #45 o newer Drupal (the previous test was done on a Drupal 8.6.18. Surprisingly the patch applied cleanly!). I tested now on a Drupal 9.3.12 and also worked fine. It was running out of memory with PHP 7.4 and 256M of memory limit. The taxonomy had around 39000 tags. With the latest patch it worked perfectly. Also tested on much smaller taxonomies with terms of depth 2 and it worked fine. Probably a someone should test on something with deeper taxonomies. The ones I have are large but have depth = 1. Still looking very promising in my opinion. Great work everybody!
Comment #48
super_romeo commentedPatch #45 does not apply on D9.4.x.
Comment #49
cesarmiquel commentedHey @super_romeo! I created a patch manually since its just a couple of lines. Please test it. It should apply cleanly to D9.4.x.
Comment #50
g_miric commentedWorks fine with D9.4.x
Also the issue reported in #43 is fixed too.
Comment #51
betoaveigaThis is not a patch to reduce memory consumption, but it is closely related and may fix the issue for some.
For this patch to work, you need to set the memory per vocabulary on your settings.php file.
Comment #52
catchRetitling since this is changing the taxonomy overview controller, not the ::loadTree() method itself.
The patch in #49 looks good, however we should add an inline comment explaining that we're avoiding loading all terms, and that the ones shown on the current page get loaded later.
Comment #53
tinny commentedThis patch doesn't work for me with 4000 child terms on a single parent (502 error).
Comment #54
sivaji_ganesh_jojodae commentedThe issue still seems to exist in D10 as well. Attached is patch #49 from branch 10.1.x.
Comment #56
drugan commentedThis is an extension of the #73 patch which fixes memory leak error not only on the terms overview page but also on a term edit page.
It allows to reduce number of loaded terms by reflecting pager "page" parameter into the sql query
range().Pager still works the same with one exception for the "Last" button which moves by 10 pages forward while clicking on it.
If you like me have an insane number of terms / pager pages then the "page" parameter in the browser address bar can be edited directly to get you to the very end of the pager.
Comment #58
grevil commentedWe just ran into this issue migrating a Drupal 7 website with a large taxonomy to Drupal 10. So this is still important to fix, as it breaks large sites. Reading the comments above, I think we still need a plan on how to fix this deep underlying issue, before we invest more time in code here?
At least with pagination, this should not break:
drush config:set taxonomy.settings terms_per_page_admin 100But a written above, changing the page size has no effect on this error.
Is any of the above approaches at least an improvement from the Core maintainers perspective? Who could have a look?
Comment #59
grevil commentedComment #60
cesarmiquel commentedNot sure why this issue derailed. The patch in #45 worked for me in several tests/sites with large taxonomies and had the blessing by Catch. All that remained was to add a comment. In all honesty I didn't understand exactly what the comment was but I believe once that comment is added this was RTBC.
There is also a version for 10.x. in #54. I recommend you test that version and leave a comment if it works.
I don't recommend the solutions that try to increase the memory size as suggested as they will eventually break. The proposed solution worked for me on two large sites I tested.
Comment #61
hongqing commentedPatch #54 works well with the latest Drupal 10.0.9. Thanks.
Comment #65
plopescCreated MR and patch against 11.x based on #49and including suggestions by catch in #52
Comment #66
smustgrave commentedAs a bug will need a test case showing the issue please
Thanks!
Comment #67
plopescThank you for the heads-up smustgrave.
Attaching new version of the patch including tests and improving the load terms logic for the submit handelr as well.
Comment #69
plopescNew patch version fixing typo.
Comment #71
smustgrave commentedGiving credit to bbrala for pointing me out to a good way for doing a profile using ddev and phpstorm.
Using devel generate created 1000 terms and went to the taxonomy overview page of my vocabulary
Attaching before/after
Before

After

So seeing a slight faster render.
Comment #73
golubovicm commentedPatch 2183565-69.patch is not working for me. Still getting:
Fatal error: Allowed memory size of 805306368 bytes exhausted (tried to allocate 4096 bytes) in /var/www/web/modules/contrib/taxonomy_menu/src/TaxonomyMenuHelper.php on line 104
Fatal error: Allowed memory size of 805306368 bytes exhausted (tried to allocate 24576 bytes) in Unknown on line 0
Could it be because I'm also using Taxonomy menu module?
Update: yes, when disabling TM it works well.
Comment #74
amavi commentedHi :)
Since 9.4 all my taxo more big 2000 is block, I put this patch and works now:
https://www.drupal.org/project/drupal/issues/3327118#comment-15252799
But, all big content is still block: Error Vanish cache server.
I can not make Sitemap too for big content, Drupal 9.4 break all web site... :(
Comment #75
poker10 commentedMoving back to RTBC (per #71) - there was a random failure.
Re #73: I have tested the patch #69 and is working for us on a test site. Taxonomy with 2000+ terms: without the patch - out of memory, after patch is applied: the page loaded without any problems.
Comment #76
catchI think it would be worth building the list of term IDs in advance, and then using ::loadMultiple() here.
Similar in the next hunk of the MR too.
Otherwise looks good though.
Comment #77
plopescGood point.
Added new version of the patch storing in an index all the entities and their weight to load all of them in a single call afterwards.
Comment #78
plopescAdd missing interdiff.
Comment #79
poker10 commentedThere are no test failures in the patch #77, but I am curious how is the while loop supposed to work now, as the
$raw_termvariable is not being changed at all?Comment #80
plopescGood catch!
That part of the code is only affected if we are loading a page that's not the first and root terms weight is not starting by 0.
Added extra tests to cover that case.
While working on this, I found out there are no tests to ensure that overview terms form submit callback behaves as expected. Should they be added here or maybe better to open a follow up issue for that?
Thanks!
Comment #81
plopescFix typo in comment.
Comment #82
smustgrave commentedFailed asserting that actual size 11 matches expected size 5.Ran new tests to make sure they failed and clearly they did.
Seems feedback has been updated.
Comment #83
alexpottI don't think we should be save the term objects to state.
I think this could be
The first state->set() is not required.
I've attached a patch that does the above. It also adjusted things so that we use loadMultiple() in the buildForm - this is very similar to what @catch requested in for the submit in #76
Comment #84
alexpottAh actually we already have a list of tids to load :) no need to built yet-another-list-of-tids...
Comment #85
alexpottUpdated the MR with all the changes from the issue after the last commit to it. Hiding all the files.
Comment #86
smustgrave commentedSeems you addressed your own feedback :)
Comment #87
poker10 commentedThis is not a deep performance benchmark, but I can confirm that the MR !4110 is working on few our sites with thousands of taxonomy terms. Before changes from MR were applied, there was an OOM error on the taxonomy overview page (
admin/structure/taxonomy/manage/vocabulary/overview). After applying the changes, the page is working again.I think a more detailed benchmark could help for this to be committed (for example memory usage on that page before/after, etc..), as there is a "needs profiling" tag, but otherwise I think this works pretty well.
Comment #88
catchI think it should be possible to write a performance test for this using #3352851: Allow assertions on the number of database queries run during tests - I wouldn't block it on that, this issue has existing for many years longer than that capability, but I'll open a spin-off and try to do a before/after comparison if I can and then we can commit the test coverage in its own issue.
Comment #91
catchI opened #3410092: Performance tests for the taxonomy overview page and wrote the test, then merged the branch here in and ran the test again - and as I was writing the test coverage, realised we multiple load taxonomy terms in taxonomy get tree, which will also multiple get the cache items, and multiple set the cache items too. Given that, we can't detect the performance improvement here with PerformanceTestBase and the test run after the merged branch confirmed that - we're exchanging a massive database query for a smaller one, and nothing runs per-term.
Bit of a shame that the tests can't detect it, but also if it had detected a difference, there'd be something wrong with multiple loading, it the test would have least have probably detected the multiple load regression fixed in #84 though so worth adding anyway
Committed/pushed to 11x and cherry-picked to 10.2.x, thanks!
Comment #92
giorgio79 commentedPS this issue impacts not just the taxonomy overview page, but taxonomy edit, batch update etc etc. (using Drupal 10.2.0 and php 8.1)
I have a large location based taxonomy with continents -> countries -> cities and unable to make edits, bulk update url aliases due to 500 timeout errors as Drupal is trying to load all terms...
Comment #94
robgreeniowaMoved to https://www.drupal.org/project/drupal/issues/3493621