-
Notifications
You must be signed in to change notification settings - Fork 0
MS_C10kProblem
- 戻る(その他、開発の色々)
「C10K 問題」(クライアント 1 万台問題)とは、ハードウェアの性能上は問題がなくても、
あまりにもクライアントの数が多くなると効率が悪化しサーバがパンクする問題のこと。
- スレッドなどのリソースを大量に消費してしまう。
- 最近は、「C10M 問題」(クライアント 1,000 万台問題)などが出て来ている。
補足(なぜスレッドを消費すると破綻するのか): 問題の本質を
数字で押さえておくと、以降の議論が理解しやすい。【スレッド 1 本あたりのコスト】 ・スタック領域 … 【既定 1MB】(Windows / .NET)★ ・カーネル オブジェクト … 数 KB ・コンテキスト スイッチ … 数 μs(切り替えのたび) 【1 接続 = 1 スレッドだと】 10,000 接続 × 1MB = 【10GB のスタック】 → メモリが足りても、【スケジューラが回らない】 → コンテキスト スイッチだけで CPU を食い潰す ★【1 接続 = 1 スレッドが成立していた時代】 ・同時接続が数百程度 ・接続は【短命】(リクエスト → レスポンス → 切断) 【成立しなくなった理由】 ・Keep-Alive、【WebSocket、SSE】= 接続が長時間居座る ★ ・Ajax / SPA による細かい通信の増加 ・IoT、モバイルの常時接続「C10M」への言及も的確で、
現在はカーネルを迂回する手法(DPDK、io_uring、eBPF、XDP)まで
議論が進んでいる。ただし、業務システムで必要になることは稀である。
- nginx(エンジンエックス)(
DNET_nginx.md) - Node.js(ノードジェイエス)
サーバサイド JavaScript の Node.js はノンブロッキング I/O というモデルにより、
イベントループを止めてしまうようなブロッキングを回避し、C10K 問題に対応する。
- Unix 系 OS 向けの非同期 I/O 環境(epoll/kqueue/event port)を使用する
「libev」ライブラリを使用していた。 - Windows 向けの非同期 I/O 環境(IOCP : Input/output completion port)を使用する
「libuv」を開発した。 - 現在、libeio / libev に代わって libuv が、Node.js のコアとして置き換わりつつある。
補足(この記述は正確。現在は置き換えが完了している): 「置き換わり
つつある」と書かれているが、
libuv への統一は 2012 年頃に完了している(Node.js 0.9 以降)。【libuv が抽象化しているもの】★ アプリのコード(Node.js) ↓ libuv が OS 差を吸収する Linux → epoll macOS → kqueue Solaris → event ports Windows → 【IOCP】ここで重要なのは、Windows だけ方式が違う点である。
【epoll / kqueue(Unix 系)】── 「準備完了通知」型 「読める状態になった」とだけ教えてくれる → 通知を受けてから、【自分で read する】 → Readiness-based 【IOCP(Windows)】── 「完了通知」型 ★ 「読み終わった。データはここ」と教えてくれる → OS が【カーネル側で読み込みまで済ませる】 → Completion-basedこの違いは .NET でも重要である。
・.NET の非同期 I/O は、Windows では【IOCP を使う】 → ThreadPool の「I/O 完了ポート スレッド」がこれ ・Linux では epoll ベース(.NET Core 以降) ・【どちらでも async/await のコードは同じ】★ → libuv と同じく、ランタイムが差を吸収しているなお、Linux でも近年
io_uring(完了通知型)が登場しており、
IOCP に近い方式へ収束しつつある。
UI サブシステムのメッセージループと同じ意味で利用される用語だが、
Web サーバーに関するコンテキストでは、C10k に対応するアーキテクチャ用語として使用される。
-
方式
シングルスレッドでループ処理を回し、- キューに溜まったイベントを処理していく方式。
- リクエストを1つのスレッドで受け取ることができる。
-
特徴
- スレッドなどのリソース消費が少なくて済む。
-
問題
- イベントループは、昔懐かしい、ノンプリエンプティブ・マルチタスク OS のように、
どこかでブロッキングが発生すると、イベントループ全体がストップしてしまう。 - この「イベントループ」の問題を解決するのが「ノンブロッキング I/O」らしい。
- イベントループは、昔懐かしい、ノンプリエンプティブ・マルチタスク OS のように、
補足(ノンプリエンプティブ・マルチタスク との対比が秀逸): この
たとえは本質を突いているので、明示的に整理しておく。【協調的(ノンプリエンプティブ)マルチタスク】 ・各タスクが【自発的に制御を返す】 ・1 つが返さないと【全体が止まる】 → Windows 3.1 の時代の問題 【イベントループ】 ・各コールバックが【自発的に制御を返す】 ・1 つが返さないと【全体が止まる】★ 同じ構造 → Node.js で重い同期処理を書くと、全リクエストが待たされる// Node.js で絶対にやってはいけない例 const data = fs.readFileSync('huge.csv'); // ← 同期 I/O // → この間、【他の全リクエストが止まる】★ for (let i = 0; i < 1e10; i++) { /* CPU を回す */ } // → これも同様。CPU バウンドな処理はイベントループを塞ぐ【対策(Node.js)】 ・同期 API を使わない(readFileSync 等) ・CPU バウンドな処理は【Worker Threads / 別プロセス】へ逃がす.NET の場合はここが違う。
【.NET(ASP.NET Core)】 ・イベントループ 1 本ではなく【スレッド プール】で処理する ・1 つの要求が CPU を回しても、他のスレッドが動く → 【全体が止まることはない】★ ・ただし、スレッド プールが枯渇すると遅くなる(後述) → 「.NET は Node.js より壊れにくいが、 枯渇のしかたが分かりにくい」とも言える
-
簡単に、ブロッキングを防止することでイベントループの停止を防止する。
-
仕組みとしては、ざっくり、
- ワーカースレッドプールと I/O 処理がうまく協調して動き、
- スレッドを節約して動作しているクライアントに処理完了後のコールバックを返す。
という、非常に優れた方法であるらしい。
-
.NET の「async/await」は、≒この、イベントループ、
ノンブロッキング I/O で動作する。
補足(
async/awaitとの関係 ── ここが本ページの核心): 「≒この、
イベントループ、ノンブロッキング I/O で動作する」という指摘は
方向として正しいが、重要な違いがあるので明確にしておく。【共通する本質】★ 「I/O を待っている間、スレッドを占有しない」 → 待ち時間中にスレッドを他の仕事に回せる → 【少ないスレッドで多数の接続を捌ける】= C10K への回答 【違い】 Node.js … 【シングル スレッド】のイベントループ .NET … 【スレッド プール】+ I/O 完了ポート → 複数スレッドで並行に処理する// async/await が「待たない」仕組み public async Task<IActionResult> Get(int id) { var order = await _db.Orders.FindAsync(id); // ← ここでスレッドを【返す】★ // DB が応答するまでの間、このスレッドは別の要求を処理できる return Ok(order); // ← 完了後に再開(別スレッドかも) }
async/awaitを使っても速くならない点は誤解が多い。【async/await が改善するもの】 ・【スループット】(同時に捌ける要求数)★ ・スレッド数・メモリ消費 【改善しないもの】 ・【個々の要求のレイテンシ】(むしろ僅かに増える) ・CPU バウンドな処理の速度同期と非同期を混ぜると最悪になる——これが実務で最も重要である。
// ✗ 絶対にやってはいけない(Sync over Async)★ var order = _service.GetOrderAsync(id).Result; // デッドロック / スレッド枯渇 _service.DoSomethingAsync().Wait(); // ✗ async void(例外を捕捉できない。イベント ハンドラ以外で使わない) public async void DoWork() { } // ○ 最後まで非同期で通す(async all the way) var order = await _service.GetOrderAsync(id);【スレッド プール枯渇(Thread Pool Starvation)】★ ・.Result / .Wait() でスレッドを塞ぐ ・→ プールのスレッドが足りなくなる ・→ .NET は【1 秒に 1~2 本しか増やさない】 ・→ 応答時間が【突然、階段状に悪化する】 ・→ 負荷が下がると回復する(原因が分かりにくい) 【検知】 ・dotnet-counters で ThreadPool Queue Length を見る ・ThreadPool.GetAvailableThreads
-
C10k problem - Wikipedia
https://en.wikipedia.org/wiki/C10k_problem -
Web2.0の先にあるC10K問題 - @IT
https://atmarkit.itmedia.co.jp/news/analysis/200701/09/c10k.html -
TheC10kProblem
「C10K 問題」(クライアント 1 万台問題)とは、ハードウェアの性能上は問題がなくても、
あまりにもクライアントの数が多くなるとサーバがパンクする問題のこと
https://www.hyuki.com/yukiwiki/wiki.cgi?TheC10kProblem
-
イベントループ - Wikipedia
https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%AB%E3%83%BC%E3%83%97 -
イベントループなしでのハイパフォーマンス
C10K問題へのGoの回答 | プログラミング | POSTD
https://postd.cc/performance-without-the-event-loop/
- ノンブロッキングI/Oと非同期I/Oの違いを理解する – PAYFORWARD
http://blog.takanabe.tokyo/2015/03/26/240/
補足(用語の整理 ── 参考リンクの主題): 「ノンブロッキング I/O」と
「非同期 I/O」は厳密には別なので、整理しておく。
用語 意味 ブロッキング I/O 完了するまで呼び出しが返らない ノンブロッキング I/O すぐ返るが、「まだ準備できていない」と返る → 自分でポーリングする I/O 多重化 複数の fd をまとめて監視(select / poll / epoll / kqueue) 非同期 I/O(AIO) 完了したら通知される(IOCP、io_uring)★ 【Node.js が「ノンブロッキング I/O」と呼ばれる理由】 正確には【I/O 多重化 + イベント通知】である → Unix 系では epoll(準備完了通知) → Windows では IOCP(完了通知= 真の非同期 I/O) 【.NET の async/await】 Windows では【IOCP= 真の非同期 I/O】★ Linux では epoll ベース → 利用者から見た効果(スレッドを塞がない)は同じだが、 仕組みの層が違う
-
Node.js を5分で大雑把に理解する - Qiita
https://qiita.com/hshimo/items/1ecb7ed1b567aacbe559 -
Node.jsについて調べてみた - Qiita
https://qiita.com/kyrieleison/items/f726061803a0e536c3e1 -
node.jsの仕組み(ノンブロッキングI/Oとかスレッドとか) - 元リア充エンジニアのメモ
https://satoshun00.hatenadiary.com/entry/2012/08/02/172936 -
注目のサーバサイドJavaScript実行環境「Node.js」 | 最新の技術・取り組み | IIJ
https://www.iij.ad.jp/company/development/tech/activities/nodejs/
-
非同期 I/O - Web/DB プログラミング徹底解説
- (1/4)
http://keicode.com/windows/win03.php - (2/4) OVERLAPPED
http://keicode.com/windows/win04.php - (3/4) 完了ルーチン
http://keicode.com/windows/win05.php - (4/4) I/O 完了ポート
http://keicode.com/windows/win06.php
- (1/4)
-
非同期 (Async) がなぜ重要なのか (.NET) – tsmatz
https://tsmatz.wordpress.com/2012/05/08/asp-net-mvc-async/ -
C#で非同期にする理由 【▲→川俣晶の縁側→ソフトウェア→技術雑記】
http://mag.autumn.org/Content.modf?id=20130930175225 -
c# - When should I use Async Controllers in ASP.NET MVC? - Stack Overflow
https://stackoverflow.com/questions/30566848/when-should-i-use-async-controllers-in-asp-net-mvc
補足(現在の .NET における C10K): 結論として、
現在の ASP.NET Core では C10K は問題にならない。【ASP.NET Core(Kestrel)】 ・最初から【非同期 I/O 前提】で設計されている ・スレッド プール + IOCP / epoll ・1 台で【数万~数十万接続】を扱える → TechEmpower のベンチマークでも上位に位置する 【[ASP.NET Web Forms](MS_ASPNETWebForms) / 旧 ASP.NET との違い】 ・旧来は同期処理が既定 ・[部分描画とJavaScript](MS_PartialRenderingAndJavaScript) の時代は 接続も短命だった現在、同時接続数で問題になるのはむしろ以下である。
① 【スレッド プール枯渇】(前述) → 同期呼び出しが 1 箇所あるだけで発生する ★ ② 【DB のコネクション プール枯渇】 → アプリは非同期でも、DB 接続数には上限がある → SQL Server の既定は 100 → 「アプリは捌けるが DB で詰まる」★ 現在の典型的なボトルネック ③ 【外部 API 呼び出しの HttpClient の使い方】 → using で毎回 new すると【ソケット枯渇】(TIME_WAIT) → IHttpClientFactory を使う ④ WebSocket / SignalR の接続あたりのメモリ → 接続ごとに状態を持つ設計は要注意 ([WebAssembly](MS_WebAssembly) の Blazor Server の項も参照)【つまり】 C10K 問題そのものは【ランタイムが解決した】 現在は「その先の資源(DB 接続、ソケット、メモリ)」が ボトルネックになる ★
Tags: 移行, インフラストラクチャ, Windows, プログラミング, .NET開発
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。