問題の説明:
タグ (tags) は、ジャンクション テーブル (tagged_as) を介して任意のオブジェクトに関連付けることができます。特定のオブジェクト タイプ (specific_object) について、一連のタグに関連付けられたすべてのオブジェクトの結合または共通部分を選択し、オブジェクトの数値列で結果を並べ替え、ページ付けのために結果を制限します。
考案されたスキーマ:
CREATE TABLE tags (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(45) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE specific_object(
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(45) NOT NULL,
vote_sum INT NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);
CREATE TABLE tagged_as(
id INT NOT NULL AUTO_INCREMENT,
tag_id INT NOT NULL,
content_type_id INT NOT NULL,
object_id INT NOT NULL,
PRIMARY KEY (id)
);
この例では、specific_object テーブルの他の多くの列を省略しています。
テーブルの行数:
タグ: 12,297
tagged_as: 46,642,064
特定のオブジェクト: 2,444,944
単純な MySQL ソリューション:
SELECT
specific_object.*
FROM
specific_object
JOIN
tagged_as
ON
specific_object.id = tagged_as.object_id
AND
tagged_as.content_type_id = <SPECIFIC_OBJECT_CONTENT_TYPE_ID>
WHERE
tagged_as.tag_id = <TAG_ONE_ID>
AND
tagged_as.tag_id = <TAG_TWO_ID>
...
ORDER BY specific_object.vote_sum DESC
LIMIT 50
このソリューションの問題は、「行をフェッチするために使用されるキーが ORDER BY で使用されるものと同じではない」ため、MySQL が ORDER BY 句を解決するためにインデックスを利用できないことです ( http://dev.mysql.com /doc/refman/5.0/en/order-by-optimization.html )。実行時間: 20 秒以上
単純な Redis ソリューション:
for each specific object: SET specfic_object:<ID> <ID>
for each tagged as: SADD tag:<TAG ID> specific_object:<ID>
specific_object_ids = SUNION tag:<TAG_ONE_ID> tag:<TAG_TWO_ID> ...
specific_object_ids = SINTER tag:<TAG_ONE_ID> tag:<TAG_TWO_ID> ...
SELECT * FROM specific_object WHERE id IN (<specific_object_ids>) ORDER BY vote_sum DESC
このソリューションの問題は、ORDER BY が MySQL によって実行されなければならないことです。また、タグは、移動する大量のデータである何十万もの特定のオブジェクトに関連付けられる可能性があります。実行時間: 大きなタグの場合は 20 秒以上
まだ試していない可能な解決策
非正規化
おそらく、vote_sum 列を tagged_as テーブルに移動します。結合で順序付けを行う必要がなくなります。これには、素朴な解決策と同じ問題がある可能性があります。
Redis ソートセット
for each specific object: SET specific_object:<ID> <ID>
for each specific object: SET specific_object_weight:<ID> <VOTE_SUM>
for each tagged as: SADD tag:<TAG_ID> specific_object:<ID>
SINTERSTORE result:<timestamp> <TAG_ONE_ID> <TAG_TWO_ID> ...
SORT result:<timestamp> BY specific_object_weight_* LIMIT 0 50
specific_object_ids = SMEMBERS result:<timestamp>
DEL result:<timestamp>
SELECT * FROM specific_object WHERE id IN (<specific_object_ids>)
すべての並べ替えを Redis に移動します。これにより、さらに複雑さが増します。これは、Redis での vote_sum 値も維持する必要があるためです。これが十分に速いかどうかはわかりません。
質問:
考えられる解決策のいずれかが実行可能ですか? 他に役立つソリューションや別のテクノロジーはありますか? この問題を解決するために、私はかなり大きな変更を受け入れる用意があります。