From 87bf8fb9e98ad3bb4ec7efab774e97641d86f8d4 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Mon, 20 Dec 2021 13:55:03 +0530 Subject: [PATCH] Force right index for post deletion (#19191) During v6 indexing changes, we replaced idx_posts_root_id with idx_posts_root_id_delete_at. This causes MySQL to trigger the index_merge path again with PRIMARY and idx_posts_root_id_delete_at as shown below: ``` mysql> UPDATE Posts SET DeleteAt = 1637998911685, UpdateAt = 1637998911685, Props = JSON_SET(Props, '$.deleteBy', 'buqskqrwmjnhfuqskqrwmjn4ca') Where Id = 'q38uaydtpink5f4wkmcsn8h47o' OR RootId = 'q38uaydtpink5f4wkmcsn8h47o'; Query OK, 9 rows affected (17.29 sec) Rows matched: 10 Changed: 9 Warnings: 0 mysql> EXPLAIN UPDATE Posts SET DeleteAt = 1637998911685, UpdateAt = 1637998911685, Props = JSON_SET(Props, '$.deleteBy', 'buqskqrwmjnhfuqskqrwmjn4ca') Where Id = 'q38uaydtpink5f4wkmcsn8h47o' OR RootId = 'q38uaydtpink5f4wkmcsn8h47o'\G *************************** 1. row *************************** id: 1 select_type: UPDATE table: Posts partitions: NULL type: index_merge possible_keys: PRIMARY,idx_posts_root_id_delete_at key: idx_posts_root_id_delete_at,PRIMARY key_len: 107,106 ref: NULL rows: 9 filtered: 100.00 Extra: Using sort_union(idx_posts_root_id_delete_at,PRIMARY); Using where; Using temporary 1 row in set, 1 warning (0.00 sec) ``` To fix the temporary sort, we order by Id ``` mysql> UPDATE Posts SET DeleteAt = 1637998911686, UpdateAt = 1637998911686, Props = JSON_SET(Props, '$.deleteBy', 'buqskqrwmjnhfuqskqrwmjn4ca') Where Id = 'q38uaydtpink5f4wkmcsn8h47o' OR RootId = 'q38uaydtpink5f4wkmcsn8h47o' ORDER BY Id; Query OK, 9 rows affected (0.01 sec) Rows matched: 9 Changed: 9 Warnings: 0 mysql> EXPLAIN UPDATE Posts SET DeleteAt = 1637998911686, UpdateAt = 1637998911686, Props = JSON_SET(Props, '$.deleteBy', 'buqskqrwmjnhfuqskqrwmjn4ca') Where Id = 'q38uaydtpink5f4wkmcsn8h47o' OR RootId = 'q38uaydtpink5f4wkmcsn8h47o' ORDER BY Id\G *************************** 1. row *************************** id: 1 select_type: UPDATE table: Posts partitions: NULL type: index_merge possible_keys: PRIMARY,idx_posts_root_id_delete_at key: idx_posts_root_id_delete_at,PRIMARY key_len: 107,106 ref: NULL rows: 9 filtered: 100.00 Extra: Using sort_union(idx_posts_root_id_delete_at,PRIMARY); Using where; Using filesort 1 row in set, 1 warning (0.00 sec) ``` Postgres uses Bitmap Heap scan which does a Bitmap OR of the index tuples and _then_ fetches the rows from the heap. This is a much better and sophisticated way. Sadly, MySQL will fetch the rows from the indexes first, and then do an OR, which is why index_merge_intersection is so bad. See: https://developers.mattermost.com/blog/mysql-index-merge/ for more info. ```release-note NONE ``` --- store/sqlstore/post_store.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index a2d9d51bbb..6047613cad 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -702,11 +702,16 @@ func (s *SqlPostStore) Delete(postID string, time int64, deleteByID string) erro Props = jsonb_set(Props, $2, $3) WHERE Id = $4 OR RootId = $4`, time, jsonKeyPath(model.PostPropsDeleteBy), jsonStringVal(deleteByID), postID) } else { + // We use ORDER BY clause for MySQL + // to trigger filesort optimization in the index_merge. + // Without it, MySQL does a temporary sort. + // See: https://dev.mysql.com/doc/refman/8.0/en/order-by-optimization.html#order-by-filesort. _, err = transaction.Exec(`UPDATE Posts SET DeleteAt = ?, UpdateAt = ?, Props = JSON_SET(Props, ?, ?) - Where Id = ? OR RootId = ?`, time, time, "$."+model.PostPropsDeleteBy, deleteByID, postID, postID) + Where Id = ? OR RootId = ? + ORDER BY Id`, time, time, "$."+model.PostPropsDeleteBy, deleteByID, postID, postID) } if err != nil {