Is it possible to optimize a query with both JOIN
s and ORDER BY
as long as the ORDER BY
clause only targets columns from the first table?
This is probably better explained with an example:
select
keywords.*
from
keywords
inner join site_keyword on keywords.id = site_keyword.keyword_id and site_keyword.site_id = 1
order by
keywords.volume desc,
keywords.id asc
limit
50 offset 0;
In MemSQL Studio the above query executes in approx. 1 sec (Visual Explain says 130 ms).
If the ORDER BY
clause is skipped the query executes in approx. 75 ms (Visual Explain says 5 ms):
select
keywords.*
from
keywords
inner join site_keyword on keywords.id = site_keyword.keyword_id and site_keyword.site_id = 1
limit
50 offset 0;
We tried with an index like alter table keywords add index (volume desc, id);
but without any notable improvement.
Can anything be done to improve the first query? The keywords
table contains approx. 35M rows while the site_keyword
for this particular query contains approx. 350.000 rows.
The index works great without the JOIN
clause with an incredible speed of approx. 75 ms (Visual Explain says 2 ms).
could it be that it is trying to sort the keywords table before the JOIN?
Can you post an explain?
Maybe try to re-write it like this:
with temp as (
select
keywords.*
from
keywords
inner join site_keyword on keywords.id = site_keyword.keyword_id and site_keyword.site_id = 1
)
select * from temp
order by
temp.volume desc,
temp.id asc
limit
50 offset 0;
@zmeidav thanks for your suggestion. Unfortunately, it seems to slow it down a bit further.
Apparently, it sorts in the end of the execution:
Top offset:0 limit:50
GatherMerge [remote_0.volume DESC, remote_0.id] partitions:all est_rows:50 alias:remote_0
Project [keywords.id, keywords.keyword, keywords.location, keywords.language, keywords.volume, keywords.cpc, keywords.cmp, keywords.trends, keywords.visibility, keywords.indexed_at, keywords.estimated_at, keywords.suggestions_at, keywords.created_at, keywords.updated_at] est_rows:50 est_select_cost:692,630
TopSort limit:[?] [keywords.volume DESC, keywords.id]
NestedLoopJoin
|---IndexSeek laravel.keywords, PRIMARY KEY (id) scan:[id = r0.keyword_id] est_table_rows:35,258,462 est_filtered:35,258,462
TableScan r0 storage:list stream:yes est_table_rows:346,315
Repartition [site_keyword.keyword_id] AS r0 shard_key:[keyword_id] est_rows:346,315
IndexRangeScan laravel.site_keyword, PRIMARY KEY (site_id, keyword_id) scan:[site_id = 1] est_table_rows:7,790,339 est_filtered:346,316
From another look at the profile it seems like the repartition (~120 ms) is the bottleneck when ordering with the joined table. Without the ordering part the repartition only takes ~5 ms. The execution time might not be affected by the ordering at all but instead by the repartition. Without the ordering it can probably just return after fetching the first 50 rows while the ordering has to repartition all data in order to complete the query.
What is the shard key definitions on your tables? I think if you could convert it to local join it will work faster as it will not have to repartition?
The shard key
is currently site_id, keyword_id
(the same as the primary key
).
The keywords
table is sharded by id
.
I guess you’re right a local join would be faster. A reference table
is unfortunately not an option, but perhaps the query can be joined locally by swapping the columns in the shard key
or skipping the site_id
column? It is worth a try.
At least the documentation mentions Local/Collocated Distributed Table Join which is probably what I need.
Try to make the shard key a subset of the joined columns. (Optimizing Table Data Structures)
I’ll post the results after testing the swapped shard key
.
i think if you use shard key just as keyword_id, it will do a local join.
Would be interesting to know how it behaves afterwards.
I just returned from the summer holiday and tested the different shard keys.
With just keyword_id
as the shard key, it performs a local join without repartition as you expected.
By swapping the original shard key into keyword_id, site_id
it stills performs a distributed join.
The drawback of the faster local join is the risk of data skew. It would be great if MemSQL was able to perform a local join with both columns in the shard key as the value of the site_id
column is giving beforehand.
That would be nice, but I don’t see how it’s possible to have it both ways. You can’t distribute your data based on one set of fields and then have local joins based on another set, even if it’s a subset because that subset can still be distributed elsewhere. As you noted, you can use the subset as your shard key instead, which allows you to have local joins with more queries, but then you have to accept the data skew. Unfortunately, that’s just the nature of a distributed database.
1 Like
You’re right, I’m not sure why I thought it would be possible. The whole point is of course to place the data on the same node in order to achieve the local join.
I’ll experiment it bit more with the exact case and decide wether I should go for a local or distributed strategy.
Thanks to you both.
1 Like