夜間対応のピッチが鳴り響き、冷え切ったNOCのフロアでモニターの青白い光に照らされながら、私は何度このコマンドを叩いたことだろう。
「おい、WebAPIのレスポンスが急激に劣化している。コネクションが枯渇しているのか、それともバックエンドのDBが詰まっているのか、今すぐカーネル内のソケット状態を確認しろ!」
若手エンジニアが慌ててキーボードを叩き、古いエンジニアリングブログを頼りに netstat -an を打ち込もうとする。私はその手を制した。「おいおい、数万のコネクションが蠢く現代の大規模インフラで、未だに netstat を使う気か? カーネルのメモリをドカ食いし、CPUを焼き尽くすレリックは今すぐ捨てろ。これからは ss コマンドだ」
今回は、現代のLinuxインフラ運用やWeb APIのパフォーマンスチューニングにおいて必須の武器となる、ss コマンドのフィルタリング機能とメモリ使用量(Memory Usage)表示について、現場の泥臭い知見を交えて徹底的に解説しよう。
—
なぜ今、netstat から ss なのか?
かつてネットワークの診断といえば netstat が定番だった。しかし、netstat は /proc/net からすべてのソケット情報を愚直にテキストとして読み込み、ユーザー空間でパースするという非効率なアプローチをとっていた。数万、数十万のコネクションを抱える高負荷なWeb APIサーバーでこれを実行すると、CPU使用率が跳ね上がり、場合によっては監視システムへの応答すら遅延させるという「犯人が自分だった」的惨事を引き起こす。
一方、ss(Socket Statistics)は、Linuxカーネルの inet_diag というダイレクトなカーネルインターフェースを利用する。カーネル内でフィルタリングを済ませた必要最小限のデータだけを高速にユーザー空間へ持ち上げるため、システムへの負荷が桁違いに低い。百戦錬磨のインフラエンジニアが深夜の障害対応で真っ先に手に取るのは、間違いなくこの ss コマンドなのだ。
—
現場で即座に使える ss の強力なフィルタリング機能
ss の真骨頂は、その圧倒的で柔軟なフィルタリング構文にある。状態(State)、ポート、アドレス、さらにはプロトコルごとの絞り込みを組み合わせることで、ノイズの海から真の原因(パケットの詰まり)を瞬時に炙り出すことができる。
1. 接続状態(State)によるフィルタリング
Web APIサーバーで最も警戒すべきは、TIME-WAITの過剰な蓄積や、CLOSE-WAITの取り残し(アプリケーション側のソケット解放漏れ)だ。
例えば、TIME-WAIT状態のソケットだけを効率よく確認したい場合は、以下のように実行する。
# TIME-WAIT状態のソケットを抽出し、簡易表示する
ss -t -a state TIME-WAIT
ここで -t はTCPソケットを指定し、-a はすべての状態(リスニング含む)を対象にするフラグだ。state キーワードの後ろに ESTABLISHED、CLOSE-WAIT、FIN-WAIT-2 などを指定することで、特定のライフサイクルにいるソケットだけをピンポイントで監視できる。
2. ポートおよびアドレス指定によるフィルタリング
「特定のWeb APIエンドポイント(例えばポート 443 や、バックエンドのマイクロサービスが待ち受けるポート 8080)へのトラフィックがどうなっているか」を調べたいときは、sport(送信元ポート)や dport(宛先ポート)のフィルタを使う。
# 宛先ポートが 443(HTTPS)の確立済みコネクションを抽出
ss -t state established '( dport = :https or dport = :443 )'
このフィルタリング構文のミソは、条件式をシングルクォーテーションと丸括弧で囲む点だ。シェルによる解釈を防ぎ、ss の強力なパーサーに直接条件を渡すことができる。
—
ソケットの「内臓」を見る:メモリ使用量(Memory Usage)の表示
アプリケーション層のエンジニアから「APIのレスポンスが返らない」と相談を受けた時、ネットワーク層で何が起きているか? その答えは多くの場合、ソケットバッファの枯渇にある。
ss コマンドに -m(Memory)オプションを付与すると、各ソケットがカーネルメモリをどれだけ消費しているか、その内臓を覗き見ることができる。
# メモリ使用量とSend/Recv Qを含めてTCPソケットを表示
ss -t -m -o state established
実際の出力例を見てみよう。
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0 1460 10.0.1.10:443 192.168.1.50:52341 users:(("nginx",pid=1234,fd=15))
mem:(r0,w0,rb12288,wb4096,shmem0,tw0,ts0,sftgnr0,sftgnt0,tok0,toas0,tos0)
この出力から読み解くべき重要なパラメータを整理する。
送受信キュー(Recv-Q / Send-Q)
Recv-Q(受信キュー): アプリケーションがまだ読み取っていない受信データのバイト数。ここが常にゼロより大きい値で張り付いている場合、アプリケーションの処理が追いついていない(スレッドプールが枯渇している等)ことを意味する。Send-Q(送信キュー): リモート側からACKが返ってきておらず、まだ送信完了していないデータのバイト数。ここが大きい場合、ネットワーク帯域の輻輳か、相手側(クライアント)の受取り能力が低下している証拠だ。
メモリ詳細(mem: パラメータ)
括弧内の mem: の中身は、Linuxカーネルがそのソケットに割り当てているメモリの状況を示している。
rb12288/wb4096: それぞれ受信バッファ(Read Buffer)と送信バッファ(Write Buffer)のバイト数。- トラフィック量やネットワークの遅延(RTT)に応じて、カーネルは
tcp_rmemやtcp_wmemのチューニング範囲内でこのバッファサイズを動的に拡大・縮小させる。ここが常に上限張り付きになっている場合、OSのネットワークバッファチューニング(sysctl)の見直しが必要になる。
—
実務での活用シナリオ:Python製Web APIのデバッグ
ここで、実務における具体的なトラブルシューティングのシナリオを考えてみよう。
あなたが運用するPython(FastAPI / Gunicorn)製のWeb APIサーバーに対し、クライアント側(例えばフロントエンドのNext.jsアプリや外部のモバイルアプリ)から大量のリクエストが送り込まれ、一部のクライアントでタイムアウトが頻発しているとする。
1. 現場での診断コマンド実行
まずは、Gunicornがリスニングしているポート(例: 8000)に対するコネクションの状態と、メモリバッファのつまり具合を ss で一網打尽にする。
# 8000番ポートへのコネクションで、Send-Qがたまっている(=クライアントへ送出できていない)ものを抽出
ss -t -m 'sport = :8000'
もし、ここで Send-Q が数千バイトから数万バイトに達し、かつ mem: の書き込みバッファが肥大化しているソケットが多数見つかった場合、原因はサーバー側ではなく「クライアント側のネットワークが細い、あるいはアプリケーションがレスポンスの読込をサボっている(スロークライアント問題)」であると即座に特定できる。
2. アプリケーション層(Python)からのアプローチ
このような「スロークライアント」や「コネクションのリーク」に直面した際、インフラ側だけでなくアプリケーションコード側(例えばPythonの requests や aiohttp、あるいはWebフレームワークのタイムアウト設定)でも適切にガードをかける必要がある。
以下は、Pythonの requests ライブラリを用いて、外部のAPIを叩く際に適切にタイムアウトとコネクション管理を行う実用的なコード例だ。
import requests
from requests.exceptions import Timeout, RequestException
def call_external_api(endpoint_url: str, payload: dict) -> dict:
"""
外部Web APIを安全に呼び出すためのラッパー関数。
スロークライアントやネットワークのスタックによるハングアップを防ぐため、
必ずコネクションのタイムアウトを明示的に指定する。
"""
# 接続確立(connect)に3秒、データ受信(read)に5秒のタイムアウトを設定
timeout_config = (3.0, 5.0)
try:
response = requests.post(
endpoint_url,
json=payload,
timeout=timeout_config,
headers={"Content-Type": "application/json"}
)
# HTTPステータスコードが 4xx / 5xx の場合に例外を発生させる
response.raise_for_status()
return response.json()
except Timeout:
# タイムアウト発生時のログ出力とリカバリ処理
print(f"[ERROR] API request timed out: {endpoint_url}")
raise
except RequestException as e:
# その他のネットワーク関連エラー
print(f"[ERROR] Network communication failed: {e}")
raise
このような堅牢なコードを実装しておくことで、万が一ネットワーク層でパケットが詰まり始めた際にも、アプリケーション全体がスレッドプールごとブラックホールに引きずり込まれるのを防ぐことができる。
—
シニアエンジニアからのメッセージ
ネットワークのトラブルシューティングにおいて、魔法の杖のような単一のコマンドなど存在しない。だが、カーネルの内部状態を正確かつ低負荷に映し出す ss コマンドは、それに最も近い「信頼できる相棒」だ。
「画面がフリーズした」「APIが重い」という抽象的なアラートに直面したとき、パニックになって闇雲にサーバーを再起動する前に、まずは冷静に ss -t -m を叩いてみる。ソケットがどんな顔をして、どれだけのメモリを抱え込んでいるのか――その声に耳を傾ければ、障害の原因は自ずと向こうから姿を現すはずだ。
さあ、次のアラートが鳴る前に、手元の検証環境で ss のフィルタリングを試してみてほしい。パケットの息吹を感じ取れるようになれば、あなたも立派なNOCの住人だ。
コメント