課題:データベースの肥大化と手動タスクの負担
以前、ECシステムを管理していた際、user_activitiesテーブルが大きな悩みの種でした。最初はレイテンシ50ms程度と非常にスムーズに動作していましたが、6ヶ月後、ログが8,000万行を超えデータベースが50GBに達すると、単純なクエリでも3〜5秒かかるようになってしまったのです。
当時の解決策はかなり「力技」でした。毎週月曜の早朝に起き、古いデータをDELETEして整理していたのです。寝坊した週には決まってシステムがDisk Fullエラーを吐いたり、インデックスが肥大化してデータベース全体がハングしたりしました。寝ぼけ眼でWHERE句を書き間違えれば、取り返しのつかない大惨事になりかねません。
なぜ従来の手法は面倒なのか?
自動化を検討する際、多くのエンジニアは以下の2つの方法を思い浮かべます:
- LinuxのCrontabを使用する: PythonやPHPのスクリプトを書き、Cron経由で呼び出す方法です。これは外部への依存関係を生みます。スクリプトを実行するサーバーで接続エラーが発生したり、環境のバージョンが異なったりすると、データベースが関知しないところでタスクが静かに失敗します。
- アプリケーションロジック内で処理する: ユーザーがアクセスするたびにデータチェックのコードを実行する方法です。これは、システム内部の処理のためにユーザーのレスポンスタイムを直接悪化させる、致命的なミスです。
MySQL自体に強力なスケジューラ機能が備わっているのに、なぜ遠回りをする必要があるのでしょうか?
MySQL Event Scheduler — 内部の「Cronジョブ」エンジン
MySQL Event Schedulerは、データベースエンジン内で動作するスケジューラです。SQLやストアドプロシージャを、定期的(毎時、毎日など)または指定した日時に一度だけ実行することができます。
1. 隠れた「武器」を有効化する
デフォルトでは、リソース節約のためにこの機能がオフになっているバージョンがあります。以下のコマンドでステータスを確認してください:
SHOW VARIABLES LIKE 'event_scheduler';
結果が OFF の場合は、すぐに有効化しましょう:
SET GLOBAL event_scheduler = ON;
ヒント: サーバーの再起動後も設定を維持するには、my.cnf または my.ini ファイルに event_scheduler=ON を追記する必要があります。
2. 初めてのイベントを設定する
午前2時に起きてログを掃除する代わりに、アクセスが最も少ない時間帯に自動実行されるようイベントを設定します。
CREATE EVENT clean_old_user_logs
ON SCHEDULE EVERY 1 DAY
STARTS (TIMESTAMP(CURRENT_DATE) + INTERVAL 2 HOUR)
DO
DELETE FROM user_activities
WHERE created_at < NOW() - INTERVAL 30 DAY;
構造は非常に直感的です。ON SCHEDULE で頻度を定義し、DO 内に実行したい処理を記述します。
3. タスクリストの管理
システムが成長するにつれ、バックグラウンドで動作するイベントが増えていきます。これらを適切に管理することで、タスク同士の競合によるリソースの枯渇を防げます。
-- 実行中のすべてのタスクを表示
SHOW EVENTS;
-- システムメンテナンス時にタスクを一時停止
ALTER EVENT clean_old_user_logs DISABLE;
-- 作業完了後に再有効化
ALTER EVENT clean_old_user_logs ENABLE;
実践的なアドバイス:イベントでデータベースをダウンさせないために
50GB規模の本番データベースを扱った経験から言えるのは、決して油断してはいけないということです。巨大な DELETE 文はテーブル全体をロックし、アプリケーションをフリーズさせる可能性があります。
分割処理(Batching)
一度に100万行を削除しようとしてはいけません。MySQLが「息をつく」時間を確保し、他のクエリにリソースを解放できるよう、5,000行ずつのバッチに分割して実行しましょう。SLEEP(1) と組み合わせることで、CPU負荷を抑え、レプリカサーバーの遅延を防ぐことができます。
DELIMITER //
CREATE PROCEDURE proc_safe_cleanup()
BEGIN
REPEAT
DELETE FROM user_activities
WHERE created_at < NOW() - INTERVAL 30 DAY
LIMIT 5000;
UNTIL ROW_COUNT() = 0
END REPEAT;
END //
DELIMITER ;
独自の監視システムの構築
MySQLはイベントが失敗しても自動で通知を送ってくれません。私は通常、各タスクの開始・終了時間とステータスを記録する event_log テーブルを作成します。もしログ掃除タスクの executed_at が24時間以上更新されていなければ、すぐに問題があることが分かります。
Event Schedulerを使うべき時、使うべきでない時
このツールは、以下のような純粋なデータ処理タスクに最適です:
- 不要なデータ、期限切れセッション、古いログのクリーンアップ。
- 一日の終わりの集計レポート計算(Aggregated data)。
- 24時間経過した未支払い注文の自動キャンセル。
ただし、外部APIの呼び出し、メール送信、S3へのファイルアップロードなどが必要な場合は、Queue Worker(Redis/RabbitMQなど)を使用してください。MySQLはデータベース環境外のタスクを処理するようには設計されていません。
Event Schedulerを導入したことで、手動のメンテナンス作業を80%削減できました。データベースは常に安全な容量を維持し、パフォーマンスも安定しています。週末や急激なシステム成長に怯える必要はもうありません。

