【Django】古いレコードを定期自動削除する完全ガイド|カスタムコマンド + cron でデータ保持期間を管理

スポンサードリンク



Django · Maintenance Automation · 2026 Edition

Django アプリで「ログテーブル」「履歴テーブル」「監査テーブル」を運用していると、データ量が日々肥大化して DB 容量を圧迫します。手動削除では運用負荷が高く、削除漏れも起きがちです。本記事では Django の カスタムコマンド + cron(or タスクスケジューラ) を使って、保持期間を超えた古いレコードを 定期自動削除 するパイプラインを、ログ出力・エラー時メール通知込みで実装します。所要時間 25 分。

user@sinyblog:~/article 01_motivation.mdなぜ自動削除が必要か — 手動運用の限界

運用フェーズに入った Django アプリでは、以下のような「増え続けるテーブル」が必ず登場します。

  • サーバーのパフォーマンス履歴(CPU / メモリ / ディスク使用率)
  • ユーザー操作のアクセスログ・監査ログ
  • API 呼び出し履歴・エラーログ
  • バッチ処理の実行履歴

これらは 直近のデータだけが価値を持ち、古いものは保持コストの方が高くなる 性質があります。データ保持期間を「過去 6 ヶ月分」のように決めて、超えたものは順次削除するのが定石です。

手動運用は確実に破綻する

「気が向いたときに削除」「忘れた頃に容量警告」は典型的な失敗パターン。週次・月次の自動削除パイプラインを組んでおけば、運用担当者の頭から「DB メンテナンス」の重荷が消えます。

基本的な削除操作の総合ガイドはこちら

「テーブルそのものを削除したい」「全データをリセットしたい」など、削除操作の基本パターンは姉妹記事 【Django】モデル・テーブル・レコードを削除する完全ガイド で解説しています。

user@sinyblog:~/article 02_architecture.md設計の全体像

本記事で構築するパイプラインは、以下のシンプルな構成です。

  1. Django カスタムコマンドpython manage.py <コマンド名> で実行可能なバッチ処理を定義
  2. 削除ロジックfilter(update_time__lte=<N日前>).delete() で日付絞り込み一括削除
  3. ログ出力logging モジュールで実行履歴を日時付きファイルに記録
  4. エラー通知try/except で例外捕捉、smtplib でメール送信
  5. スケジュール実行 — Linux: cron、Windows: タスクスケジューラ で定期起動

user@sinyblog:~/article 03_sample_model.mdサンプルモデル(パフォーマンス履歴テーブル)

本記事の例では、サーバーの CPU / メモリ使用率を時系列で記録する PerfHist モデルを題材にします。過去 180 日(約 6 ヶ月)以前のレコードを定期削除する という想定です。

python— app1/models.py


from django.db import models
from datetime import datetime


class PerfHist(models.Model):
    class Meta:
        verbose_name = 'パフォーマンスデータ履歴情報'
        verbose_name_plural = 'パフォーマンスデータ履歴情報'

    server_name = models.ForeignKey(
        Server, on_delete=models.CASCADE, verbose_name="サーバ名"
    )
    cpu = models.IntegerField(help_text="単位は%", verbose_name="CPU使用率")
    memory = models.IntegerField(help_text="単位は%", verbose_name="メモリ使用率")
    update_time = models.DateTimeField(default=datetime.now, verbose_name="更新日時")

    def __str__(self):
        return str(self.server_name)

update_time フィールドが 削除判定の基準日 になります。

user@sinyblog:~/article 04_filter_delete.md削除ロジックの基本 — filter + lte で日付絞り込み

「N 日前以前のレコードを全削除」は Django ORM の filter() + ルックアップ __lte(less than or equal) + delete() の組み合わせで一発です。

python— 削除コアロジック


from datetime import date, timedelta
from app1.models import PerfHist

# 180 日前の日付を取得
before_180_days = date.today() - timedelta(days=180)

# update_time が 180 日前以前のレコードを全削除
PerfHist.objects.filter(update_time__lte=before_180_days).delete()
ルックアップ 意味
__lt 未満(less than)
__lte 以下(less than or equal) — 本記事はこれを使用
__gt より大きい(greater than)
__gte 以上(greater than or equal)
__range=(d1, d2) 2 つの日付の間
本番投入前に必ず件数確認

初回実行前に必ず .count() で削除対象件数を確認してください。timedelta の単位ミスや基準日の設定ミスで意図しない大量削除を起こす事故が多発します。

python— 安全確認


qs = PerfHist.objects.filter(update_time__lte=before_180_days)
print(f"削除対象: {qs.count()} 件")
# 想定通りなら実行
# qs.delete()

user@sinyblog:~/article 05_custom_command.mdDjango カスタムコマンドの実装

削除ロジックを python manage.py rotate_perfhist のように呼び出せるカスタムコマンドにラップします。

ディレクトリ構成

text— ファイル配置


app1/
├── __init__.py
├── models.py
├── management/                 ← 新規作成
│   ├── __init__.py             ← 空ファイルでOK
│   └── commands/               ← 新規作成
│       ├── __init__.py         ← 空ファイルでOK
│       └── rotate_perfhist.py  ← 本記事のカスタムコマンド

カスタムコマンド本体

python— app1/management/commands/rotate_perfhist.py


# -*- coding: utf-8 -*-
from django.core.management.base import BaseCommand
from app1.models import PerfHist
from datetime import date, datetime, timedelta
from email.mime.text import MIMEText
import smtplib
import sys
import os
import logging

# ===== 初期設定 =====
LOG_DIR = "/var/log/django_rotate/"  # ログ出力先(環境に合わせて変更)
RETENTION_DAYS = 180                 # データ保持期間(日)

# ===== ログファイル設定 =====
date_name = datetime.now().strftime("%Y%m%d-%H%M%S")
file_name = os.path.join(LOG_DIR, f"{date_name}_ROTATE_PERFHIST.log")
logging.basicConfig(
    filename=file_name,
    level=logging.DEBUG,
    format='%(asctime)s %(message)s',
    datefmt='%Y/%m/%d %H:%M:%S',
)


def send_mail(subject, content):
    """異常終了時の通知メール送信"""
    message = MIMEText(content)
    message['Subject'] = subject
    message['From'] = 'noreply@example.com'
    message['To'] = 'admin@example.com'

    with smtplib.SMTP('mail.example.com') as sender:
        sender.sendmail(message['From'], message['To'], message.as_string())


class Command(BaseCommand):
    help = '180 日以前の PerfHist レコードを削除する'

    def handle(self, *args, **options):
        logging.info('[正常] パフォーマンス履歴データのハウスキープ処理を開始')

        try:
            before_n_days = date.today() - timedelta(days=RETENTION_DAYS)
            qs = PerfHist.objects.filter(update_time__lte=before_n_days)
            count = qs.count()
            logging.info(f'[情報] 削除対象件数: {count} 件')

            deleted_count, _ = qs.delete()
            logging.info(f'[正常] {deleted_count} 件のレコードを削除しました')

        except Exception as e:
            logging.exception('[異常] ハウスキープ処理でエラー発生')
            send_mail(
                subject='【異常終了】PerfHist ハウスキープジョブ',
                content=f'PerfHist ハウスキープでエラー発生。\n詳細: {e}\nログファイル: {file_name}',
            )
            sys.exit(1)

        logging.info('[正常] ハウスキープ処理が正常終了しました')

実行方法

bash— 手動実行


cd /path/to/your/project
python manage.py rotate_perfhist

user@sinyblog:~/article 06_scheduling.mdスケジュール実行 — cron / タスクスケジューラ

カスタムコマンドができたので、あとは OS 側のスケジューラから定期起動するだけです。

Linux / macOS — cron

bash— crontab -e で開いて追加


# 毎日 午前 3 時に実行
0 3 * * * cd /path/to/your/project && /path/to/venv/bin/python manage.py rotate_perfhist

# 毎週日曜午前 4 時に実行
0 4 * * 0 cd /path/to/your/project && /path/to/venv/bin/python manage.py rotate_perfhist

Windows — タスクスケジューラ

GUI からの設定が一般的ですが、コマンドラインで一発登録するなら以下:

powershell— 管理者 PowerShell


schtasks /Create /SC DAILY /TN "DjangoRotatePerfhist" /ST 03:00 ^
  /TR "C:\path\to\venv\Scripts\python.exe C:\path\to\project\manage.py rotate_perfhist" ^
  /RL HIGHEST
実行時刻はアクセス少ない時間帯に

大量レコード削除は DB に負荷をかけるので、ユーザーアクセスが少ない深夜〜早朝(午前 3〜5 時)が定石です。

user@sinyblog:~/article 07_operations.md運用 Tips — ログ・通知・トラブル対処

ログファイルの確認

本記事の実装では {日時}_ROTATE_PERFHIST.log 形式でログが出力されます。正常時の例:

log— 20260502-030000_ROTATE_PERFHIST.log


2026/05/02 03:00:00 [正常] パフォーマンス履歴データのハウスキープ処理を開始
2026/05/02 03:00:00 [情報] 削除対象件数: 14523 件
2026/05/02 03:00:01 [正常] 14523 件のレコードを削除しました
2026/05/02 03:00:01 [正常] ハウスキープ処理が正常終了しました

異常時のメール通知

例外発生時は登録メールアドレスに件名「【異常終了】PerfHist ハウスキープジョブ」で通知されます。本文には例外内容とログファイルパスが含まれるので、すぐに調査できます。

よくあるトラブル

症状 原因 対処
cron で実行されるとエラー 環境変数(DJANGO_SETTINGS_MODULE 等)が読まれてない cron に cd /path && /path/venv/bin/python ... 形式でフルパス指定 or source /path/venv/bin/activate
削除に時間がかかりすぎる 対象件数が多すぎて 1 トランザクションで処理しきれない バッチサイズで分割: qs[:10000].delete() をループ
メールが届かない SMTP 認証 / TLS 設定不足 smtplib.SMTP_SSL + login() で認証付きに変更
関連レコードが残ってしまう ForeignKey の on_deleteSET_NULL CASCADE に変更 or 関連テーブル側でも別途削除コマンドを用意

user@sinyblog:~/article 99_summary.mdまとめ

本記事のポイントは 3 つです。

  1. 削除ロジックは filter(update_time__lte=N日前).delete() の 1 行で書ける。本番では .count() で件数確認を必ず挟む
  2. カスタムコマンド + ログ + try/except + メール通知 をセットで作っておけば、運用時の可観測性と安全性が一気に上がる
  3. cron(Linux) or タスクスケジューラ(Windows) で定期起動すれば、運用担当者の手を完全に離れる

ログテーブル・履歴テーブルが肥大化して頭を抱える前に、運用初期からこの仕組みを入れておくのがおすすめです。

本記事は 2020 年 3 月に初版を公開し、2026 年 5 月に sinytech デザインへ移行・cron / タスクスケジューラ設定例の追加・運用 Tips 表を追加しました。コア実装は初版の動作確認済バージョンを踏襲しつつ、現代的な運用シナリオに合わせて補強しています。運営者(現役 IT エンジニア・15 年以上の業界経験)。

おすすめの記事