Python学習15日目です!
これまで、HTML変換サービスにいくつかのセキュリティ対策を実装してきました。
今回は、いよいよ本番環境へのデプロイを見据え、レートリミット機能(Flask-Limiter)のデータ保存先を、開発用のメモリからより堅牢な「Redis」へと変更する手順を学んでいきます。
Redisとは
Redis(レディス)は、データをメモリ上に保存することで、非常に高速な読み書きを実現する「インメモリデータベース」です。
その圧倒的なパフォーマンスとシンプルさから、世界中のWebサービスで広く利用されています。
Redisの3つの大きな特徴
- インメモリデータベース
- キー・バリュー型ストア
- 多彩なデータ型
Redisは、データをハードディスクやSSDではなく、コンピューターのメインメモリ(RAM)上に保存します。
メモリはディスクに比べて読み書きの速度が桁違いに速いため、Redisは非常に高いパフォーマンスを発揮します。
これにより、Webサイトの応答速度を劇的に改善したり、リアルタイムでのデータ処理が可能になります。
データはメモリ上に保存されるため、Redisサーバーが停止するとデータは失われてしまいますが、設定によってデータをディスクに書き出して保存(永続化)することも可能です。
Redisのデータ構造は非常にシンプルで、すべてのデータを「キー」と「値(バリュー)」のペアで管理します。
この「キー・バリュー型」という単純な構造が、高速なデータアクセスを可能にしています。
Redisは、単純な文字列だけでなく、リスト(時系列データの管理などに便利)、ハッシュ(オブジェクト情報の保存に最適)、セット(ユニークな値の集合)、ソート済みセット(ランキング機能の実装に威力)など、用途に応じた多彩なデータ型をサポートしています。
これにより、単なるキャッシュとしてだけでなく、メッセージキュー、リアルタイムランキングシステムなど、より高度で多様な用途に活用できます。
Redisがよく使われる場面
- キャッシュ
- セッション管理
- レートリミット
- リアルタイムアプリケーション
一度データベースから読み込んだ時間のかかる処理結果や、WebページのHTML断片などを一時的にRedisに保存しておくことで、2回目以降のアクセスを劇的に高速化します。これはRedisの最も一般的な使われ方です。
ユーザーのログイン情報(セッション)をRedisで管理します。
これにより、複数のWebサーバーでシステムが構成されている場合でも、ユーザーはどのサーバーにアクセスしてもログイン状態を維持できます。
今回のFlask-Limiterのような用途です。特定のIPアドレスやユーザーからのリクエスト回数を高速にカウントし制限をかけます。
オンラインゲームのランキング、リアルタイムチャットのメッセージング、通知システムなど、即時性が求められる機能の実装に使われます。
Redisインストール
公式サイトで推奨されている手順に従い、Redisの公式リポジトリを追加してインストールします。
リポジトリのセットアップ
まず、リポジトリの追加に必要なパッケージをインストールします。
$ sudo apt update $ sudo apt install lsb-release curl gpg
次に、RedisリポジトリのGPGキー(リポジトリが本物であることを保証する署名)をダウンロードし、システムのキーリングに追加します。
$ curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
キーファイルのパーミッションを調整し、APTがリポジトリ情報を記述するファイルを作成します。
$ sudo chmod 644 /usr/share/keyrings/redis-archive-keyring.gpg $ echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
Redisのインストール
リポジトリの準備が整ったら、パッケージリストを更新してRedisをインストールします。
$ sudo apt update $ sudo apt install redis
起動と自動起動設定
Redisを起動し、正常に動作しているかステータスを確認します。
Active: active (running)と緑色で表示されていれば、正常に動作しています。
$ sudo systemctl start redis-server $ sudo systemctl status redis-server ● redis-server.service - Advanced key-value store Loaded: loaded (/usr/lib/systemd/system/redis-server.service; enabled; preset: enabled) Active: active (running) since Tue 2025-09-09 14:04:18 JST; 1 day 21h ago Docs: http://redis.io/documentation, man:redis-server(1) Main PID: 130041 (redis-server) Status: "Ready to accept connections" Tasks: 7 (limit: 9373) Memory: 1.7M (peak: 7.4M swap: 3.8M swap peak: 3.8M) CPU: 23min 24.755s
自動起動設定
正常な起動が確認できたら、サーバー起動時にRedisが自動で立ち上がるように設定しておきます。
$ sudo systemctl enable redis-server
Pythonの開発環境でredisライブラリをインストール
FlaskアプリケーションからRedisに接続するために、Python用のredisライブラリをインストールします。
プロジェクトのディレクトリに移動し、Pythonの仮想環境を有効化してからインストール作業を行います。
$ cd ~/project/html_converter $ source .venv/bin/activate (html_converter) $ uv pip install redis
新しいライブラリを追加したので、プロジェクトの依存関係を記録している「requirements.txt」も忘れずに更新しておきます。
(html_converter) $ uv pip freeze > requirements.txt
Redisへの接続URLを設定
「.env」ファイルにRedisへの接続URLを追記します。
これにより、接続情報をコードに直接書き込むことなく、安全に管理できます。
FLASK_SECRET_KEY='シークレットキー'
REDIS_URL='redis://localhost:6379' # 追加
app.py設定変更
「app.py」を編集し、Flask-Limiterのデータ保存先「storage_uri」を、先ほど「.env」ファイルに設定したRedisの接続先に変更します。
また、環境変数「REDIS_URL」が設定されていない場合にプログラムがエラーで停止するよう、「try...except」ブロックを追加して堅牢性を高めます。
# .envからREDIS_URLを読み込み try: redis_storage_uri = os.environ["REDIS_URL"] except KeyError: # REDIS_URLが設定されていない場合は、エラーメッセージを表示してプログラムを停止させる raise RuntimeError("環境変数 REDIS_URL が設定されていません。.envファイルを確認してください。") # Limiterを初期化しアプリに適用 limiter = Limiter( get_remote_address, app=app, default_limits=["200 per day", "60 per hour"], # storage_uri="memory://" # 変更前 storage_uri=redis_storage_uri # データ保存先をRedisに変更 )
設定反映
「app.py」の変更を反映させるため、systemdで管理しているGunicornのソケットを再起動します。
$ sudo systemctl restart html_converter.socket
Redis連携の動作確認
設定が完了したら、Flask-Limiterが本当にRedisを使ってリクエストをカウントしているかを確認します。
サービスにアクセスしてキーを生成させる
まず、WebブラウザでHTML変換サービスにアクセスし、何回か変換を実行します。これにより、レートリミットのカウント情報がRedisに書き込まれます。
app.pyでは、以下の通り1分間に15回という制限をかけているため、生成されるキーの有効期限は1分です。
@limiter.limit("15 per minute")
そのため、ブラウザで操作してから1分以内に、次の確認作業を行う必要があります。
redis-cliでRedisに接続する
redis-cliコマンドを実行して、Redisの対話モードに入ります。
$ redis-cli 127.0.0.1:6379>
キーの存在を確認する
SCANコマンドで、現在Redisに保存されているキーをスキャンします。
127.0.0.1:6379> SCAN 0 COUNT 20 1) "0" 2) 1) "LIMITS:LIMITER/127.0.0.1/index/15/1/minute"
「LIMITS:LIMITER/...」という形式のキーが見つかれば、Flask-LimiterがRedisにデータを書き込んでいる証拠です。
ちなみに、Flask-Limiterが生成するキーは、以下のような命名規則になっています。
LIMITS:LIMITER/キー/エンドポイント/回数/期間数値/期間単位
今回の「LIMITS:LIMITER/127.0.0.1/index/15/1/minute」の場合、各要素は以下の意味を持ちます。
- LIMITS:LIMITER Flask-Limiterが管理するデータであることを示す接頭辞
- /127.0.0.1 リクエスト元のIPアドレス(レートリミットの対象キー)
- /index 制限対象のエンドポイント(index関数)
- /15/1/minute 「1分あたり15回」という制限ルール
これらのエンドポイントや制限ルールは、「app.py」の以下の部分で定義されています。
@app.route('/', methods=['GET', 'POST'])
@limiter.limit("15 per minute") # 1分間に15回までアクセスを許可
def index():
form = HtmlEscapeForm()
escaped_html = None
キーを詳しく調べるコマンド
「redis-cli」では、キーの存在を確認する以外にも、その内容を詳しく調べるためのコマンドが用意されています。
SCAN: パターンに一致するキーを検索
「MATCH」オプションを使うと、特定のパターンに一致するキーだけを効率的に探し出すことができます。
127.0.0.1:6379> SCAN 0 MATCH "LIMITS:LIMITER/*" COUNT 100 1) "0" 2) 1) "LIMITS:LIMITER/127.0.0.1/index/15/1/minute"
「MATCH "LIMITS:LIMITER/*"」は、LIMITS:LIMITER/で始まり、その後に任意の文字列(*)が続くキーを検索する、という意味です。
GET: キーに保存されている値を取得
「GET」コマンドでキーを指定すると、そのキーに保存されている値(この場合はリクエスト回数)を取得できます。
下記の例では、1分間のうちに6回アクセスがあったことを示しています。
127.0.0.1:6379> GET "LIMITS:LIMITER/127.0.0.1/index/15/1/minute" "6"
TTL: キーの残り有効期限を確認
「TTL」(Time To Live)コマンドを使うと、キーが自動的に削除されるまでの残り時間(秒)を確認できます。
下記の例では、あと46秒でこのキーが失効することを示しています。
127.0.0.1:6379> TTL "LIMITS:LIMITER/127.0.0.1/index/15/1/minute" (integer) 46
【要注意】「KEYS *」 はなぜ危険なのか?
Redisのキーを一覧表示するコマンドとして「KEYS *」もよく知られています。
127.0.0.1:6379> KEYS * 1) "LIMITS:LIMITER/127.0.0.1/index/15/1/minute"
しかし、このコマンドは本番環境では絶対に実行してはいけません。
その理由は、「KEYS」コマンドがRedisサーバーを「ブロック」してしまう(一時的に応答不能にする)危険性があるためです。
なぜブロックされるのか?
Redisは、コマンドの処理を基本的に1つのスレッドで順番に行います。
「KEYS *」コマンドは、データベース内のすべてのキーを一つずつスキャンするため、キーの数が数百万、数千万と増えると、処理に数秒から数分かかることがあります。
このスキャン処理が完了するまでの間、Redisは他のすべてのコマンド(Webアプリケーションからのキャッシュ読み込みや書き込みなど)を受け付けられなくなります。これが「ブロック」状態です。
結果として、Webアプリケーションの応答が極端に遅くなったり、タイムアウトエラーが多発したりして、サービス全体が停止してしまう重大な障害につながる可能性があります。
そのため、本番環境でキーを調査する際は、「KEYS」コマンドの代わりに、サーバーをブロックしない「SCAN」コマンドを必ず使用してください。
まとめ
今回は、HTML変換サービスのレートリミット機能(Flask-Limiter)のデータ保存先を、開発用のインメモリから本番環境でも利用できるRedisへと変更しました。
この変更により、Gunicornのワーカープロセスが再起動してもレートリミットのカウント情報が失われることがなくなり、より堅牢で信頼性の高いサービスへと一歩近づきました。
今回の学習で、以下の重要なポイントを実践的に学ぶことができました。
- Redisの役割と導入
- PythonとRedisの連携
- Flask-LimiterとRedisの統合
- redis-cliでの動作確認
- 本番環境での注意点
高速なインメモリデータベースであるRedisをUbuntuにインストールし、サービスとして自動起動させる方法を学びました。
「redis」ライブラリを使ってFlaskアプリケーションからRedisに接続し、「.env」ファイルで接続情報を安全に管理する方法を実践しました。
lask-Limiterの「storage_uri」設定を変更するだけで、簡単にデータ保存先をRedisに切り替えられることを確認しました。
「redis-cli」を使い、「SCAN」や「GET」、「TTL」といったコマンドで、実際にアプリケーションがRedisと連携して動作していることを確認する方法を学びました。
本番環境で「KEYS *」コマンドを使うことの危険性と、その代替として「SCAN」コマンドを使うべき理由を理解しました。
単に機能を実装するだけでなく、その機能を本番環境で安定して運用するための基盤を整えることの重要性を実感しました。
Redisはレートリミットだけでなく、キャッシュやセッション管理など、Webサービスのパフォーマンスとスケーラビリティを向上させるための多くの場面で活躍する強力なツールなので、今後も積極的に活用していきたいと思います。
コメント