Python Tornado: 大量のアクセス、まだ1つずつ順番にさばいていますか?

📝 TL;DR (3行要約)
Tornadoは、Python製の強力な「非同期」Webフレームワークです。 チャットアプリやライブ配信など、大量の同時接続とリアルタイム性が求められる場面で真価を発揮します。 その最大の利点は、少ないサーバーリソースで驚くほど多くのユーザーを効率的に処理できる高いパフォーマンスにあります。
1. 🤔 一体Tornadoとは何?(核心的な役割と主な使用例)
PythonでWebサーバーを作ろうと思ったとき、多くの人がDjangoやFlaskを思い浮かべるかもしれません。それらは素晴らしいフレームワークですが、特定のシナリオ、特に「たくさんの人が同時にアクセスする」状況では、Tornadoが輝きを放ちます。
- 核心的な役割: 忙しいカフェの超優秀な店員さん ☕
Tornadoが何者なのかを理解するために、少し変わった例え話をさせてください。
従来の多くのWebフレームワークの動きを「一つの窓口しかない銀行」だと想像してみてください。 一人目のお客さん(リクエスト)が窓口に来て、時間がかかる手続き(データベースからのデータ取得や、外部APIへの問い合わせなど)を始めたとします。この手続きが終わるまで、後ろに並んでいるお客さんたちは、ただひたすら待つしかありません。もし最初のお客さんの手続きが10秒かかったら、後ろの全員が10秒以上待たされることになります。これが「同期的」な処理です。一つが終わるまで、次は始まりません。
一方、Tornadoは「超優秀なカフェの店員さん」のような働き方をします。 お客さんから「エスプレッソを一杯」と注文(リクエスト)を受けたら、すぐにエスプレッソマシンに豆をセットして抽出を開始します。しかし、Tornado店員は抽出が終わるのをじっと待ったりはしません。マシンがコーヒーを淹れている「待ち時間」に、すかさず次のお客さんの注文を取ったり、レジを打ったり、別のドリンクを作り始めたりします。そして、エスプレッソの抽出が終わったタイミングで、さっと提供します。
このように、一つのタスクの「待ち時間」を無駄にせず、その間に他のタスクをどんどん進めていく。これがTornadoの得意な「非同期(ノンブロッキング)」な処理です。CPUが暇になる時間(主にネットワーク通信やファイルの読み書きなどのI/O待ち)を徹底的に活用することで、まるで複数の処理を同時に行っているかのように、システム全体のスループットを劇的に向上させるのです。
この「待ち時間を有効活用する」という特性から、Tornadoは特にI/Oバウンドな(CPUの計算よりも、データのやり取りの待ち時間が多い)タスクで絶大なパワーを発揮します。
- 主な使用例: Tornadoが本領を発揮する場所 🚀
この「超優秀なカフェ店員」のような働き方が、具体的にどのような場面で役立つのでしょうか?代表的な例をいくつか見てみましょう。
リアルタイムWebアプリケーション (チャット、オンラインゲーム) Webチャットや、ゲーム内での通知、株価のリアルタイム更新など、サーバーとクライアントが常に接続を保ち、双方向で即座にデータをやり取りする必要があるアプリケーションです。TornadoはWebSocketという技術をネイティブでサポートしており、何千、何万というクライアントとの接続を効率的に維持し、リアルタイムなメッセージ交換を軽快に実現します。
大量の同時接続を処理するAPIサーバー 例えば、SNSのタイムラインを更新するAPIや、世界中に設置された無数のIoTデバイスからセンサーデータを受け取るサーバーを想像してみてください。秒間何千ものリクエストが殺到するような状況でも、Tornadoは非同期処理によってリクエストを詰まらせることなく、効率的にさばき続けることができます。一つ一つの処理は軽くても、数がとにかく多い、という場合に最適です。
Long Pollingを利用するサービス これは、サーバー側で新しい情報が発生するまでクライアントへの応答を保留しておく、古典的なリアルタイム通信技術です。例えば、ニュースサイトで新しい記事が投稿されたらすぐに通知したい場合などに使われます。Tornadoは、応答を保留している間もその接続のためにリソースを占有し続けることなく、他のリクエストを処理できるため、このような接続を大量に保持するタスクが非常に得意です。
DjangoやFlaskが多機能な百貨店だとしたら、Tornadoは「同時接続処理」という特定分野に特化した、驚異的なパフォーマンスを誇る専門店と言えるでしょう。
2. 💻 インストール方法
Tornadoのインストールは、他のPythonライブラリと同様にpipコマンド一つで完了します。とても簡単ですよ。
ターミナル(WindowsならコマンドプロンプトやPowerShell)を開いて、以下のコマンドを実行してください。
pip install tornado
これだけで、あなたの開発環境にTornadoがインストールされます。
3. 🛠️ 実際に動作するサンプルコード
百聞は一見に如かず。Tornadoの非同期処理の力を体感できる、シンプルなWebサーバーのコードを書いてみましょう。
このコードは、2つのページを提供します。
- /: すぐに「Hello, Tornado!」と表示するページ
- /sleep: 5秒間待ってから「5 seconds passed.」と表示するページ
このコードを server.py という名前で保存して実行し、Tornadoの動きを実際に確かめてみてください。
import tornado.ioloop import tornado.web import tornado.gen import asyncio # メインページに対応するハンドラ class MainHandler(tornado.web.RequestHandler): def get(self): # クライアントに文字列を書き込む self.write("Hello, Tornado!") # 5秒待つ非同期処理のページに対応するハンドラ class SleepHandler(tornado.web.RequestHandler): # getメソッドを非同期関数として定義する async def get(self): self.write("I'm going to sleep for 5 seconds...") # self.flush()を使うと、ここまで書いた内容を先にクライアントに送ることができる await self.flush() # Tornadoが提供する非同期のsleep関数を呼び出す # ここで5秒間待つが、イベントループはブロックされない! await asyncio.sleep(5) # 5秒後にこのメッセージを書き込む self.write("<br>5 seconds passed.") # アプリケーションのURLルーティングなどを設定する関数 def make_app(): return tornado.web.Application([ (r"/", MainHandler), # ルートURL ("/") をMainHandlerにマッピング (r"/sleep", SleepHandler), # "/sleep" をSleepHandlerにマッピング ]) # スクリプトが直接実行された場合にサーバーを起動 if __name__ == "__main__": # Python 3.8以降のWindowsではasyncioのイベントループポリシー設定が必要な場合がある # asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) app = make_app() port = 8888 app.listen(port) print(f"🚀 Server is running at http://localhost:{port}/") print("➡️ Try accessing http://localhost:8888/ and http://localhost:8888/sleep") # IOLoopを開始して、リクエストの待受を始める tornado.ioloop.IOLoop.current().start()
実行方法:
- 上記のコードを
server.pyとして保存します。 - ターミナルで
python server.pyと入力して実行します。 🚀 Server is running at http://localhost:8888/と表示されたら成功です。
体験してみよう!
- Webブラウザで新しいタブを開き、
http://localhost:8888/sleepにアクセスします。ページに "I'm going to sleep for 5 seconds..." と表示され、読み込み中のままになるはずです。 - その読み込みを待たずに、すぐに別の新しいタブを開き、
http://localhost:8888/にアクセスします。 - どうでしょう?
/sleepのページが5秒待っている間にも関わらず、/のページは即座に「Hello, Tornado!」と表示されたはずです。
これがTornadoの非同期(ノンブロッキング)の力です!一つの重い処理(5秒待機)が、他の軽い処理を一切妨げていないことが分かりますね。
4. 🔍 コードの詳細説明
サンプルコードが魔法のように動くのを確認できたところで、その中身を少し詳しく見ていきましょう。コードをいくつかの塊(チャンク)に分けて解説します。
import文: 必要な道具を揃えるpython import tornado.ioloop import tornado.web import tornado.gen import asyncioここではTornadoを動かすために必要な部品をインポートしています。tornado.web: Webサーバーの基本的な機能(リクエストハンドラ、アプリケーションなど)が含まれています。tornado.ioloop: Tornadoの心臓部であるI/Oループ(イベントループ)です。すべての非同期処理を管理・実行します。asyncio: Python標準の非同期I/Oライブラリです。最近のTornadoはasyncioと密に連携しており、asyncio.sleepのような便利な非同期関数を利用できます。
MainHandlerクラス: 通常のリクエスト処理python class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, Tornado!")tornado.web.RequestHandlerを継承することで、Webリクエストを処理できるクラスになります。getメソッドは、HTTPのGETリクエストが来たときに自動的に呼び出されます。POSTリクエストを処理したい場合はpostメソッドを定義します。self.write()は、ブラウザに返すレスポンス内容を書き込むためのメソッドです。
SleepHandlerクラス: 非同期処理の核心python class SleepHandler(tornado.web.RequestHandler): async def get(self): # ... (省略) ... await asyncio.sleep(5) # ... (省略) ...- こちらがこのサンプルの主役です。
getメソッドの前にasyncが付いていることに注目してください。これは、このメソッドが「非同期関数」であることを示しています。 await asyncio.sleep(5)の部分が魔法の正体です。awaitは「この非同期処理が終わるまで待ってね」という意味のキーワードです。- しかし、ただ待つのではありません。
asyncio.sleep(5)が5秒間の待機に入ると、TornadoのI/Oループに「5秒後にまた呼びに来て」と伝え、処理の制御を一旦返します。 - 制御を返されたI/Oループは、その5秒間、他のリクエスト(例えば
/へのアクセス)を処理することができるのです。そして5秒経つと、約束通りこの場所に戻ってきて、awaitの次の行のコードを実行します。 - これが、サーバー全体を停止させずに「待つ」ことができる仕組みです。
- こちらがこのサンプルの主役です。
make_app関数: URLと処理の紐付けpython def make_app(): return tornado.web.Application([ (r"/", MainHandler), (r"/sleep", SleepHandler), ])- この関数は、Tornadoアプリケーションの設定を行っています。
tornado.web.Applicationにリストを渡すことで、URLのパターンと、それを処理するハンドラクラスを紐づけています。r"/"はルートURLへのアクセスを意味し、MainHandlerが処理します。r"/sleep"は/sleepへのアクセスを意味し、SleepHandlerが処理します。
サーバー起動部分: エンジンをかける
python if __name__ == "__main__": app = make_app() app.listen(8888) tornado.ioloop.IOLoop.current().start()if __name__ == "__main__":は、このスクリプトが直接実行されたときにのみ以下のコードを実行するためのおまじないです。app = make_app()でアプリケーション設定を読み込みます。app.listen(8888)で、ポート8888番でリクエストを待ち受けるようにサーバーに指示します。tornado.ioloop.IOLoop.current().start()が、Tornadoの心臓部であるI/Oループを起動するコマンドです。これが実行されると、サーバーは無限ループに入り、やってくるリクエストを待ち受け、非同期タスクを管理し始めます。
5. ⚠️ 注意点またはヒント
Tornadoは非常に強力ですが、その力を最大限に引き出すためには、一つだけ絶対に守らなければならないルールがあります。初心者が最も陥りやすい罠でもあるので、ぜひ覚えておいてください。
罠: 非同期コードの中で、絶対に「同期的な」重い処理を呼ばないこと! 🚫
Tornadoの非同期の世界では、すべての処理が素早く完了し、I/Oの待ち時間には制御をI/Oループに返すことが前提となっています。
もし、
async defの中で、Pythonの標準ライブラリであるtime.sleep(5)を使ってしまったらどうなるでしょうか?
# やってはいけない悪い例
import time
class BadHandler(tornado.web.RequestHandler):
async def get(self):
time.sleep(5) # ← これがイベントループ全体を5秒間完全に停止させる!
self.write("This is a bad example.")
time.sleep() は、I/Oループに制御を返すことなく、その場でプログラムの実行を完全に5秒間フリーズさせてしまいます。これは「超優秀なカフェ店員」が、コーヒーの抽出が終わるまで、他の仕事を一切せずにマシンの前で仁王立ちしているようなものです。その結果、その5秒間は他のどんなリクエストも処理できなくなり、Tornadoの利点がすべて失われてしまいます。
ヒント:
- 時間のかかる処理を行いたい場合は、必ず await asyncio.sleep() や、Tornadoが提供する非同期対応の関数を使いましょう。
- データベースアクセスや外部APIへのリクエストなども同様です。非同期に対応したライブラリ(例: asyncpg for PostgreSQL, aiohttp for HTTP requests)を使う必要があります。
- どうしても同期的な重い処理(例: 巨大な画像の処理や複雑な科学計算など、CPUを長時間占有する処理)を実行したい場合は、IOLoop.run_in_executor を使って別のスレッドで実行するなど、イベントループをブロックしない工夫が必須となります。
async の中では、すべてが async の作法に従う必要がある、と覚えておきましょう。
6. 🔗 一緒に見ておくと良いライブラリ
Tornadoで非同期Webプログラミングの面白さに目覚めたあなたに、次の一歩としてぜひ触ってみてほしいライブラリがあります。
FastAPI (ファスト・エーピーアイ)
FastAPIは、Tornadoと同じく非同期処理をベースにした、よりモダンで非常に人気のあるPythonのWebフレームワークです。
- なぜオススメか: Tornadoで学んだ
async/awaitの知識をそのまま活かすことができます。さらに、FastAPIはAPIドキュメントの自動生成、データのバリデーション(検証)、DI(依存性注入)など、現代的なWebアプリケーション開発に便利な機能が最初から豊富に揃っています。 - 関係性: Tornadoが非同期Webのパイオニアの一人だとすれば、FastAPIはその後継者とも言える存在で、Pythonの標準であるASGI (Asynchronous Server Gateway Interface) という規格に準拠して作られています。Tornadoで非同期の基礎概念を掴んだ後、FastAPIを学ぶことで、より生産的に高パフォーマンスなAPIを構築できるようになるでしょう。
- なぜオススメか: Tornadoで学んだ
7. 🎉 まとめ
今日はお疲れ様でした!Pythonの非同期Webフレームワーク「Tornado」について、その核心的な考え方から実践的なコードまで駆け足で見てきました。
最後に、今日の学びのポイントを振り返ってみましょう。
- Tornadoは「非同期(ノンブロッキング)」処理を得意とし、大量の同時接続を効率的にさばくためのフレームワークである。
- その動きは、一つの作業の待ち時間に他の作業を進める「超優秀なカフェの店員」に似ている。
- async def と await を使うことで、サーバー全体をブロックせずに時間のかかる処理を実行できる。
- 最大の注意点は、非同期コードの中で time.sleep() のような同期的な重い処理を呼び出さないこと。
理論を学んだら、次は手を動かして自分のものにするのが一番です。ぜひ、以下の課題に挑戦してみてください!
【今日の挑戦課題】 💻
今日のサンプルコード (server.py) を改造して、以下の機能を追加してみましょう。
- 新しいページを追加:
/user/{あなたの名前}というURLにアクセスすると、「Hello, {あなたの名前}!」と表示される新しいハンドラUserHandlerを追加してみましょう。(ヒント:getメソッドはdef get(self, name):のように引数を取ることができます) - 待ち時間を動的に:
/sleepページの待ち時間を、URLのクエリパラメータで指定できるようにしてみましょう。例えばhttp://localhost:8888/sleep?duration=3にアクセスしたら3秒、?duration=10なら10秒待つようにSleepHandlerを改造してみてください。(ヒント:self.get_argument("duration", "5")でクエリパラメータを取得できます。デフォルト値は5秒に設定すると親切です)
この課題がクリアできれば、あなたはもうTornado使いの第一歩を踏み出したも同然です。Happy Coding!