django-startup-time について
PostHog のバックエンド開発者向けスキルです。Django の起動時のインポートコストを管理し、遅延インポートによる回帰を防ぐためのガード、測定方法、注意点をまとめています。
主な機能
- 01Django 起動時のインポート予算
- 02重い依存関係の遅延インポート
- 03モデルとレシーバーの登録指針
- 04起動性能の測定方法
- 05回帰を防ぐチェックリスト
活用シーン
- 01バックエンドアプリやレシーバーの追加
- 02インポート予算違反の調査
- 03遅延インポートの影響評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ posthog/ posthog --skill django-startup-timeインストール後、Claude Code で/django-startup-timeを入力して読み込まれたか確認します。
詳細
Django の起動時間
詳細な背景 — 4 つの仕組み(遅延ルーター、モデル登録、レシーバーの配線、起動時の GC ウィンドウ)、リグレッションガード、それらを壊さずにコードを追加する方法、測定方法、および詳細なトラップの解説: docs/internal/django-startup-time.md。詳細の唯一の情報源はそのドキュメントです。このスキルはトリガーとチェックリストです。
ガードは posthog/test/repo_invariants/test_startup_import_budget.py にあります。失敗した場合は、インポートを遅延させてください — 回避するためにエントリを削除しないでください。 逆に、セットアップ時に重大な重量級ライブラリを意図的に遅延させた場合は、FORBIDDEN_AT_SETUP に追加してください(素の django.setup() にそれが含まれていないことを確認したうえで)。プロダクトのファサードやそのコントラクトモジュールをそこに固定しないでください。ファサードはどこからでもインポート可能である必要があるため、代わりに遅延させた重量級モジュールを固定してください。まだ誰も名前を付けていない新しいインポートは test_no_new_heavy_imports_at_setup で捕捉されます。posthog/test/repo_invariants/setup_import_baseline.txt に含まれていないパッケージで、セットアップ時に 100ms 以上かかるものはビルドを失敗させます — それを遅延させてください。ベースライン化するのは、すべてのプロセスがセットアップ時に本当に必要とするものだけにしてください。
バックエンドコードを追加する際のデフォルト
- 新しいアプリ/プロダクト:
models/__init__.pyからモデルを登録し、AppConfig.ready()からレシーバーを配線し、ready()は軽量に保ちます。 - 新しいレシーバー: 所有する
AppConfig.ready()で、専用の軽量モジュール(activity_logging.py、signals.py)から配線してください — たとえ今日は軽量に見えても、API/viewset モジュールを経由しないでください。それらは重量級のインポートを蓄積し、ready()が気付かないうちにそれらを継承してしまいます。 - 新しい重量級の依存関係(ベンダー SDK、Temporal/AI/ClickHouse、pandas/pyarrow/scipy): それを使用するパス上で
# noqa: PLC0415を付けて関数ローカルにインポートし、モジュールスコープでは絶対にインポートしないでください。 - セットアップパス上のモジュールでのスキーマ型: enum は
posthog.schema_enumsから(低コスト)。pydantic モデルはそれらを使用するメソッド内でのみposthog.schemaから。セットアップパス上でモジュールレベルのfrom posthog.tasks...はなし —CeleryQueueはposthog.celery_queuesにあります。 - 新しい viewset/ルート: それはもはやセットアップ時にロードされません — インポートの副作用に依存しないでください。ルートは
rest_router.pyに置き、__init__.pyのシムには置かないでください。 - どこで削るか: セットアップパスのエントリ(
ready()が配線するモジュール、モデルファイル、apps.py)で行い、プロダクトのファサードを消費するモジュールの内部では絶対に行わないでください。ファサードがセットアップに到達する場合、そのエントリはそこに至る実装モジュールを遅延させます。ファサードのインポートはモジュールスコープに留めます。セットアップパスが重量級モジュールから 1 つのシンボルを必要とする場合は、それを軽量モジュールに移動して再エクスポートしてください。 - あらゆる遅延はコストを移転します — 今どのプロセスが、どのパスで支払うのか、そしてそのパスがレイテンシーに敏感かどうかを問いかけてください(バックグラウンドワーカーが遅延して支払う: 問題なし。Web ワーカーが最初のリクエストで支払う: 通常は問題あり)。
コミット前に確認すべき罠
それぞれ 1 行です。詳細な解説と各項目の修正手順はドキュメントに記載されています。
ready()が重い依存関係を起動時に再び引きずり込む — レシーバーのモジュールをインポートすると、そのモジュールレベルのインポートも実行されます。配線後に再測定してください。- レシーバーのサイレントな消失 — 消失したレシーバーはエラーになりません。ルーターを構築しないプロセス(
manage.py shell、celery、migrate)で再現してください。 - モデルがレジストリから消える — viewset のインポート経由でしか到達できないモデルは
makemigrations/admin/mypy を壊します。models/__init__.pyからインポートし、その後dmypy stopを実行してください。 - レイジールーターのマージコンフリクト —
__init__.pyのシムを維持し、master のアグリゲーターの差分をrest_router.pyに移植してください。 - アグリゲーターパッケージの
__init__— PEP 562 の__getattr__でシムし、importlib.import_moduleと__all__ホワイトリストを使用してください(包括的なキャッチオールは兄弟インポートで再帰したりデッドロックしたりします)。 - シリアライザーのフィールド kwargs はインポート時に実行される —
choices=...の入力は関数ローカルに遅延できません。定数をインポートの軽いモジュールに移動してください。 - 消えた再エクスポート — モジュールを再生成/シムすると、他のコードがそこから偶発的にインポートしていた名前(例:
schema.BaseModel)が失われます。相対インポートの表記も含め、古い名前空間の利用者を grep で探してください。 - パッチターゲットは呼び出し時のインポートで壊れる —
modが呼び出し時にhelperをインポートすると、@patch("mod.helper")はインターセプトしなくなります。代わりに定義元のモジュールにパッチを当ててください。 - 不正なマージでのスナップショット再生成 — マージ後のブランチに対して
.ambrを再生成し、それがリグレッションを覆い隠していないことを確認してください。 - 作業ではなくラッパーを測定する — プロセス内で計測してください。pyinstrument ではなく
importtime+tuna、循環と入口の列挙にはgrimpを使用してください。 - どこを遅延すべきか当てずっぽうにする —
importtimeのログに対して hothog を実行してください。その1-cut@列が遅延すべき支配元を示し、--compareが前後の比較を提供します。このスキルと hothog を PR のエージェントコンテキストに記載し、レビュアーがカットの選び方を把握できるようにしてください。 - GC による importtime の幻のコスト —
gc.disable()で再キャプチャしてください。コストが消えれば、その所見はモジュールではなく GC です。エントリーポイントの GC ウィンドウのクローズはfinallyに保ってください。