◀ 28.

Djangoの本番で変更が反映されないとき|プロセス・テンプレート・静的配信の確認

▶

Djangoの本番で変更が見えないときは、まず「Pythonの応答」「テンプレート」「CSS/画像等の静的ファイル」のどれが古いかを分けます。原因の頻度を決めつけず、配布物・読込元・実行プロセス・配信経路の順に確認します。

配布したファイル、Python実行プロセス、テンプレートと静的配信、ブラウザCDNを別々に確認する図

古いもの先に照合すること初手にしないこと
ビュー・Python設定配布版、実行サービスの読込先・環境、worker世代全サーバー再起動、DEBUG公開、全キャッシュ削除
HTMLの一部ビューの入力値、選択されたテンプレートとloader、応答キャッシュPythonやCSSの問題と即断する
CSS・JS・画像HTMLに載るURL、探索元、収集/配信先、実際の応答collectstaticだけで配信完了とする
人や経路によって違う認証/言語/tenant/キャッシュキー、LB配下の配布版、経路差連打で全workerを確認済みにする

DjangoとWSGIの元の説明を訂正する

DjangoのDEBUG=Falseそのものがコードをキャッシュする、という説明は正確ではありません。Pythonプロセスに読み込まれたモジュール、テンプレートloader、アプリの応答キャッシュ、ブラウザ/CDNは別の仕組みです。

NginxやApache全体の再起動を初手にはしません。実際にDjangoを実行するプロセスと読込元、配布版を確認し、対象サービスの配備手順に従って必要な更新だけを行います。プロキシだけを再起動しても別のPythonプロセスが読み直すとは限りません。

1. 配布物と、実際に動いている版を分ける

対象URL、発生時刻、変更したファイル、期待した結果、観測した結果をそろえます。デプロイの成功ログと公開応答の確認は別です。稼働コンテナ/サービスが参照するリリース、作業ディレクトリ、virtualenv、Python、DJANGO_SETTINGS_MODULE、URLconfを運用側で照合します。手元や別のシェルでバージョンを表示しても、稼働workerの実行環境を証明したことにはなりません。

GitのHEADが新しくても、未配布の生成物、別ディレクトリ/volume、古いimage、別サービスへ向いたルーティングがあり得ます。ビルド時の固定release IDや配布済みファイルのdigestを、既存の管理用記録と照合します。リクエストごとにgitやshellを実行してホスト名/パス/環境変数を公開する診断URLは追加しません。

読取例:既知の配布ファイルのdigest

fingerprint.pyは管理者が指定した変更されない配布ファイルを読み、SHA-256とbytesだけを返します。fingerprint(configured_path)として利用し、存在しなければ例外にします。ディスク上の一致を確認する道具で、稼働中のモジュールやすべての配布物が一致する保証ではありません。書換え中のファイルに対するsnapshot機能もなく、URL入力をファイルパスへ渡すWeb機能として公開しません。.envや秘密鍵の確認例ではありません。

"""Read a configured artifact file, not application settings or environment."""
import hashlib
from pathlib import Path


def fingerprint(path):
    digest = hashlib.sha256()
    size = 0
    with Path(path).open("rb") as source:
        for block in iter(lambda: source.read(65536), b""):
            digest.update(block)
            size += len(block)
    return {"sha256": digest.hexdigest(), "bytes": size}

2. PythonプロセスとWebサーバーを区別する

Pythonはimportしたモジュールをプロセス内に保持します。ディスクのファイル更新やimportlib.invalidate_caches()だけで、既存オブジェクトやfrom ... importで保持した参照がすべて更新されるわけではありません。importlib.reload()を本番サービス全体の配備手段にしません。

mod_wsgiはembedded/daemonモードで再読込みの範囲が異なります。daemonモードではWSGIエントリ変更によるprocess groupの再起動を利用できる構成がありますが、WSGIScriptReloading等の条件を確認します。embeddedではエントリだけの再読込みと、他のPythonモジュールの更新は別です。「wsgi.pyに触ればどの構成でも全部直る」とは扱いません。

GunicornのHUPは設定とworkerを更新しますが、preload_appでmaster側に読込済みのアプリは同じ扱いではありません。service managerのreloadが何を実行するかもunit/設定で異なります。uWSGIやASGIも採用サーバーの手順に従います。知らないPIDへのsignalや全サービスrestartを実行せず、対象アプリ、待機/失敗/旧新worker混在、ヘルス確認と戻し方を確認してから配備します。gracefulという名称だけで無停止を保証しません。

Djangoのrunserverは開発用です。自動再読込みは稼働する本番WSGI/ASGIサーバーの挙動とは別で、DEBUG=Trueを公開して調査したり、本番でrunserverへ切り替えたりしません。

3. テンプレートは探索先とloaderを確認する

編集したファイルが本当に選ばれるテンプレートかを確認します。TEMPLATESのDIRS、APP_DIRS/アプリ順、同じ名前のファイル、継承先を照合します。既存の管理用診断や隔離環境でTemplate.originを確認できますが、本番ページへ内部パスを出しません。

cached.LoaderはコンパイルしたTemplateをメモリへ保持します。Django5.2ではOPTIONSのloaders未指定時にも自動で使われます。DEBUGだけで判断せず、実効loaderとサーバーの再読込方式を見ます。アプリのcache_page等により完成HTMLが別に保持される場合もあり、テンプレートファイルだけの問題とは限りません。

隔離例:同じEngineと新しいEngineを比べる

template_probe.pyは新しい一時ディレクトリにだけ架空のA/Bを作ります。同じEngineではAを保持し、新しいEngineではBを読むことを確認する教材です。既存テンプレートを変更せず、このEngineを本体へ導入しません。新しいEngineをリクエストごとに作る対処やloaderの強制resetを推奨する例でもありません。

"""Only modifies a new temporary fixture, never an existing project."""
from pathlib import Path
from tempfile import TemporaryDirectory

from django.template import Context, Engine


def demonstrate():
    with TemporaryDirectory(prefix="cwiki-template-probe-") as directory:
        path = Path(directory) / "sample.html"
        path.write_text("release-A", encoding="utf-8")

        def engine():
            return Engine(dirs=[directory], loaders=[(
                "django.template.loaders.cached.Loader",
                ["django.template.loaders.filesystem.Loader"],
            )])

        old = engine()
        before = old.get_template("sample.html").render(Context({}))
        path.write_text("release-B", encoding="utf-8")
        same_engine = old.get_template("sample.html").render(Context({}))
        fresh_engine = engine().get_template("sample.html").render(Context({}))
        return before, same_engine, fresh_engine


if __name__ == "__main__":
    print(demonstrate())

専用環境で実行した表示は ('release-A', 'release-A', 'release-B') でした。実サイトのテンプレート再読込を保証する結果ではなく、メモリの保持とディスク更新の違いを示しています。

4. 静的ファイルは探索・収集・配信・URLを別に確認する

CSS/JS/画像だけが古いなら、HTMLが参照する正確なURLと、Network上のstatus/応答ヘッダー/実体を確認します。findstaticは探索元と重複を調べる操作で、配布はしません。同名ファイルが複数あると先に見つかるものが選ばれ、編集したファイルとは違うことがあります。静的ファイルの構成は静的ファイルの置き場所と読み込みも参照してください。

collectstaticはSTORAGESのstaticfilesとSTATIC_ROOT等に従う収集/後処理であり、Nginx/別ホスト/CDN/ストレージへ正しく配信されることまで保証しません。ManifestStaticFilesStorageでは内容ハッシュを含む名前とmanifestを使うため、HTML側も対応するstatic参照を使い、リリース間のmanifest・生成物・配信先をそろえます。元の固定URLや手書きクエリだけを変えても、キャッシュキーや配信設定によっては期待どおりになりません。

古いHTMLが旧ハッシュのファイルを参照する期間も考え、旧生成物を即時一括削除しません。UPLOAD/MEDIAとSTATICの保存先を混同せず、調査目的のcollectstatic --clearや公開ディレクトリの再帰削除は行いません。collectstaticが必要と確認できた変更だけ、既存の配備手順で扱います。

5. ブラウザ・プロキシ・CDN・LBの経路差を確かめる

同じURLと入力で比較し、Cache-Control、Age、ETag、Last-Modified、Vary等とstatusを読みます。Ageの有無や304だけで配信元・最新性を断定しません。ブラウザの再読込みも全CDNやservice workerの保持を消す操作ではありません。no-cacheは再検証を要求する指示で保存禁止ではなく、no-storeとは異なります。既存キャッシュを消す命令としてHTMLのmetaタグを足すことも解決策にしません。

複数台/workerなら、運用記録で新旧releaseや設定/生成物の一致を確認します。ブラウザから何度かアクセスして同じ結果でも、sticky session等で一部へしか届かない場合があり、全台を検証済みとは言えません。Cookie/言語/ログイン状態/tenantの違いで正常に別内容になるケースも分けます。

アプリのRedis等のキャッシュは、環境・alias・キー・version・TTLと生成/失効の仕組みを確認し、影響するキーだけを扱えるか検討します。全消去は他アプリ/セッション/高負荷に影響し得るため初手にしません。CDNのパージも対象と権限・回復時負荷・利用者影響を確認し、全URLパージやAPI実行を切り分け作業に混ぜません。キャッシュの境界はCDN・キャッシュの読み方、本番構成はDjangoの本番運用を参照してください。

反映後と、直らない場合に残す情報

  • 変更対象と期待release/生成物、対象サービスの配布成功を確認する。
  • 代表URLのstatusだけでなく、期待した本文/静的URL/操作結果を確認する。実際の書込み・課金は承認された手順だけで行う。
  • 新旧workerと経路、エラー/待機、利用者別の差を確認し、更新した対象以外も影響していないか確かめる。
  • 問題が残るときは時刻・固定のrelease ID・対象URL/ヘッダーの秘密値を伏せた観測を保存する。全環境変数、Cookie、認証値、例外本文を貼らない。
  • 復帰は既存のリリース/生成物とDB変更の互換性に従う。git pullやプロセス再起動だけで全変更が巻き戻るとは扱わない。

検証範囲と公式資料(2026年10月26日)

Python3.12.10/Django5.2.18の新規検体で、既存WSGI processの旧応答と新processの新版、ディスクのdigest、同一/新規template Engine、DEBUG=Trueでも明示cached.Loaderは保持すること、探索順/Template.origin、静的finder重複/manifestの内容ハッシュと旧生成物保持を確認しました。WSGIはwsgirefによるloopback HTTPだけで、実Apache/mod_wsgi/Gunicorn/uWSGI/ASGI・本番サービスreload・CDN/ブラウザ全体・実認証/DB/無停止/本番負荷は未検証です。

公式資料:Python importlib、Djangoとmod_wsgi、mod_wsgi再読込、Gunicorn signals、Gunicorn設定、cached.Loader、staticfiles/manifest、静的配信、Djangoキャッシュ、runserver、HTTPキャッシュ仕様。

子ページ

子ページはありません

同階層のページ
  1. 環境構築とプロジェクト/アプリの作成
  2. MVC(MVT)のそれぞれの使い方と説明
  3. データベースへの接続と操作
  4. Django Administration
  5. git管理
  6. エラー一覧
  7. バージョンの確認方法
  8. ログ出力方法
  9. SQLのログ出力方法
  10. ログのローテート設定
  11. settings.pyの定数にアクセスする方法
  12. 本番環境へのインストールとアプリのデプロイ(apache編)
  13. 本番環境へのインストールとアプリのデプロイ(nginx編)
  14. djangoアプリの本番の開始URLを変更する
  15. 静的(static)ファイルの置き場所と読み込み(画像、css、js )
  16. CSRFトークンをAjaxで使用する方法
  17. ajaxの使用例(POST編)
  18. ファイルのアップロードとファイルの名前
  19. クイックスタート/チュートリアル
  20. ログイン機能
  21. テンプレート側のログイン判定
  22. ビュー側のログイン判定
  23. 管理者ユーザーの作成/判定と管理画面
  24. モデルのjson化とレスポンス
  25. runserverでポートを指定する方法
  26. cronによるバッチ実行
  27. テンプレートで利用する共通のcontextを定義する方法
  28. プログラムが本番サーバーで反映されない場合の対処法
  29. APIの作成
  30. cron用コマンド・ファイルの作成

最近更新/作成されたページ