Problem/Motivation

The access check looks if the node has more than one revisions, the query that it executes for that is not indexed:


mysql> select count(*) from node_revision;
+----------+
| count(*) |
+----------+
|   172996 |
+----------+

mysql> SELECT sql_no_cache COUNT(*) FROM node_field_revision WHERE nid = N AND default_langcode = 1;
+----------+
| COUNT(*) |
+----------+
|        1 |
+----------+
1 row in set (0.14 sec)

mysql> explain SELECT COUNT(*) FROM node_field_revision WHERE nid = N AND default_langcode = 1;
+----+-------------+---------------------+------+-----------------------+-----------------------+---------+-------+-------+-------------+
| id | select_type | table               | type | possible_keys         | key                   | key_len | ref   | rows  | Extra       |
+----+-------------+---------------------+------+-----------------------+-----------------------+---------+-------+-------+-------------+
|  1 | SIMPLE      | node_field_revision | ref  | node_default_langcode | node_default_langcode | 4       | const | 86881 | Using where |

Proposed resolution

extend the node_default_langcode index to include NID?

mysql> SELECT sql_no_cache COUNT(*) FROM node_field_revision WHERE nid = N AND default_langcode = 1;
+----------+
| COUNT(*) |
+----------+
|        1 |
+----------+
1 row in set (0.00 sec)

mysql> explain SELECT COUNT(*) FROM node_field_revision WHERE nid = N AND default_langcode = 1;
+----+-------------+---------------------+------+-----------------------+-----------------------+---------+-------------+------+-------------+
| id | select_type | table               | type | possible_keys         | key                   | key_len | ref         | rows | Extra       |
+----+-------------+---------------------+------+-----------------------+-----------------------+---------+-------------+------+-------------+
|  1 | SIMPLE      | node_field_revision | ref  | node_default_langcode | node_default_langcode | 8       | const,const |    1 | Using index |

Remaining tasks

User interface changes

API changes

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug because of performance
Issue priority Major because of performance
Prioritized changes The main goal of this issue is performance

Comments

berdir’s picture

Priority: Normal » Major

Raising to major, this query is really slow. Only for users who can actually access revisions, but still. On some sites, that's all, like drupal.org.

I was hoping that #808730: Show the Revisions tab/page even when only one revision exists. would remove the query, but it doesn't look like it.

berdir’s picture

Status: Active » Needs review
StatusFileSize
new565 bytes
new557 bytes

Two patches, one adds a new index, the other ads nid to the existing one.

According to http://dba.stackexchange.com/questions/87556/mysql-multiple-column-index..., the order shouldn't matter too much, so having only one index is probably better?

catch’s picture

Adding to the existing index is good. The default_language index would be completely redundant with the new one anyway.

berdir’s picture

No, it would't be. (default_langcode) would be redundant with (default_langcode, nid) but it is not redundant with (nid, default_langcode).

But yes, I guess extending the existing one is fine, as you said, we save the storage of an additional index.

plach’s picture

Not sure what node__default_langcode is currently buying us, actually. Same goes for node_field__langcode. Would about replacing them with a [nid, default_langcode, langcode] index? I noticed we have no index on nid in node_field_revision, which sounds baad.

berdir’s picture

That works to for me, that would be generic enough to add it by default to all revision/data tables, actually, should we do that?

And doing it generic also means that the NOT NULL additions should be generic...

update.php runs through partially, but seems to fail on those with data. I've seen just adding indexes work, maybe that's because of the NOT NULL change?

The diff between the schema for the node tables is:

$ diff -up before.txt after.txt 
--- before.txt	2015-03-29 15:20:16.936271073 +0200
+++ after.txt	2015-03-29 15:26:36.606140278 +0200
@@ -12,19 +12,19 @@ node_field_data | CREATE TABLE `node_fie
   `sticky` tinyint(4) NOT NULL,
   `default_langcode` tinyint(4) NOT NULL,
   PRIMARY KEY (`nid`,`langcode`),
+  KEY `node__id__langcode` (`nid`,`default_langcode`,`langcode`),
   KEY `node__vid` (`vid`),
   KEY `node_field__type__target_id` (`type`),
   KEY `node_field__langcode` (`langcode`),
   KEY `node_field__uid__target_id` (`uid`),
   KEY `node_field__created` (`created`),
   KEY `node_field__changed` (`changed`),
-  KEY `node__default_langcode` (`default_langcode`),
   KEY `node__frontpage` (`promote`,`status`,`sticky`,`created`),
   KEY `node__status_type` (`status`,`type`,`nid`),
   KEY `node__title_type` (`title`,`type`(4))
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='The data table for node entities.'
 
-node_field_revision | CREATE TABLE `node_field_revision` (
+ node_field_revision | CREATE TABLE `node_field_revision` (
   `nid` int(10) unsigned NOT NULL,
   `vid` int(10) unsigned NOT NULL,
   `langcode` varchar(12) NOT NULL,
@@ -37,9 +37,7 @@ node_field_revision | CREATE TABLE `node
   `sticky` tinyint(4) DEFAULT NULL,
   `default_langcode` tinyint(4) NOT NULL,
   PRIMARY KEY (`vid`,`langcode`),
+  KEY `node__id__langcode` (`nid`,`default_langcode`,`langcode`),
   KEY `node_field__langcode` (`langcode`),
-  KEY `node_field__uid__target_id` (`uid`),
-  KEY `node__default_langcode` (`default_langcode`)
+  KEY `node_field__uid__target_id` (`uid`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='The revision data table for node entities.'

Status: Needs review » Needs work

The last submitted patch, 6: generalize-data-revision-indexes-2261669-6.patch, failed testing.

berdir’s picture

Status: Needs work » Needs review
StatusFileSize
new3.57 KB
new1.21 KB

Using the key....

Status: Needs review » Needs work

The last submitted patch, 8: generalize-data-revision-indexes-2261669-8.patch, failed testing.

berdir’s picture

Status: Needs work » Needs review
StatusFileSize
new8.3 KB
new4.73 KB

Fixing the unit tests.

berdir’s picture

Assigned: Unassigned » plach

A review would be great :)

jhedstrom’s picture

Issue summary: View changes

Added a beta phase evaluation. Reviewing now.

jhedstrom’s picture

This patch works as expected. Not sure if there's anything left to do before RTBC.

plach’s picture

Assigned: plach » Unassigned

In general looks good to me, I'm wondering whether we could get rid of ENTITY_TYPE_field__langcode too.

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -882,7 +882,9 @@ protected function initializeDataTable(ContentEntityTypeInterface $entity_type)
+        $entity_type_id . '__id__langcode' => array($id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')),

Can we name this $entity_type_id . '__id__default__langcode'?

berdir’s picture

I'm not sure what you mean with ENTITY_TYPE_field__langcode. __langcode only exists three times in the code with this patch, and it's all added here for the new index.

Renamed.

wim leers’s picture

Looks sane to me. But I'm no SQL expert, so not RTBC'ing.

plach’s picture

Sorry for losing track of this

I'm not sure what you mean with ENTITY_TYPE_field__langcode.

It's not touched by this patch, but if you have a look to node_field_data you will notice a node_field__langcode index that IMO could be dropped too.

berdir’s picture

Ok, removed that.

I think we have an issue to allow index updates on entity types with data? Somehow that doesnt' seem to quite work. I think it works when it is defined by the field, but not here..

Failed: Drupal\Core\Entity\EntityStorageException: The SQL storage cannot change the schema for an existing entity type with data. in Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema->onEntityTypeUpdate() (line 265 of /home/berdir/Projekte/d8/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php).

Wondering if we should try to fix that first, to make this less of a problem for existing sites. I'll try to dig that issue out.

berdir’s picture

berdir’s picture

Found it :)

#2340993: SqlContentEntityStorageSchema::requiresEntityDataMigration() returns TRUE for cases where it should return FALSE

Works great in combination with this, can switch back and forth without problems.

berdir’s picture

Nice, that's in already!

Which means this issue comes with a working upgrade path :)

plach’s picture

Status: Needs review » Reviewed & tested by the community

Cool :)

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

What query are we adding the langcode to the index for?

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -882,7 +882,9 @@ protected function initializeDataTable(ContentEntityTypeInterface $entity_type)
+        $entity_type_id . '__id__default__langcode' => array($id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')),

@@ -918,7 +920,9 @@ protected function initializeRevisionDataTable(ContentEntityTypeInterface $entit
+        $entity_type_id . '__id__default__langcode' => array($id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')),

For consistency shouldn't this be __id__default_langcode__langcode?

plach’s picture

The new index could cover queries like "give me all nodes whose original language is English". We prioritize the default flag, as we already have the primary key to cover plain language conditions, and we have many queries in core having entity id + default language condition.

For consistency shouldn't this be __id__default_langcode__langcode?

Yep, technically that's more consistent, I suggested the current value as it's more compact and IMO conveys more or less the same information at a first look.

timmillwood’s picture

StatusFileSize
new9.17 KB

Had to manually apply the patch as it was so old so hope I got everything, also updated the suggested key in #23.

Status: Needs review » Needs work

The last submitted patch, 25: slow_query_in-2261669-25.patch, failed testing.

timmillwood’s picture

Status: Needs work » Needs review
StatusFileSize
new0 bytes

Forgot to update the tests to __id__default_langcode__langcode

timmillwood’s picture

StatusFileSize
new9.19 KB

Helps if the patch has some code in it.

timmillwood’s picture

awesome, a passing test.

googletorp’s picture

Status: Needs review » Reviewed & tested by the community

Code looks good. Changes can be applied on a test database with some random content, so all looks good to me.

catch’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +D8 upgrade path, +Needs tests

I think entity storage will handle this automatically, so it shouldn't need a hook_update_N().

However we should be able to test that the new index has been created successfully using db_index_exists() in an update test.

jhedstrom’s picture

Assigned: Unassigned » jhedstrom

I'll write the test.

jhedstrom’s picture

This adds a test. The only downside to adding this is that it will start failing if the test database is ever updated to be *after* this patch is committed. However, the test could simply be removed at that time.

timmillwood’s picture

Status: Needs review » Reviewed & tested by the community

@jhedstrom Awesome! Thanks for the tests.

Looks good to me, and passes.

The failure in Postgres seems unrelated to this patch so assuming Postgres tests are not quite ready yet.

andypost’s picture

+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -906,7 +906,9 @@ protected function initializeDataTable(ContentEntityTypeInterface $entity_type)
+      'indexes' => array(
+        $entity_type_id . '__id__default_langcode__langcode' => array($id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')),

@@ -942,7 +944,9 @@ protected function initializeRevisionDataTable(ContentEntityTypeInterface $entit
+      'indexes' => array(
+        $entity_type_id . '__id__default_langcode__langcode' => array($id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')),

Any reason to make the name so much long?

timmillwood’s picture

@andypost - I was a suggestion from @alexpott in #23 for consistency.

plach’s picture

Status: Reviewed & tested by the community » Needs review

Looks great, but should we check also the removed indexes in the test?

jhedstrom’s picture

StatusFileSize
new1.57 KB
new11.3 KB

This tests for the previous index on node_field_data to be properly removed.

catch’s picture

Status: Needs review » Fixed

Thanks test additions look great.

jhedstrom’s picture

Status: Fixed » Reviewed & tested by the community

I don't think the commit was pushed.

catch’s picture

Sorry I meant to RTBC, not mark fixed...

catch’s picture

Status: Reviewed & tested by the community » Fixed

But since I'm here.

Committed/pushed to 8.0.x, thanks!

  • catch committed a9298fb on 8.0.x
    Issue #2261669 by Berdir, timmillwood, jhedstrom: Slow query in...

Status: Fixed » Closed (fixed)

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