When flags are used as part of a views relationship the resulting query performance slows as more and more flag content is introduced. This issue arouse as part of how the message subscribe module uses flagging on content and provides a way to view all flagged content you have subscribed to.

The following is the query and explain output for before and after the attached patch.

EXPLAIN SELECT COUNT(*) AS expression FROM  (SELECT 1 AS expression FROM  node_field_data node_field_data INNER JOIN flagging flagging_node_field_data ON node_field_data.nid = flagging_node_field_data.flagged_entity__target_id_int AND flagging_node_field_data.flag_id = 'subscribe_node' LEFT JOIN flagging flagging_node_field_data_1 ON node_field_data.nid = flagging_node_field_data_1.flagged_entity__target_id_int AND (flagging_node_field_data_1.flag_id = 'email_node' AND flagging_node_field_data_1.uid = '254') WHERE (( (flagging_node_field_data.uid = '254' ) )) subquery;
+----+--------------------+----------------------------+------+----------------------------------------------------------------+--------------------------------+---------+-----------------------------------------
| id | select_type        | table                      | type | possible_keys                                                  | key                            | key_len | ref                                     
+----+--------------------+----------------------------+------+----------------------------------------------------------------+--------------------------------+---------+-----------------------------------------
|  1 | PRIMARY            | <derived2>                 | ALL  | NULL                                                           | NULL                           | NULL    | NULL                                    
|  2 | DERIVED            | flagging_node_field_data   | ref  | flagging_field__uid__target_id                                 | flagging_field__uid__target_id | 4       | const                                   
|  2 | DERIVED            | node_field_data            | ref  | PRIMARY,node__id__default_langcode__langcode,node__status_type | PRIMARY                        | 4       | community_dev.flagging_node_field_data.f
|  2 | DERIVED            | flagging_node_field_data_1 | ref  | flagging_field__uid__target_id                                 | flagging_field__uid__target_id | 4       | const                                   
|  3 | DEPENDENT SUBQUERY | na                         | ref  | PRIMARY                                                        | PRIMARY                        | 4       | community_dev.node_field_data.nid       
+----+--------------------+----------------------------+------+----------------------------------------------------------------+--------------------------------+---------+-----------------------------------------
(END) 


although it is defaulting to an index query times still range from 45-50 seconds for 4800 return values.

After adding an index on uid, flag_id, flagged_entity__target_id_int the average query time is about _.085 seconds_

+----+--------------------+----------------------------+------+-----------------------------------------------------------------------------------+----------------------------------------------------+---------+--
| id | select_type        | table                      | type | possible_keys                                                                     | key                                                | key_len | r
+----+--------------------+----------------------------+------+-----------------------------------------------------------------------------------+----------------------------------------------------+---------+--
|  1 | PRIMARY            | <derived2>                 | ALL  | NULL                                                                              | NULL                                               | NULL    | N
|  2 | DERIVED            | flagging_node_field_data   | ref  | flagging_field__uid__target_id,flagging_flag_id_uid_flagged_entity__target_id_int | flagging_flag_id_uid_flagged_entity__target_id_int | 770     | c
|  2 | DERIVED            | node_field_data            | ref  | PRIMARY,node__id__default_langcode__langcode,node__status_type                    | PRIMARY                                            | 4       | c
|  2 | DERIVED            | flagging_node_field_data_1 | ref  | flagging_field__uid__target_id,flagging_flag_id_uid_flagged_entity__target_id_int | flagging_flag_id_uid_flagged_entity__target_id_int | 775     | c
|  3 | DEPENDENT SUBQUERY | na                         | ref  | PRIMARY                                                                           | PRIMARY                                            | 4       | c
+----+--------------------+----------------------------+------+-----------------------------------------------------------------------------------+----------------------------------------------------+---------+--

The performance for the selection of data mimics this performance gain with query times around .11-.13 seconds.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

ndrake86 created an issue. See original summary.

ndrake86’s picture

Status: Active » Needs review
FileSize
1.48 KB

Attaching patch.

Status: Needs review » Needs work

The last submitted patch, 2: flagging_views_index-2888664-2.patch, failed testing. View results

ndrake86’s picture

Realized this patch really doesn't make sense until https://www.drupal.org/node/2678756 lands since there is a schema change.

ndrake86’s picture

Uploading a patch that will work once https://www.drupal.org/node/2678756 gets committed. There is still a valid index that can be applied before 2678756 which is basically the same just using but just uses the integer entity ID field but for my use case adding flagging for content views is important.

socketwench’s picture

Thank you for both patches! Right now I'm not sure if the config entity patch will get in, so I'm inclined to get the first patch in and add the indexing to the patch in the config entity issue.

ndrake86’s picture

Definitely understand. Let me do a cleaner patch that doesn't take into account the content entity stuff.

ndrake86’s picture

Status: Needs work » Needs review
FileSize
1.26 KB

Attached is a patch for the current dev branch, not just for the content entity flagging.

Status: Needs review » Needs work

The last submitted patch, 8: flagging_views_index-2888664-8.patch, failed testing. View results

martin107’s picture

I am not an expert in this area,

so I want to talk this through ... I suspect different parts of this will be obvious to each reader and if we put our heads together then we can make progress.. I will do any legs work regarding research and patch making.

Drupal\Tests\flag\FunctionalJavascript\LinkTypeAjaxTest::testAjaxLink
Drupal\Core\Entity\EntityStorageException: Exception thrown while performing a schema update. MySQL needs the 'uid, flag_id, entity_id' field specification in order to normalize the 'flagging_field__uid__flag_id__entity_id' index

Do I read that correctly ....

In addition to our definition of the "flag config entity" in flag.schema we also need to add definitions in flag.install flag_schema()?

I will keep this at needs review but as a note to myself when I come back to make the next patch we currently use array() when [] is preferred.

     $schema['flagging']['indexes'] += array(
       'entity_id__uid' => array('entity_id', 'uid'),
+      'flagging_field__uid__flag_id__entity_id' => array('uid, flag_id, entity_id'),
     );
sylus’s picture

I'm curious about the above as well as have been unable to workaround this.

sylus’s picture

Status: Needs work » Needs review
FileSize
1.42 KB

I needed a quick workaround as was unsure how to solve this via /src/Entity/Storage/FlaggingStorageSchema.php. I just leveraged hook_install + hook_update_n.

Status: Needs review » Needs work

The last submitted patch, 12: new_index_for_flagging-2888664-12.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

myke’s picture

(I have a site running on Drupal 7 that had Views involving flags get extremely slow. I added an index to uid and a query that was taking 60 seconds to complete now takes less than .1 seconds. I believe it may be related to this issue in Drupal 8's Flags module. Glad to see it being addressed before I upgrade.)

KlemenDEV’s picture

I can confirm this. We have a website with views counting flag count using aggregation and the performance of these pages is very bad.

dureaghin’s picture

I can confirm this too. We have a flagging table with +500.000 records and it's super slow 2-3 minutes query execution time.

I checked duplicate indexes using "pt-duplicate-key-checker", and I found this:

# ########################################################################
# drupal8.flagging
# ########################################################################

# flagging_field__flag_id__target_id is a left-prefix of flag_id__uid__session_id
# Key definitions:
#   KEY `flagging_field__flag_id__target_id` (`flag_id`),
#   KEY `flag_id__uid__session_id` (`flag_id`,`uid`,`session_id`(191))
# Column types:
#         `flag_id` varchar(32) character set ascii not null comment 'the id of the target entity.'
#         `uid` int(10) unsigned not null comment 'the id of the target entity.'
#         `session_id` varchar(255) default null
# To remove this duplicate index, execute:
ALTER TABLE `drupal8`.`flagging` DROP INDEX `flagging_field__flag_id__target_id`;

Removing this index does not help.

parkout’s picture

I can confirm too. Loading page about 2-3 minutes. With 5000 node + . Why anybody cant find a way to repair it? flag not working for drupal 8 correctly =(

acrollet’s picture

re-roll

parkout’s picture

Did not help.
I have cleared my flagging table. And its start working faster (because table just cleaned) . But loading page with flags takes about 6 seconds. And this page without flags takes about 2 sec.
How to make it work faster?

brunodbo’s picture

Status: Needs work » Needs review
FileSize
3.03 KB

This is a reroll of #8.

Re:10: It looks like $schema->addIndex($table, $name, $fields, $spec) expects the full table specification as the fourth argument (see https://www.drupal.org/project/drupal/issues/2537928). Attached patch adds the full spec, we'll see what testbot says.

Adding this index allowed me to sort a node view on flagging count (43,617 nodes, 145,824 flaggings in my db), as long as the view uses flaggings by the current user in the relationship:

Query build time: 17.38 ms
Query execute time: 35.86 ms
View render time: 74.69 ms

If I configure the flagging relationship to show flaggings by any user (which is what I need), the preview in Views never even gets rendered, and the query just seems to hang (same behaviour as without the patch).

Status: Needs review » Needs work

The last submitted patch, 20: flagging_views_index-2888664-20.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

brunodbo’s picture

After playing around with the view some more, it seems that it's the 'Sort by flagging count' that's causing this to become very slow.

KlemenDEV’s picture

Even views without 'Sort by flagging count' just showing the flagging count as a field can be very slow with large amount of nodes and flags on them.

parkout’s picture

The main problem is the relations of the flag. If you remove this, then everything loads quickly. In node specific page, but without relationship. it works fast there. But on the pages of terms it takes a very long time to load. Loads quickly only if the person is logged in
And i have not sort by flag, and still have performance problem for anonymous users

parkout’s picture

Please help with this problem( Flag module normally works only for authed users.

slogar32’s picture

Status: Needs work » Needs review
FileSize
3.07 KB
650 bytes

Fixed the FlaggingStorageSchema index array definition.

parkout’s picture

It did not help. If there are a lot of nodes, the site slows down a lot. Or the database crashes altogether

sashken2’s picture

I have same problem too if I use Flags as Relations in my views. Site works very slow. If delete Flags from Relations - site works very fast. But I need this relation for 'Sort by flagging count'

sashken2’s picture

Patch #26 works good for new install module Flag (patched #26).
But if I using patch for existing flagging entities. I have error. I can't update flagging entities. I use module https://www.drupal.org/project/entity_update but it don't work.

andyg5000’s picture

I'm having issues with views relationships and the query using the wrong index as well. The query that views generates is taking > 6 seconds to execute. Adding FORCE INDEX (PRIMARY) after the first INNER JOIN of flagging table fixes this. Adding the index in the patch above does not.

I guess I'm going to try https://www.drupal.org/project/views_index_hint, but would like to hear what others do to resolve this!

SELECT 
  users_field_data.uid AS uid, 
  flagging_users_field_data.id AS flagging_users_field_data_id, 
  users_field_data.login AS users_field_data_login_minute \
FROM users_field_data users_field_data 
INNER JOIN 
  flagging user__applicant_status ON users_field_data.uid = user__applicant_status.entity_id 
INNER JOIN 
  flagging flagging_users_field_data ON users_field_data.uid = flagging_users_field_data.entity_id AND flagging_users_field_data.flag_id = 'approved_audit' 
INNER JOIN 
  user__roles user__roles ON users_field_data.uid = user__roles.entity_id AND user__roles.deleted = '0' 
WHERE 
  user__roles.roles_target_id = 'teacher' AND 
  user__applicant_status.entity_type = 'user' AND 
  user__applicant_status.flag_id = 'applicant_active' AND 
  users_field_data.status = '1' AND
  users_field_data.applicant_stage = 'approved'
LIMIT 25 OFFSET 25; 

Associated EXPLAIN

+------+-------------+---------------------------+------+---------------------------------------------------+------------------------------------+---------+--------------------------------------------------+--------+------------------------------------+
| id   | select_type | table                     | type | possible_keys                                     | key                                | key_len | ref                                              | rows   | Extra                              |
+------+-------------+---------------------------+------+---------------------------------------------------+------------------------------------+---------+--------------------------------------------------+--------+------------------------------------+
|    1 | SIMPLE      | flagging_users_field_data | ref  | flagging_field__flag_id__target_id,entity_id__uid | flagging_field__flag_id__target_id | 34      | const                                            | 566    | Using index condition; Using where |
|    1 | SIMPLE      | users_field_data          | ref  | PRIMARY,user__id__default_langcode__langcode      | PRIMARY                            | 4       | epi8_migrate.flagging_users_field_data.entity_id | 1      | Using where                        |
|    1 | SIMPLE      | user__roles               | ref  | PRIMARY,roles_target_id                           | PRIMARY                            | 5       | epi8_migrate.users_field_data.uid,const          | 1      | Using where                        |
|    1 | SIMPLE      | user__applicant_status    | ref  | flagging_field__flag_id__target_id,entity_id__uid | flagging_field__flag_id__target_id | 34      | const                                            | 103978 | Using index condition; Using where |
+------+-------------+---------------------------+------+---------------------------------------------------+------------------------------------+---------+--------------------------------------------------+--------+------------------------------------+
andyg5000’s picture

As a follow up to my last comment, we've ended up converting the schema for `entity_id` in the flagging table to an integer column. We also added an index for flag_id and entity_id.

I realize this may cause issues for others including those wanting to flag config entities that have non-numeric ids. For us, it dropped our query time from 35 seconds to 0.035 ms.

KlemenDEV’s picture

That is very drastic. Maybe this module should add option for core entities that are known to have numeric ids to offer numeric id in flagging table too, some UI parameter maybe?

mvogel’s picture

@andyg5000
Woah thx for this catch. I also changed the type for 'entity_id' to int and query execution time dropped from about 5 seconds to ~0.0xx seconds.

KlemenDEV’s picture

Priority: Normal » Major
Status: Needs review » Needs work
demon326’s picture

Could somebody create a new patch with the latest suggestions and or fixes? The last one is from about a year ago and at least one person reported that is only works on fresh installs.

Where using the flag module on many places and I'd be happy if i could apply a patch that works on existing installs :)

mvogel’s picture

I created a patch, which should be working for new installs and has an update hook (drush updb) that fixes existing installs.

Be aware that changing the column type could have drawbacks if something expects this column to be a string but it is now an integer. I tested it for my projects and it works fine. I think it will be ok for most cases because the entity_id in drupal is always an integer.

demon326’s picture

@mvogel , thanks for taking your time to create a patch. I applied it and it seems that everthing still works as expected. Still getting dupe results, but thats a core bug :)

mediabounds’s picture

This is an updated version of the patch from #26 that correctly updates the entity definition so there is no error in the site status report about outdated field definitions.

(To be clear: this patch merely adds the missing index; it does not change the column type.)

igork96’s picture

I tested the patch from #36 and can confirm that it's working.

pascalim’s picture

An unspoken rule in Drupal is that entity_id columns have always been numeric. Adding more indexes will not help much if those indexes are not getting hit from the datatypes being different. I tested patch #36 and it works fine. Here is an updated patch #36 with the proper patch context.

Status: Needs review » Needs work

The last submitted patch, 41: flagging_views_index-2888664-41.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

huzooka’s picture

scambler’s picture

#41 worked for me, thanks.

jackfoust’s picture

This is night and day with my Views with flag attached. Is this production ready?