Hope you're doing fine.
I need a help a bit with this database:
This is a database that stores votes. Users pick the audio tracks they like, and they vote for them. They can vote 'up' or 'down'. Easy as pie. But, when it comes to the calculating stats it gets hairy.
Meta
It's a key-value styled table, that stores the most commonly used stats (just sort-of caching):
mysql> SELECT * FROM Meta;
+-------------+-------+
| Key | Value |
+-------------+-------+
| TRACK_COUNT | 2620 |
| VOTE_COUNT | 3821 |
| USER_COUNT | 371 |
+-------------+-------+
Vote
The vote table holds the vote itself. The only interesting field here is the Type
, value of which means:
0
- App made Vote, user voted for the track using the UI1
- Imported Vote (from external service)2
- Merged Vote. Actually the same as the Imported Vote, but it actually makes a note, that this user is already voted for this track using the external service, and now he's repeating himself using the App.
Track
The track is holds the total stats for itself. Amount of likes, dislikes, likes from external service (LikesRP
), dislikes from external service (DislikesRP
), likes/dislikes adjustments.
App
The app requires to get the votes for:
- 5 most up-voted tracks during the last 7 days
- 5 most down-voted tracks during the last 7 days
- 5 most up-voted tracks during the last 7 days, votes of which were imported from the external service (
Vote.Type = 1
) - 100 most up-voted tracks during the last month
To get the 100 most-up voted track I use this query:
SELECT
T.Hash,
T.Title,
T.Artist,
COALESCE(X.VotesTotal, 0) + T.LikesAdjust as VotesAdjusted
FROM (
SELECT
V.TrackHash,
SUM(V.Vote) AS VotesTotal
FROM
Vote V
WHERE
V.CreatedAt > NOW() - INTERVAL 1 MONTH AND V.Vote = 'up'
GROUP BY
V.TrackHash
ORDER BY
VotesTotal DESC
) X
RIGHT JOIN Track T
ON T.Hash = X.TrackHash
ORDER BY
VotesAdjusted DESC
LIMIT 0, 100;
This query is working OK and it honors the adjustments (client wanted to adjust the track position in lists). Almost the same query is used to get the 5 most up/down voted tracks. And query for task #3 is this:
SELECT
T.Hash,
T.Title,
T.Artist,
COALESCE(X.VotesTotal, 1) as VotesTotal
FROM (
SELECT
V.TrackHash,
SUM(V.Vote) AS VotesTotal
FROM
Vote V
WHERE
V.Type = '1' AND
V.CreatedAt > NOW() - INTERVAL 1 WEEK AND
V.Vote = 'up'
GROUP BY
V.TrackHash
ORDER BY
VotesTotal DESC
) X
RIGHT JOIN Track T
ON T.Hash = X.TrackHash
ORDER BY
VotesTotal DESC
LIMIT 0, 5;
The problem is that the first query is taking about 2 seconds to perform and we have less than 4k votes. By the end of year, this figure will be about 200k votes, which most likely will kill this database. So I'm figuring out how to solve this puzzle.
And now I came down to these questions:
- Did I make the database design wrong? I mean, could it be better?
- Did I make the query wrong?
- Anything else I could improve?
The first thing I did was caching. But, OK, this solves the problem drastically. But I'm curious about SQL-related solution (always leaning towards perfection).
The second thing I had an idea was to put those calculated values to the Meta
table and change them during the voting procedure. But I'm quite short on time just to try it out. Would it be worth it by the way? Or, how do the enterprise class apps solve these problems?
Thanks.
EDIT
I can't believe I forgot to include indices. Here they are:
mysql> SHOW INDEXES IN Vote;
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Vote | 0 | UNIQUE_UserId_TrackHash | 1 | UserId | A | 890 | NULL | NULL | | BTREE | |
| Vote | 0 | UNIQUE_UserId_TrackHash | 2 | TrackHash | A | 4450 | NULL | NULL | | BTREE | |
| Vote | 1 | INDEX_TrackHash | 1 | TrackHash | A | 4450 | NULL | NULL | | BTREE | |
| Vote | 1 | INDEX_CreatedAt | 1 | CreatedAt | A | 1483 | NULL | NULL | | BTREE | |
| Vote | 1 | UserId | 1 | UserId | A | 1483 | NULL | NULL | | BTREE | |
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
mysql> SHOW INDEXES IN Track;
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Track | 0 | PRIMARY | 1 | Hash | A | 2678 | NULL | NULL | | BTREE | |
| Track | 1 | INDEX_Likes | 1 | Likes | A | 66 | NULL | NULL | | BTREE | |
| Track | 1 | INDEX_Dislikes | 1 | Dislikes | A | 27 | NULL | NULL | | BTREE | |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+