szurubooru/scripts/cron-stats.php
2015-06-28 12:26:10 +02:00

41 lines
2.9 KiB
PHP

<?php
require_once(__DIR__
. DIRECTORY_SEPARATOR . '..'
. DIRECTORY_SEPARATOR . 'src'
. DIRECTORY_SEPARATOR . 'Bootstrap.php');
// Why this exists:
// 1. Some entities store a few basic stats in special columns for performance reasons. The benefit of such
// denormalization is vast.
// 2. The maintenance of the stats is implemented using triggers - when users tags a post, tag usage increases.
// 3. This mostly works.
// 4. Meanwhile, in order not to leave any orphans upon row deletions (e.g. have dangling postTags row after specific
// post removal), the database schema uses foreign keys with CASCADE option. This option recursively removes
// everything that would have missing references. This is good.
// 5. Here's the thing: row removals caused by CASCADE foreign key checks don't execute triggers. So if user removes a
// post, then although corresponding postTags entries will get deleted, ON postTags AFTER DELETE trigger will not
// execute, leaving the tags with invalid usage count.
//
// There are three possible solutions to this problem:
// 1. Implement all that logic in the appplication layer. I don't feel like doing this at all, it causes more havoc in
// the code and possibly adds even more holes to the whole denormalization maintenance process.
// 2. Convert CASCADE foreign checks to another set of triggers. This won't work for MySQL because of its limitations:
// >Can't update table 'comments' in stored function/trigger because it is already used by statement which invoked
// >this stored function/trigger
// Creating complex triggers will result very quickly in this error message (I tested it on postTags and posts, it
// did). I strongly believe the reason behind the error above is linked directly into the discussed MySQL's
// limitation.
// 3. Make a scripts like this. This is the easiest option out. The downside is that changes will be seen not
// immediately, but except for heavy tag maintenance, I don't see where such a delay in stat synchronization might
// really hurt.
use Szurubooru\DatabaseConnection;
$databaseConnection = Szurubooru\Injector::get(DatabaseConnection::class);
$pdo = $databaseConnection->getPDO();
$pdo->exec('UPDATE tags SET usages = (SELECT COUNT(1) FROM postTags WHERE tagId = tags.id)');
$pdo->exec('UPDATE posts SET tagCount = (SELECT COUNT(1) FROM postTags WHERE postId = posts.id)');
$pdo->exec('UPDATE posts SET score = (SELECT SUM(score) FROM scores WHERE postId = posts.id)');
$pdo->exec('UPDATE posts SET favCount = (SELECT COUNT(1) FROM favorites WHERE postId = posts.id)');
$pdo->exec('UPDATE posts SET lastFavTime = (SELECT MAX(time) FROM favorites WHERE postId = posts.id)');
$pdo->exec('UPDATE posts SET commentCount = (SELECT COUNT(1) FROM comments WHERE postId = posts.id)');
$pdo->exec('UPDATE posts SET lastCommentTime = (SELECT MAX(lastEditTime) FROM comments WHERE postId = posts.id)');