Problem/Motivation

The JSON:API module is creating a lot of URLs for entities and their fields for different actions like GET, POST, PATCH, PUT. For example the JSON:API module is generated 219 routes after fresh installation using "standard" profile and the total count of non admin routes is 319.

Our project has difficult structure and we are using a lot of different entities and some content types have about 60 fields. As a result the JSON:API module is generating 3225 routes. All this routes are non admin routes and cached by the Drupal\Core\Routing\RoutePreloader::onFinishedRoutes() first and by the Drupal\Core\Routing\RouteProvider::preLoadRoutes() on kernel.request event (routes are cached as serialized objects and the size of cached item is about 50mb and about 80% of routes are generated by the JSON:API). This routes are not used during page render and there are no reasons to store them in the cache. As a result the memory dedicated for the cache is used in not effective way because it contains about 80% of unused data.

Proposed resolution

Previous resolution in #3

1. Provide an alter to allow manage preloadable routes
2. Implement alter to exclude the URLs generated by the JSON:API module from the preload cache
3. Make it configurable.

Current resolution

  • Change logic in \Drupal\Core\Routing\RoutePreloader::onAlterRoutes() to only preload routes that are for HTML and support GET

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

zestagio created an issue. See original summary.

zestagio’s picture

Title: RoutePreloader: prevent preloading for URLs generated by JSON:API » RoutePreloader: prevent preloading of routes generated by JSON:API
zestagio’s picture

zestagio’s picture

zestagio’s picture

Issue summary: View changes
zestagio’s picture

Issue summary: View changes
charginghawk’s picture

Wow, perfect timing, we just ran into this issue, and it was adding 15000+ routes, which could mean a ~232 MB cache object that's not just expensive to generate, but could also crowd out valuable cache items and cause poor performance elsewhere. Thanks for the patch!

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

alexpott’s picture

Version: 8.9.x-dev » 9.1.x-dev
Category: Task » Bug report
FileSize
1.64 KB

Here's a different approach which makes the RoutePreloader obey it's own documentation. It says:

 * On an actual site we want to avoid too many database queries so we build a
 * list of all routes which most likely appear on the actual site, which are all
 * HTML routes not starting with "/admin".

So let's only pre load HTML routes. Because they are the ones that can appear as links... and then we can also use the _admin_route option that's added by \Drupal\system\EventSubscriber\AdminRouteSubscriber() automatically and also is in some routing definitions. Also we can check if GET is in the allowed methods because linking to a post route in a menu doesn't really work.

I think this is a bug.

alexpott’s picture

Here's a test.

The last submitted patch, 9: 3120301-9.patch, failed testing. View results

alexpott’s picture

Issue summary: View changes

Done some additional testing with #10...

Standard with patch preloaded route count: 76
Standard preloaded route count: 98

Routes no longer preloaded on Standard:

     2 => "block_content.add_page",
     3 => "block_content.add_form",
     4 => "entity.block_content.canonical",
     5 => "entity.block_content.edit_form",
     6 => "entity.block_content.delete_form",
     31 => "node.add_page",
     32 => "node.add",
     34 => "entity.node.version_history",
     36 => "node.revision_revert_confirm",
     37 => "node.revision_revert_translation_confirm",
     38 => "node.revision_delete_confirm",
     48 => "shortcut.set_switch",
     64 => "system.batch_page.html",
     65 => "system.batch_page.json",
     69 => "entity.taxonomy_term.edit_form",
     70 => "entity.taxonomy_term.delete_form",
     75 => "user.pass.http",
     78 => "user.login.http",
     79 => "user.login_status.http",
     80 => "user.logout.http",
     92 => "entity.node.delete_form",
     93 => "entity.node.edit_form",
     95 => "entity.user.edit_form",
     96 => "entity.user.cancel_form",

Routes now preloaded that weren't before:

     1 => "block.admin_demo",
     44 => "system.php",

Therefore I think preloading block.admin_demo is wrong and potentially it is way more useful to pre node.add as many users can have that route so I'm going to revert the _admin_route logic checking and leave the GET and HTML format check in as that solves the most pressing problem at hand - namely all the JSON API routes ending up in this list.

Routes no longer preloaded on Standard with JSON API:

     2 => "block_content.add_page",
     3 => "block_content.add_form",
     4 => "entity.block_content.canonical",
     5 => "entity.block_content.edit_form",
     6 => "entity.block_content.delete_form",
     31 => "jsonapi.block--block.collection",
     32 => "jsonapi.block--block.individual",
     33 => "jsonapi.block_content--basic.collection",
     34 => "jsonapi.block_content--basic.collection.post",
     35 => "jsonapi.block_content--basic.individual",
     36 => "jsonapi.block_content--basic.individual.patch",
     37 => "jsonapi.block_content--basic.individual.delete",
     38 => "jsonapi.block_content--basic.block_content_type.relationship.get",
     39 => "jsonapi.block_content--basic.block_content_type.relationship.post",
     40 => "jsonapi.block_content--basic.block_content_type.relationship.patch",
     41 => "jsonapi.block_content--basic.block_content_type.relationship.delete",
     42 => "jsonapi.block_content--basic.block_content_type.related",
     43 => "jsonapi.block_content--basic.revision_user.relationship.get",
     44 => "jsonapi.block_content--basic.revision_user.relationship.post",
     45 => "jsonapi.block_content--basic.revision_user.relationship.patch",
     46 => "jsonapi.block_content--basic.revision_user.relationship.delete",
     47 => "jsonapi.block_content--basic.revision_user.related",
     48 => "jsonapi.block_content_type--block_content_type.collection",
     49 => "jsonapi.block_content_type--block_content_type.individual",
     50 => "jsonapi.comment_type--comment_type.collection",
     51 => "jsonapi.comment_type--comment_type.individual",
     52 => "jsonapi.comment--comment.collection",
     53 => "jsonapi.comment--comment.collection.post",
     54 => "jsonapi.comment--comment.individual",
     55 => "jsonapi.comment--comment.individual.patch",
     56 => "jsonapi.comment--comment.individual.delete",
     57 => "jsonapi.comment--comment.comment_type.relationship.get",
     58 => "jsonapi.comment--comment.comment_type.relationship.post",
     59 => "jsonapi.comment--comment.comment_type.relationship.patch",
     60 => "jsonapi.comment--comment.comment_type.relationship.delete",
     61 => "jsonapi.comment--comment.comment_type.related",
     62 => "jsonapi.comment--comment.uid.relationship.get",
     63 => "jsonapi.comment--comment.uid.relationship.post",
     64 => "jsonapi.comment--comment.uid.relationship.patch",
     65 => "jsonapi.comment--comment.uid.relationship.delete",
     66 => "jsonapi.comment--comment.uid.related",
     67 => "jsonapi.comment--comment.pid.relationship.get",
     68 => "jsonapi.comment--comment.pid.relationship.post",
     69 => "jsonapi.comment--comment.pid.relationship.patch",
     70 => "jsonapi.comment--comment.pid.relationship.delete",
     71 => "jsonapi.comment--comment.pid.related",
     72 => "jsonapi.comment--comment.entity_id.relationship.get",
     73 => "jsonapi.comment--comment.entity_id.relationship.post",
     74 => "jsonapi.comment--comment.entity_id.relationship.patch",
     75 => "jsonapi.comment--comment.entity_id.relationship.delete",
     76 => "jsonapi.comment--comment.entity_id.related",
     77 => "jsonapi.contact_message--feedback.collection.post",
     78 => "jsonapi.contact_message--personal.collection.post",
     79 => "jsonapi.contact_form--contact_form.collection",
     80 => "jsonapi.contact_form--contact_form.individual",
     81 => "jsonapi.editor--editor.collection",
     82 => "jsonapi.editor--editor.individual",
     83 => "jsonapi.field_storage_config--field_storage_config.collection",
     84 => "jsonapi.field_storage_config--field_storage_config.individual",
     85 => "jsonapi.field_config--field_config.collection",
     86 => "jsonapi.field_config--field_config.individual",
     87 => "jsonapi.file--file.collection",
     88 => "jsonapi.file--file.collection.post",
     89 => "jsonapi.file--file.individual",
     90 => "jsonapi.file--file.individual.patch",
     91 => "jsonapi.file--file.individual.delete",
     92 => "jsonapi.file--file.uid.relationship.get",
     93 => "jsonapi.file--file.uid.relationship.post",
     94 => "jsonapi.file--file.uid.relationship.patch",
     95 => "jsonapi.file--file.uid.relationship.delete",
     96 => "jsonapi.file--file.uid.related",
     97 => "jsonapi.filter_format--filter_format.collection",
     98 => "jsonapi.filter_format--filter_format.individual",
     99 => "jsonapi.image_style--image_style.collection",
     100 => "jsonapi.image_style--image_style.individual",
     101 => "jsonapi.menu_link_content--menu_link_content.collection",
     102 => "jsonapi.menu_link_content--menu_link_content.collection.post",
     103 => "jsonapi.menu_link_content--menu_link_content.individual",
     104 => "jsonapi.menu_link_content--menu_link_content.individual.patch",
     105 => "jsonapi.menu_link_content--menu_link_content.individual.delete",
     106 => "jsonapi.menu_link_content--menu_link_content.revision_user.relationship.get",
     107 => "jsonapi.menu_link_content--menu_link_content.revision_user.relationship.post",
     108 => "jsonapi.menu_link_content--menu_link_content.revision_user.relationship.patch",
     109 => "jsonapi.menu_link_content--menu_link_content.revision_user.relationship.delete",
     110 => "jsonapi.menu_link_content--menu_link_content.revision_user.related",
     111 => "jsonapi.node_type--node_type.collection",
     112 => "jsonapi.node_type--node_type.individual",
     113 => "jsonapi.node--article.collection",
     114 => "jsonapi.node--article.collection.post",
     115 => "jsonapi.node--article.individual",
     116 => "jsonapi.node--article.individual.patch",
     117 => "jsonapi.node--article.individual.delete",
     118 => "jsonapi.node--article.node_type.relationship.get",
     119 => "jsonapi.node--article.node_type.relationship.post",
     120 => "jsonapi.node--article.node_type.relationship.patch",
     121 => "jsonapi.node--article.node_type.relationship.delete",
     122 => "jsonapi.node--article.node_type.related",
     123 => "jsonapi.node--article.revision_uid.relationship.get",
     124 => "jsonapi.node--article.revision_uid.relationship.post",
     125 => "jsonapi.node--article.revision_uid.relationship.patch",
     126 => "jsonapi.node--article.revision_uid.relationship.delete",
     127 => "jsonapi.node--article.revision_uid.related",
     128 => "jsonapi.node--article.uid.relationship.get",
     129 => "jsonapi.node--article.uid.relationship.post",
     130 => "jsonapi.node--article.uid.relationship.patch",
     131 => "jsonapi.node--article.uid.relationship.delete",
     132 => "jsonapi.node--article.uid.related",
     133 => "jsonapi.node--article.field_image.relationship.get",
     134 => "jsonapi.node--article.field_image.relationship.post",
     135 => "jsonapi.node--article.field_image.relationship.patch",
     136 => "jsonapi.node--article.field_image.relationship.delete",
     137 => "jsonapi.node--article.field_image.related",
     138 => "jsonapi.node--article.field_tags.relationship.get",
     139 => "jsonapi.node--article.field_tags.relationship.post",
     140 => "jsonapi.node--article.field_tags.relationship.patch",
     141 => "jsonapi.node--article.field_tags.relationship.delete",
     142 => "jsonapi.node--article.field_tags.related",
     143 => "jsonapi.node--page.collection",
     144 => "jsonapi.node--page.collection.post",
     145 => "jsonapi.node--page.individual",
     146 => "jsonapi.node--page.individual.patch",
     147 => "jsonapi.node--page.individual.delete",
     148 => "jsonapi.node--page.node_type.relationship.get",
     149 => "jsonapi.node--page.node_type.relationship.post",
     150 => "jsonapi.node--page.node_type.relationship.patch",
     151 => "jsonapi.node--page.node_type.relationship.delete",
     152 => "jsonapi.node--page.node_type.related",
     153 => "jsonapi.node--page.revision_uid.relationship.get",
     154 => "jsonapi.node--page.revision_uid.relationship.post",
     155 => "jsonapi.node--page.revision_uid.relationship.patch",
     156 => "jsonapi.node--page.revision_uid.relationship.delete",
     157 => "jsonapi.node--page.revision_uid.related",
     158 => "jsonapi.node--page.uid.relationship.get",
     159 => "jsonapi.node--page.uid.relationship.post",
     160 => "jsonapi.node--page.uid.relationship.patch",
     161 => "jsonapi.node--page.uid.relationship.delete",
     162 => "jsonapi.node--page.uid.related",
     163 => "jsonapi.path_alias--path_alias.collection",
     164 => "jsonapi.path_alias--path_alias.collection.post",
     165 => "jsonapi.path_alias--path_alias.individual",
     166 => "jsonapi.path_alias--path_alias.individual.patch",
     167 => "jsonapi.path_alias--path_alias.individual.delete",
     168 => "jsonapi.rdf_mapping--rdf_mapping.collection",
     169 => "jsonapi.rdf_mapping--rdf_mapping.individual",
     170 => "jsonapi.search_page--search_page.collection",
     171 => "jsonapi.search_page--search_page.individual",
     172 => "jsonapi.shortcut--default.collection",
     173 => "jsonapi.shortcut--default.collection.post",
     174 => "jsonapi.shortcut--default.individual",
     175 => "jsonapi.shortcut--default.individual.patch",
     176 => "jsonapi.shortcut--default.individual.delete",
     177 => "jsonapi.shortcut--default.shortcut_set.relationship.get",
     178 => "jsonapi.shortcut--default.shortcut_set.relationship.post",
     179 => "jsonapi.shortcut--default.shortcut_set.relationship.patch",
     180 => "jsonapi.shortcut--default.shortcut_set.relationship.delete",
     181 => "jsonapi.shortcut--default.shortcut_set.related",
     182 => "jsonapi.shortcut_set--shortcut_set.collection",
     183 => "jsonapi.shortcut_set--shortcut_set.individual",
     184 => "jsonapi.action--action.collection",
     185 => "jsonapi.action--action.individual",
     186 => "jsonapi.menu--menu.collection",
     187 => "jsonapi.menu--menu.individual",
     188 => "jsonapi.taxonomy_term--tags.collection",
     189 => "jsonapi.taxonomy_term--tags.collection.post",
     190 => "jsonapi.taxonomy_term--tags.individual",
     191 => "jsonapi.taxonomy_term--tags.individual.patch",
     192 => "jsonapi.taxonomy_term--tags.individual.delete",
     193 => "jsonapi.taxonomy_term--tags.vid.relationship.get",
     194 => "jsonapi.taxonomy_term--tags.vid.relationship.post",
     195 => "jsonapi.taxonomy_term--tags.vid.relationship.patch",
     196 => "jsonapi.taxonomy_term--tags.vid.relationship.delete",
     197 => "jsonapi.taxonomy_term--tags.vid.related",
     198 => "jsonapi.taxonomy_term--tags.revision_user.relationship.get",
     199 => "jsonapi.taxonomy_term--tags.revision_user.relationship.post",
     200 => "jsonapi.taxonomy_term--tags.revision_user.relationship.patch",
     201 => "jsonapi.taxonomy_term--tags.revision_user.relationship.delete",
     202 => "jsonapi.taxonomy_term--tags.revision_user.related",
     203 => "jsonapi.taxonomy_term--tags.parent.relationship.get",
     204 => "jsonapi.taxonomy_term--tags.parent.relationship.post",
     205 => "jsonapi.taxonomy_term--tags.parent.relationship.patch",
     206 => "jsonapi.taxonomy_term--tags.parent.relationship.delete",
     207 => "jsonapi.taxonomy_term--tags.parent.related",
     208 => "jsonapi.taxonomy_vocabulary--taxonomy_vocabulary.collection",
     209 => "jsonapi.taxonomy_vocabulary--taxonomy_vocabulary.individual",
     210 => "jsonapi.tour--tour.collection",
     211 => "jsonapi.tour--tour.individual",
     212 => "jsonapi.user--user.collection",
     213 => "jsonapi.user--user.collection.post",
     214 => "jsonapi.user--user.individual",
     215 => "jsonapi.user--user.individual.patch",
     216 => "jsonapi.user--user.individual.delete",
     217 => "jsonapi.user--user.roles.relationship.get",
     218 => "jsonapi.user--user.roles.relationship.post",
     219 => "jsonapi.user--user.roles.relationship.patch",
     220 => "jsonapi.user--user.roles.relationship.delete",
     221 => "jsonapi.user--user.roles.related",
     222 => "jsonapi.user--user.user_picture.relationship.get",
     223 => "jsonapi.user--user.user_picture.relationship.post",
     224 => "jsonapi.user--user.user_picture.relationship.patch",
     225 => "jsonapi.user--user.user_picture.relationship.delete",
     226 => "jsonapi.user--user.user_picture.related",
     227 => "jsonapi.user_role--user_role.collection",
     228 => "jsonapi.user_role--user_role.individual",
     229 => "jsonapi.view--view.collection",
     230 => "jsonapi.view--view.individual",
     231 => "jsonapi.base_field_override--base_field_override.collection",
     232 => "jsonapi.base_field_override--base_field_override.individual",
     233 => "jsonapi.date_format--date_format.collection",
     234 => "jsonapi.date_format--date_format.individual",
     235 => "jsonapi.entity_form_display--entity_form_display.collection",
     236 => "jsonapi.entity_form_display--entity_form_display.individual",
     237 => "jsonapi.entity_view_mode--entity_view_mode.collection",
     238 => "jsonapi.entity_view_mode--entity_view_mode.individual",
     239 => "jsonapi.entity_view_display--entity_view_display.collection",
     240 => "jsonapi.entity_view_display--entity_view_display.individual",
     241 => "jsonapi.entity_form_mode--entity_form_mode.collection",
     242 => "jsonapi.entity_form_mode--entity_form_mode.individual",
     243 => "jsonapi.resource_list",
     244 => "jsonapi.node--article.file_upload.new_resource",
     245 => "jsonapi.node--article.file_upload.existing_resource",
     246 => "jsonapi.user--user.file_upload.new_resource",
     247 => "jsonapi.user--user.file_upload.existing_resource",
     248 => "node.add_page",
     249 => "node.add",
     251 => "entity.node.version_history",
     253 => "node.revision_revert_confirm",
     254 => "node.revision_revert_translation_confirm",
     255 => "node.revision_delete_confirm",
     265 => "shortcut.set_switch",
     281 => "system.batch_page.html",
     282 => "system.batch_page.json",
     286 => "entity.taxonomy_term.edit_form",
     287 => "entity.taxonomy_term.delete_form",
     292 => "user.pass.http",
     295 => "user.login.http",
     296 => "user.login_status.http",
     297 => "user.logout.http",
     309 => "entity.node.delete_form",
     310 => "entity.node.edit_form",
     312 => "entity.user.edit_form",
     313 => "entity.user.cancel_form",
alexpott’s picture

Now with the patch attached... on standard and JSON API installed we have 93 routes in state

No new routes have stored.
And the routes no longer stored are:

     31 => "jsonapi.block--block.collection",
     32 => "jsonapi.block--block.individual",
     33 => "jsonapi.block_content--basic.collection",
     34 => "jsonapi.block_content--basic.collection.post",
     35 => "jsonapi.block_content--basic.individual",
     36 => "jsonapi.block_content--basic.individual.patch",
     37 => "jsonapi.block_content--basic.individual.delete",
     38 => "jsonapi.block_content--basic.block_content_type.relationship.get",
     39 => "jsonapi.block_content--basic.block_content_type.relationship.post",
     40 => "jsonapi.block_content--basic.block_content_type.relationship.patch",
     41 => "jsonapi.block_content--basic.block_content_type.relationship.delete",
     42 => "jsonapi.block_content--basic.block_content_type.related",
     43 => "jsonapi.block_content--basic.revision_user.relationship.get",
     44 => "jsonapi.block_content--basic.revision_user.relationship.post",
     45 => "jsonapi.block_content--basic.revision_user.relationship.patch",
     46 => "jsonapi.block_content--basic.revision_user.relationship.delete",
     47 => "jsonapi.block_content--basic.revision_user.related",
     48 => "jsonapi.block_content_type--block_content_type.collection",
     49 => "jsonapi.block_content_type--block_content_type.individual",
     50 => "jsonapi.comment_type--comment_type.collection",
     51 => "jsonapi.comment_type--comment_type.individual",
     52 => "jsonapi.comment--comment.collection",
     53 => "jsonapi.comment--comment.collection.post",
     54 => "jsonapi.comment--comment.individual",
     55 => "jsonapi.comment--comment.individual.patch",
     56 => "jsonapi.comment--comment.individual.delete",
     57 => "jsonapi.comment--comment.comment_type.relationship.get",
     58 => "jsonapi.comment--comment.comment_type.relationship.post",
     59 => "jsonapi.comment--comment.comment_type.relationship.patch",
     60 => "jsonapi.comment--comment.comment_type.relationship.delete",
     61 => "jsonapi.comment--comment.comment_type.related",
     62 => "jsonapi.comment--comment.uid.relationship.get",
     63 => "jsonapi.comment--comment.uid.relationship.post",
     64 => "jsonapi.comment--comment.uid.relationship.patch",
     65 => "jsonapi.comment--comment.uid.relationship.delete",
     66 => "jsonapi.comment--comment.uid.related",
     67 => "jsonapi.comment--comment.pid.relationship.get",
     68 => "jsonapi.comment--comment.pid.relationship.post",
     69 => "jsonapi.comment--comment.pid.relationship.patch",
     70 => "jsonapi.comment--comment.pid.relationship.delete",
     71 => "jsonapi.comment--comment.pid.related",
     72 => "jsonapi.comment--comment.entity_id.relationship.get",
     73 => "jsonapi.comment--comment.entity_id.relationship.post",
     74 => "jsonapi.comment--comment.entity_id.relationship.patch",
     75 => "jsonapi.comment--comment.entity_id.relationship.delete",
     76 => "jsonapi.comment--comment.entity_id.related",
     77 => "jsonapi.contact_message--feedback.collection.post",
     78 => "jsonapi.contact_message--personal.collection.post",
     79 => "jsonapi.contact_form--contact_form.collection",
     80 => "jsonapi.contact_form--contact_form.individual",
     81 => "jsonapi.editor--editor.collection",
     82 => "jsonapi.editor--editor.individual",
     83 => "jsonapi.field_storage_config--field_storage_config.collection",
     84 => "jsonapi.field_storage_config--field_storage_config.individual",
     85 => "jsonapi.field_config--field_config.collection",
     86 => "jsonapi.field_config--field_config.individual",
     87 => "jsonapi.file--file.collection",
     88 => "jsonapi.file--file.collection.post",
     89 => "jsonapi.file--file.individual",
     90 => "jsonapi.file--file.individual.patch",
     91 => "jsonapi.file--file.individual.delete",
     92 => "jsonapi.file--file.uid.relationship.get",
     93 => "jsonapi.file--file.uid.relationship.post",
     94 => "jsonapi.file--file.uid.relationship.patch",
     95 => "jsonapi.file--file.uid.relationship.delete",
     96 => "jsonapi.file--file.uid.related",
     97 => "jsonapi.filter_format--filter_format.collection",
     98 => "jsonapi.filter_format--filter_format.individual",
     99 => "jsonapi.image_style--image_style.collection",
     100 => "jsonapi.image_style--image_style.individual",
     101 => "jsonapi.menu_link_content--menu_link_content.collection",
     102 => "jsonapi.menu_link_content--menu_link_content.collection.post",
     103 => "jsonapi.menu_link_content--menu_link_content.individual",
     104 => "jsonapi.menu_link_content--menu_link_content.individual.patch",
     105 => "jsonapi.menu_link_content--menu_link_content.individual.delete",
     106 => "jsonapi.menu_link_content--menu_link_content.revision_user.relationship.get",
     107 => "jsonapi.menu_link_content--menu_link_content.revision_user.relationship.post",
     108 => "jsonapi.menu_link_content--menu_link_content.revision_user.relationship.patch",
     109 => "jsonapi.menu_link_content--menu_link_content.revision_user.relationship.delete",
     110 => "jsonapi.menu_link_content--menu_link_content.revision_user.related",
     111 => "jsonapi.node_type--node_type.collection",
     112 => "jsonapi.node_type--node_type.individual",
     113 => "jsonapi.node--article.collection",
     114 => "jsonapi.node--article.collection.post",
     115 => "jsonapi.node--article.individual",
     116 => "jsonapi.node--article.individual.patch",
     117 => "jsonapi.node--article.individual.delete",
     118 => "jsonapi.node--article.node_type.relationship.get",
     119 => "jsonapi.node--article.node_type.relationship.post",
     120 => "jsonapi.node--article.node_type.relationship.patch",
     121 => "jsonapi.node--article.node_type.relationship.delete",
     122 => "jsonapi.node--article.node_type.related",
     123 => "jsonapi.node--article.revision_uid.relationship.get",
     124 => "jsonapi.node--article.revision_uid.relationship.post",
     125 => "jsonapi.node--article.revision_uid.relationship.patch",
     126 => "jsonapi.node--article.revision_uid.relationship.delete",
     127 => "jsonapi.node--article.revision_uid.related",
     128 => "jsonapi.node--article.uid.relationship.get",
     129 => "jsonapi.node--article.uid.relationship.post",
     130 => "jsonapi.node--article.uid.relationship.patch",
     131 => "jsonapi.node--article.uid.relationship.delete",
     132 => "jsonapi.node--article.uid.related",
     133 => "jsonapi.node--article.field_image.relationship.get",
     134 => "jsonapi.node--article.field_image.relationship.post",
     135 => "jsonapi.node--article.field_image.relationship.patch",
     136 => "jsonapi.node--article.field_image.relationship.delete",
     137 => "jsonapi.node--article.field_image.related",
     138 => "jsonapi.node--article.field_tags.relationship.get",
     139 => "jsonapi.node--article.field_tags.relationship.post",
     140 => "jsonapi.node--article.field_tags.relationship.patch",
     141 => "jsonapi.node--article.field_tags.relationship.delete",
     142 => "jsonapi.node--article.field_tags.related",
     143 => "jsonapi.node--page.collection",
     144 => "jsonapi.node--page.collection.post",
     145 => "jsonapi.node--page.individual",
     146 => "jsonapi.node--page.individual.patch",
     147 => "jsonapi.node--page.individual.delete",
     148 => "jsonapi.node--page.node_type.relationship.get",
     149 => "jsonapi.node--page.node_type.relationship.post",
     150 => "jsonapi.node--page.node_type.relationship.patch",
     151 => "jsonapi.node--page.node_type.relationship.delete",
     152 => "jsonapi.node--page.node_type.related",
     153 => "jsonapi.node--page.revision_uid.relationship.get",
     154 => "jsonapi.node--page.revision_uid.relationship.post",
     155 => "jsonapi.node--page.revision_uid.relationship.patch",
     156 => "jsonapi.node--page.revision_uid.relationship.delete",
     157 => "jsonapi.node--page.revision_uid.related",
     158 => "jsonapi.node--page.uid.relationship.get",
     159 => "jsonapi.node--page.uid.relationship.post",
     160 => "jsonapi.node--page.uid.relationship.patch",
     161 => "jsonapi.node--page.uid.relationship.delete",
     162 => "jsonapi.node--page.uid.related",
     163 => "jsonapi.path_alias--path_alias.collection",
     164 => "jsonapi.path_alias--path_alias.collection.post",
     165 => "jsonapi.path_alias--path_alias.individual",
     166 => "jsonapi.path_alias--path_alias.individual.patch",
     167 => "jsonapi.path_alias--path_alias.individual.delete",
     168 => "jsonapi.rdf_mapping--rdf_mapping.collection",
     169 => "jsonapi.rdf_mapping--rdf_mapping.individual",
     170 => "jsonapi.search_page--search_page.collection",
     171 => "jsonapi.search_page--search_page.individual",
     172 => "jsonapi.shortcut--default.collection",
     173 => "jsonapi.shortcut--default.collection.post",
     174 => "jsonapi.shortcut--default.individual",
     175 => "jsonapi.shortcut--default.individual.patch",
     176 => "jsonapi.shortcut--default.individual.delete",
     177 => "jsonapi.shortcut--default.shortcut_set.relationship.get",
     178 => "jsonapi.shortcut--default.shortcut_set.relationship.post",
     179 => "jsonapi.shortcut--default.shortcut_set.relationship.patch",
     180 => "jsonapi.shortcut--default.shortcut_set.relationship.delete",
     181 => "jsonapi.shortcut--default.shortcut_set.related",
     182 => "jsonapi.shortcut_set--shortcut_set.collection",
     183 => "jsonapi.shortcut_set--shortcut_set.individual",
     184 => "jsonapi.action--action.collection",
     185 => "jsonapi.action--action.individual",
     186 => "jsonapi.menu--menu.collection",
     187 => "jsonapi.menu--menu.individual",
     188 => "jsonapi.taxonomy_term--tags.collection",
     189 => "jsonapi.taxonomy_term--tags.collection.post",
     190 => "jsonapi.taxonomy_term--tags.individual",
     191 => "jsonapi.taxonomy_term--tags.individual.patch",
     192 => "jsonapi.taxonomy_term--tags.individual.delete",
     193 => "jsonapi.taxonomy_term--tags.vid.relationship.get",
     194 => "jsonapi.taxonomy_term--tags.vid.relationship.post",
     195 => "jsonapi.taxonomy_term--tags.vid.relationship.patch",
     196 => "jsonapi.taxonomy_term--tags.vid.relationship.delete",
     197 => "jsonapi.taxonomy_term--tags.vid.related",
     198 => "jsonapi.taxonomy_term--tags.revision_user.relationship.get",
     199 => "jsonapi.taxonomy_term--tags.revision_user.relationship.post",
     200 => "jsonapi.taxonomy_term--tags.revision_user.relationship.patch",
     201 => "jsonapi.taxonomy_term--tags.revision_user.relationship.delete",
     202 => "jsonapi.taxonomy_term--tags.revision_user.related",
     203 => "jsonapi.taxonomy_term--tags.parent.relationship.get",
     204 => "jsonapi.taxonomy_term--tags.parent.relationship.post",
     205 => "jsonapi.taxonomy_term--tags.parent.relationship.patch",
     206 => "jsonapi.taxonomy_term--tags.parent.relationship.delete",
     207 => "jsonapi.taxonomy_term--tags.parent.related",
     208 => "jsonapi.taxonomy_vocabulary--taxonomy_vocabulary.collection",
     209 => "jsonapi.taxonomy_vocabulary--taxonomy_vocabulary.individual",
     210 => "jsonapi.tour--tour.collection",
     211 => "jsonapi.tour--tour.individual",
     212 => "jsonapi.user--user.collection",
     213 => "jsonapi.user--user.collection.post",
     214 => "jsonapi.user--user.individual",
     215 => "jsonapi.user--user.individual.patch",
     216 => "jsonapi.user--user.individual.delete",
     217 => "jsonapi.user--user.roles.relationship.get",
     218 => "jsonapi.user--user.roles.relationship.post",
     219 => "jsonapi.user--user.roles.relationship.patch",
     220 => "jsonapi.user--user.roles.relationship.delete",
     221 => "jsonapi.user--user.roles.related",
     222 => "jsonapi.user--user.user_picture.relationship.get",
     223 => "jsonapi.user--user.user_picture.relationship.post",
     224 => "jsonapi.user--user.user_picture.relationship.patch",
     225 => "jsonapi.user--user.user_picture.relationship.delete",
     226 => "jsonapi.user--user.user_picture.related",
     227 => "jsonapi.user_role--user_role.collection",
     228 => "jsonapi.user_role--user_role.individual",
     229 => "jsonapi.view--view.collection",
     230 => "jsonapi.view--view.individual",
     231 => "jsonapi.base_field_override--base_field_override.collection",
     232 => "jsonapi.base_field_override--base_field_override.individual",
     233 => "jsonapi.date_format--date_format.collection",
     234 => "jsonapi.date_format--date_format.individual",
     235 => "jsonapi.entity_form_display--entity_form_display.collection",
     236 => "jsonapi.entity_form_display--entity_form_display.individual",
     237 => "jsonapi.entity_view_mode--entity_view_mode.collection",
     238 => "jsonapi.entity_view_mode--entity_view_mode.individual",
     239 => "jsonapi.entity_view_display--entity_view_display.collection",
     240 => "jsonapi.entity_view_display--entity_view_display.individual",
     241 => "jsonapi.entity_form_mode--entity_form_mode.collection",
     242 => "jsonapi.entity_form_mode--entity_form_mode.individual",
     243 => "jsonapi.resource_list",
     244 => "jsonapi.node--article.file_upload.new_resource",
     245 => "jsonapi.node--article.file_upload.existing_resource",
     246 => "jsonapi.user--user.file_upload.new_resource",
     247 => "jsonapi.user--user.file_upload.existing_resource",
     282 => "system.batch_page.json",
     292 => "user.pass.http",
     295 => "user.login.http",
     296 => "user.login_status.http",
     297 => "user.logout.http",
alexpott’s picture

Forgot to add the patch to #13 lol.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

routes are cached as serialized objects and the size of cached item is about 50mb and about 80% of routes are generated by the JSON:API).

🤯

  1. +++ b/core/lib/Drupal/Core/Routing/RoutePreloader.php
    @@ -98,7 +99,7 @@ public function onRequest(KernelEvent $event) {
    -      if (strpos($route->getPath(), '/admin/') !== 0 && $route->getPath() != '/admin') {
    +      if (strpos($route->getPath(), '/admin/') !== 0 && $route->getPath() != '/admin' && static::isGetAndHtmlRoute($route)) {
    

    This seems very reasonable 👍😊

  2. +++ b/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php
    @@ -108,14 +108,29 @@ public function testOnAlterRoutesWithNonAdminRoutes() {
    +    // Non HTML routes, like json_api routes should be ignored.
    

    Übernit: s/Non HTML/Non-HTML/

  3. +++ b/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php
    @@ -108,14 +108,29 @@ public function testOnAlterRoutesWithNonAdminRoutes() {
    +    $route5->setRequirement('_format', 'json_api');
    

    Nit: the format that the JSON:API module uses is not called json_api but api_json.

    (Yes, that is confusing.)

    That being said, it doesn't matter for the test coverage here: the point is that the html format is absent.

  4. +++ b/core/lib/Drupal/Core/Routing/RoutePreloader.php
    @@ -126,4 +127,20 @@ public static function getSubscribedEvents() {
    +    $format = $route->hasRequirement('_format') ? explode('|', $route->getRequirement('_format')) : ['html'];
    

    This could use an @see \Drupal\Core\Routing\RequestFormatRouteFilter::getAvailableFormats() because it technically reimplements (duplicates) that logic.

Wim Leers’s picture

Forgot some critical context to justify RTBC'ing this: this cannot be a performance regression for JSON:API requests because \Drupal\Core\Routing\RoutePreloader::onRequest() does this:

  public function onRequest(KernelEvent $event) {
    // Only preload on normal HTML pages, as they will display menu links.
    if ($this->routeProvider instanceof PreloadableRouteProviderInterface && $event->getRequest()->getRequestFormat() == 'html') {

… if anything, this means JSON:API could do its own route preloading strategy!

alexpott’s picture

Thanks for the review @Wim Leers - I've addressed the nits. Since this is all comment and test changes leaving at rtbc.

  • catch committed a6ef32e on 9.2.x
    Issue #3120301 by alexpott, zestagio, Wim Leers: RoutePreloader: prevent...

  • catch committed 3bd59f9 on 9.1.x
    Issue #3120301 by alexpott, zestagio, Wim Leers: RoutePreloader: prevent...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Very nice find, good to see this RTBC.

Committed a6ef32e and pushed to 9.2.x. Thanks!
Also cherry-picked to 9.1.x

alexpott’s picture

Issue summary: View changes
alexpott’s picture

Version: 9.1.x-dev » 8.9.x-dev
Priority: Normal » Major
Status: Fixed » Reviewed & tested by the community

I think given the amount memory being wasted here if you're using json api we should consider this a major bug and backport to 8.9.x.

  • catch committed f4931ef on 8.9.x
    Issue #3120301 by alexpott, zestagio, Wim Leers: RoutePreloader: prevent...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Yes agreed and the high memory usage definitely makes this major, could even lead to OOM errors. Cherry-picked to 8.9.x.

Wim Leers’s picture

🥳

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.