こんにちは。sinyです。
この記事ではDjangoで古いデータベースレコードを削除する方法をご紹介します。
前提
ここでは、「過去6か月以前の古いレコードを自動削除する」といったケースを想定して説明します。
モデルは以下のような定義とします。
※サーバのパフォーマンスデータ(CPU、メモリ)の履歴情報を格納するテーブルという想定。
※Serverモデルの記載は省略しています。
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="使用率") memory = models.IntegerField(help_text="単位は%",verbose_name="使用率") update_time = models.DateTimeField(default=datetime.now,verbose_name="更新日時") def __str__(self): return str(self.server_name)
上記モデルを対象として、6か月以前の更新日付(update_time)のレコードを対象にデータを削除するという前提で話を進めます。
レコードの削除方法
まず最初にレコードの削除方法ですが、単純にあるテーブルの全レコードを削除したい場合は以下のコマンドでOKです。
<モデルクラス名>.objects.all().delete()
今回はPerfHistクラステーブルのレコードの中で更新日付(update_time)が現在日付から180日以前のレコードのみ削除したいので、以下のようにobjects.filterを利用します。
<モデルクラス名>.objects.filter(update_time__lte=<180日前の日付>).delete()
現在日付は以下のコードで取得します。
import datetime d_today = datetime.date.today() #現在日付の取得 before_180_days = d_today - timedelta(days=180) #180日前の日付を取得
180日以前の日付が取得できたので、以下のコードでPerfHistテーブルからupdate_timeが180日以前のレコードをすべて抽出してdelete()メソッドをつけてレコードを削除します。
PerfHist.objects.filter(update_time__lte=before_180_days).delete() #180前のレコードを抽出して削除
「過去6か月以前の古いレコードを削除する」部分は以上で完了です。
レコードを自動削除する方法
今回は、Djangoのカスタムコマンドを使ってレコードの削除処理を実装します。
また、カスタムコマンドの処理結果はログファイル<年月日>-<時刻>_LOTATE_PERF_HIST_PY.logに書き込み、処理でエラーが発生した場合はメール通知させる機能も実装します。
※Djangoカスタムコマンドの初期設定部分の説明は割愛します。
<アプリケーション>\management\commandsフォルダ配下にlotate_hist.py(ファイル名は何でもOK)を作成し以下のコードを記載します。
# -*- coding:utf-8 -*- from django.core.management.base import BaseCommand from django.core.exceptions import ObjectDoesNotExist from app1.models import PerfHist from datetime import datetime,timedelta from email.mime.text import MIMEText import smtplib import sys import os import logging #初期パラメータ設定 logdir = "<logフォルダのパスを指定>" #現在時刻の取得 date_name = datetime.now().strftime("%Y%m%d-%H%M%S") #ファイル名の生成 file_name = logdir + date_name + "_" + "LOTATE_PERF_HIST_PY.log" logging.basicConfig(filename=file_name,level=logging.DEBUG,format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') def send_mail(subject, content): """異常終了時の処理結果通知メソッド""" message = MIMEText(content) message['Subject'] = subject message['From'] = '<送信元メールアドレス指定>' message['To'] = '<送信先メールアドレスを指定>' sender = smtplib.SMTP('<メールサーバのアドレスを指定>') sender.sendmail(message['From'], message['To'] , message.as_string()) sender.quit() class Command(BaseCommand): """ カスタムコマンド定義 """ def handle(self, *args, **options): # ここに実行したい処理を書く logging.info('[正常]パフォーマンス履歴データのハウスキープ処理を開始。') try: import datetime d_today = datetime.date.today() #現在日付の取得 before_180_days = d_today - timedelta(days=180) #180日前の日付を取得 PerfHist.objects.filter(update_time__lte=before_180_days).delete() #180前のレコードを抽出して削除 except Exception as e: logging.info('[異常]パフォーマンス履歴データのハウスキープ処理でエラーが発生しました。') logging.info('[異常]エラー内容:%s', str(e.args)) logging.exception('Detail: %s', e) logging.info('[異常]パフォーマンス履歴データのハウスキープ処理が異常終了しました。') subject = "【異常終了】パフォーマンス履歴データのハウスキープジョブ" content = 'パフォーマンス履歴データのハウスキープジョブでエラーが発生しました。\n詳細はバッチログファイルを確認してください。' send_mail(subject,content) sys.exit(1) logging.info('[正常]パフォーマンス履歴データのハウスキープ処理が正常終了しました。')
ソースコードの概要は以下の通りです。
ログファイルの定義
以下のコードで、「20200331-103046_LOTATE_PERF_HIST_PY.log」のようなログファイル名を定義しています。
#現在時刻の取得 date_name = datetime.now().strftime("%Y%m%d-%H%M%S") #ファイル名の生成 file_name = logdir + date_name + "_" + "LOTATE_PERF_HIST_PY.log" logging.basicConfig(filename=file_name,level=logging.DEBUG,format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
異常時のメール送信メソッドの定義
処理でエラーが発生した場合にメールで検知できるように以下のコードでメール送信メソッドを定義しています。
def send_mail(subject, content): """異常終了時の処理結果通知メソッド""" message = MIMEText(content) message['Subject'] = subject message['From'] = '<送信元メールアドレス指定>' message['To'] = '<送信先メールアドレスを指定>' sender = smtplib.SMTP('<メールサーバのアドレスを指定>') sender.sendmail(message['From'], message['To'] , message.as_string()) sender.quit()
MIMETextとsmtplibを利用して上記のようなコードを定義するとメールタイトル(subject)とメール内容(content)を指定のアドレスにメール送信することができます。
メイン処理
カスタムコマンドのメイン処理はhandleメソッド内に定義します。
以下の部分がメイン処理です。
logging.info('[正常]パフォーマンス履歴データのハウスキープ処理を開始。') try: import datetime d_today = datetime.date.today() #現在日付の取得 before_180_days = d_today - timedelta(days=180) #180日前の日付を取得 PerfHist.objects.filter(update_time__lte=before_180_days).delete() #180前のレコードを抽出して削除 except Exception as e: logging.info('[異常]パフォーマンス履歴データのハウスキープ処理でエラーが発生しました。') logging.info('[異常]エラー内容:%s', str(e.args)) logging.exception('Detail: %s', e) logging.info('[異常]パフォーマンス履歴データのハウスキープ処理が異常終了しました。') subject = "【異常終了】パフォーマンス履歴データのハウスキープジョブ" content = 'パフォーマンス履歴データのハウスキープジョブでエラーが発生しました。\n詳細はバッチログファイルを確認してください。' send_mail(subject,content) sys.exit(1) logging.info('[正常]パフォーマンス履歴データのハウスキープ処理が正常終了しました。')
既に解説した通り以下のコードで180以前のレコードを削除しています。
DiskHist.objects.filter(update_time__lte=before_180_days).delete()
ログファイルへの書き込みはlogging.info(<ログに書き込みたい内容>で行っています。
また、メイン処理をtry~exceptで囲いexcept内でエラーが発生した場合にエラー内容をログに書き込み、subjectとcontentを設定してsend_mailメソッドの引数に指定してエラーメールを送信します。
最後にsys.exit(1)でカスタムコマンドの処理を異常終了させています。
ちなみに処理が異常終了した場合は以下のようなログが生成されます。
03/31/2020 10:30:47 AM [正常]パフォーマンス履歴データのハウスキープ処理を開始します。 03/31/2020 10:30:47 AM [異常]パフォーマンス履歴データのハウスキープ処理でエラーが発生しました。 03/31/2020 10:30:47 AM [異常]エラー内容:("'method_descriptor' object has no attribute 'today'",) 03/31/2020 10:30:47 AM Detail: 'method_descriptor' object has no attribute 'today' Traceback (most recent call last): File "***********\management\commands\lotate_perf_hist.py", line 48, in handle d_today = datetime.date.today() #現在日付の取得 AttributeError: 'method_descriptor' object has no attribute 'today' 03/31/2020 10:30:47 AM [異常]パフォーマンス履歴データのハウスキープ処理が異常終了しました。
あとは、Djangoのカスタムコマンド「python manage.py lotate_hist」をバッチ化してスケジュール実行すれば古いレコードを定期的に自動削除することができます。
色々なやり方があると思いますが、1つの方法として参考にしていただければ幸いです。
以上、「Djangoで古いレコードを自動削除する方法」でした。