高速サーバー

2026/03/05 23:11

高速サーバー

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

概要:
この記事では、CPU コアごとに 1 本の固定スレッドを割り当てる高性能ネットワーク構成を紹介しています。各スレッドは独自の epoll/kqueue イベントループを所有し、専用のファイルディスクリプタプールを管理することで、共有された決定ポイントと競合を排除します。

  1. スレッド作成
    pthread_create
    を使用してデタッチ状態かつ
    PTHREAD_SCOPE_SYSTEM
    で行い、その後各スレッドが CPU アフィニティ (
    pthread_setaffinity_np
    または macOS のポリシー API) を設定します。
  2. ファイルディスクリプタ制限
    setrlimit(RLIMIT_NOFILE)
    (例:2048 個のディスクリプタ)で各スレッドごとに引き上げ、数多くの同時接続をサポートします。
  3. accept ループ は専用リスナースレッドで実行され、
    accept()
    または
    accept4()
    を呼び出した後、新しいソケットを選択されたワーカーの epoll/kqueue に即座に登録します(
    epoll_ctl
    /
    kevent
    )。
  4. 各ワーカースレッドは
    epoll_wait
    (Linux)または
    kevent
    (BSD/macOS)でイベントを処理します。イベント時には EOF/close フラグが設定されていればディスクリプタを閉じ、そうでなければリクエストハンドラー (
    handle(fd)
    ) を呼び出します。ソケットは通常非ブロッキングに設定され、
    SO_RCVTIMEO
    でタイムアウトを持つ場合があります。
  5. 状態遷移(accept → read → write → close)は別々のスレッドで処理されます。クライアントのファイルディスクリプタは対応するワーカーキュー間で渡され、各 FD が常に 1 本のスレッドだけに所有されるようにします。
  6. 設計では TCP_DEFER_ACCEPT を採用し、リッスンソケットのリーングを無効化(
    SO_LINGER{onoff=1, linger=0}
    )して遅延を低減しています。
  7. 新規接続は単純なラウンドロビン
    pick()
    関数でロードバランシングされ、ワーカーキューを順に巡回します。
  8. このアーキテクチャは、分離されたスレッド上のブロッキング I/O 呼び出しと状態遷移を所有スレッドへルーティングすることで、最新ハードウェアで約 100 k リクエスト/秒 を実現できます。

このモデルを採用すると、高接続量に対する CPU オーバーヘッドが低減され、クラウドサービスのスケーラビリティが向上し、開発者はイベントループロジックを簡素化できます。将来的な拡張としては、ワーカースレッドの動的スケーリング、ラウンドロビン以外の高度なロードバランシング、および

SO_RCVTIMEO
を用いたより細粒度のタイムアウト処理が考えられます。

本文

ネットワークサーバ設計パターン

ネットワークサーバを構築する際の標準的な手法は、単純なループに沿っています。

  1. メインスレッドが
    epoll
    kqueue
    を通じてイベントを待ちます。
  2. イベントが発生すると、ファイルディスクリプタとその現在状態に基づき処理を振り分けます。

昔は接続ごとに

fork()
でプロセスを作成していましたが、今日では「ワーカースレッド」が多数生成され、同じタスクを実行しながらカーネルがファイルディスクリプタの割り当てを行う方式が主流です。
epoll
/
kqueue
を活用したより良い設計も可能ですが、多くの開発者は libevent のようなラッパーに頼り、古い遅延パターンを生かし続けています。


私の推奨設計

  • コア数だけスレッドを作成 – CPU に固定(affinity)し、各スレッドが独自の
    epoll/kqueue
    ディスクリプタを所有。
  • 状態遷移は専用スレッドで処理 – クライアントがある状態から別の状態へ移る際に、そのファイルディスクリプタを他のスレッドの
    epoll/kqueue
    に渡す。

この設計では決定点がなく、単純なブロッキング/IO 呼び出しだけで動作します。結果として 1 ページ程度のサーバコードで、最新ハードウェア上で約 100 k リクエスト/秒を達成できます。


スレッドプールの構築

pthread_attr_t a;
pthread_attr_init(&a);
pthread_attr_setscope(&a, PTHREAD_SCOPE_SYSTEM);
pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED);

long t = sysconf(_SC_NPROCESSORS_ONLN);   /* コア数 */
for (int i = 0; i < t; ++i)
    pthread_create(&id, &a, (void *)run, (void *)i);

/* 全スレッドが準備完了を通知するまで待機 */
while (busy(t)) {
    pthread_mutex_lock(&tm);
    pthread_cond_wait(&tc, &tm);
    pthread_mutex_unlock(&tm);
}

各スレッドは以下のように動作します。

void *run(void *id) {
    int id = (int)(intptr_t)id;
    set_affinity(id);

    pthread_mutex_lock(&tm);
#ifdef __linux__
    worker[id].q = epoll_create1(0);
#else
    worker[id].q = kqueue();
#endif
    /* …その他スレッド固有の初期化… */
    pthread_mutex_unlock(&tm);
    pthread_cond_signal(&tc);
}

プロセッサアフィニティの設定

cpu_set_t c;
CPU_ZERO(&c);
CPU_SET(id, &c);
pthread_setaffinity_np(pthread_self(), sizeof(c), &c);

macOS では

pthread_setaffinity_np
が無いので、以下を使用します。

extern int thread_policy_set(thread_t, thread_policy_flavor_t,
                             thread_policy_t, mach_msg_type_number_t);

thread_affinity_policy_data_t ap;
thread_extended_policy_data_t ep;

ap.affinity_tag = id + 1;
ep.timeshare    = FALSE;

thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,
                  (thread_policy_t)&ep, THREAD_EXTENDED_POLICY_COUNT);
thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,
                  (thread_policy_t)&ap, THREAD_EXTENDED_POLICY_COUNT);

リッスンソケットの作成

struct rlimit r;
getrlimit(RLIMIT_NOFILE, &r);
if (r.rlim_cur < n) {          /* 必要なら制限を上げる */
    r.rlim_cur = n;
    setrlimit(RLIMIT_NOFILE, &r);
}

int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

struct linger lf = { .l_onoff  = 1,
                     .l_linger = 0 };
setsockopt(s, SOL_SOCKET, SO_LINGER, &lf, sizeof(lf));

listen(s, n * t);              /* バックログ */

クライアントが HTTP などで接続を開始する場合、Linux では遅延受け入れ(deferred accept)を有効にします。

#ifdef __linux__
int o = 5;
setsockopt(s, SOL_TCP, TCP_DEFER_ACCEPT, &o, sizeof(o));
#endif

Accept‑ループ

ここでは

epoll
/
kevent
は不要で、接続受け入れのみを行います。

for (;;) {
    int f = accept(s, NULL, NULL);
    /* …オプションの初期化… */
    int q = pick();              /* ワーカー選択 */

#ifdef __linux__
    struct epoll_event ev = {0};
    ev.data.fd   = f;
    ev.events    = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLET;
    epoll_ctl(q, EPOLL_CTL_ADD, f, &ev);
#else
    struct kevent ev;
    EV_SET(&ev, f, EVFILT_READ,
           EV_ADD | EV_CLEAR, 0, NM, NULL);
    kevent(q, &ev, 1, NULL, 0, NULL);
#endif
}

ハンドオフ前のソケット設定

struct timeval tv = { .tv_sec = 5 };
setsockopt(f, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
fcntl(f, F_SETFL, O_NONBLOCK);

Linux では

accept4()
を使うと
accept()
fcntl()
を一度に実行できます。

ワーカー選択

int pick(void) {
    static int c = 0;
    return worker[(++c) % t].q;
}

高度な負荷分散では、このロジックをチューニングしてスループットを最適化します。


リクエスト・ループ

各ワーカーは

epoll
/
kevent
を待ち受け、入力があると処理します。

#ifdef __linux__
struct epoll_event e[1000];
for (int i = 0; i < epoll_wait(q, e, 1000, -1); ++i) {
    if (e[i].events & (EPOLLRDHUP | EPOLLHUP))
        close(e[i].data.fd);
    else
        handle(e[i].data.fd);
}
#else
struct kevent e[1000];
for (int i = 0; i < kevent(q, NULL, 0, e, 1000, NULL); ++i) {
    if (e[i].flags & EV_EOF)
        close(e[i].ident);
    else
        handle(e[i].ident);
}
#endif

ファイルディスクリプタは各リクエストの単一状態で使用されるため、FD ごとに入力バッファを配列化すると多くのアルゴリズムが簡素化できます。

handle(fd)
は入力を読み取り、必要に応じて書き込みやファイル送信を行い、複数システムコールが必要な場合は適切なワーカーへタスクをスケジュールします。


まとめ

  • コアごとに 1 スレッド(
    epoll/kqueue
    を所有)
  • スレッドを CPU に固定し予測可能性を確保
  • 状態遷移時にファイルディスクリプタを別スレッドへ渡す
  • Accept‑ループは最小化、ノンブロッキングソケットとタイムアウト設定
  • ワーカー選択は負荷特性に応じてローテーションやバイアス

この設計でクリーンかつ高性能なサーバを、わずかなコード量で実現できます。

同じ日のほかのニュース

一覧に戻る →

2026/03/06 1:04

多数の管理者アカウントが侵害された後、ウィキペディアは読み取り専用モードになった。

## Japanese Translation: **概要:** 本書は、2026年2月20日から3月5日にかけて Wiki サービスに影響を与えた一連の技術的インシデントを記録しています。 - **2月20日:** 19:28 UTC に問題が確認され、19:44 UTC に修正が適用されました。23:33 UTC に監視を再開しました。 - **2月25日:** 16:40 UTC に調査が開始され、17:24 UTC に解決しました。 - **2月26日:** 16:25 UTC に修正で問題が解消され、16:58 UTC に監視を開始しました。 - **3月3日:** 10:09 UTC にデータベースサーバーの問題が検知され、10:24 UTC に修正が適用されました。10:17 UTC に監視更新が行われました。 - **3月5日:** 16:11 UTC に問題が確認され、17:09 UTC に初期修正(読み書き復旧)が実施されました。さらに編集は17:36 UTC の追加修正まで無効のままでした。18:36 UTC に監視を継続し更新しました。 3月4日、2月1–2、2月27–28、2月22–24、または2月21日はインシデントが報告されていません。 インシデントは運用上の問題、性能低下、一部停止、大規模停止、およびメンテナンスカテゴリにわたります(ただし各イベントに対する具体的なカテゴリは割り当てられていません)。 すべての修正後、チームは安定性を確認するために継続的監視を実施し、完全回復を宣言しました。ユーザーは一時的な読み取り専用アクセスと編集制限を経験し、継続的な Wiki 利用が必要な企業や教育グループの協力に影響を与える可能性があります。新たな症状が出現した場合に備えて、引き続き観測が行われることが示唆されています。 *この拡張版がご要望に合致する場合は、元の概要を置き換えることができます。*

2026/03/06 2:44

**「ブランドの時代」**

## 日本語訳: **要約:** スイスの時計業界は、1970年代に起こったクォーツ危機によって軌道を変えました。この危機は日本企業の競争とフランス・米ドル為替レートの急騰が引き金となり、ユニット販売数は1970年代初頭から1980年代初頭にかけて約3分の2減少しました。その結果、多くのメーカーは破綻または買収を余儀なくされました。残存した数社は純粋な技術的精密さから**ブランド主導のラグジュアリー**へとシフトしました。 視覚的マイルストーンがこの転換を確固たるものにしました:パテック・フィリップの1968年「ゴールデン・エリプス」ケース、オーデム・ピゲの1972年ロイヤルオーク(ジェラルド・ゲンタ設計)、そして1976年のノーティラスはすべて技術的洗練よりも瞬時に認識できるデザインを強調しました。1984年にはパテックの広告代理店長レネ・ビッテルが「ホブナイル・カラトラバ」(3919)を提唱し、手巻き機構と独特な模様が投資銀行家の注目を集め、1987年までに売上を急増させました。 メカニカル時計は**高級アクセサリー**として再登場しました。大きさと視覚的インパクトが男性の「ユーピー」(若手社会人)に富を披露するために理想的だったためです。ブランド時代は現在、オーバーサイズで独特な形状のケース、人工的希少性、および二次市場(例:パテックの時計買い戻し)の積極的管理によって定義されます。 主要ブランドは階層化された製品ラインを割り当てる持株会社に統合されました。独立ブティックは、パテック・オーデム・ピゲ、ロレックスなどの数少ないフラッグシップハウスでのみ存続しています。業界がステータスシンボルに焦点を当てることで、メーカーによって管理される**資産バブルに似たビジネスモデル**が生まれました。 **教訓:** ブランド力は収益性を推進しますが、過度の依存はイノベーションを抑制するリスクがあります。次の「黄金時代」は、名声を売るだけでなく、本当に興味深い問題に取り組むことで生まれる可能性が高いです。

2026/03/02 18:26

**Linuxにおけるハードウェア・ホットプラグイベント ― 詳細解説**

## Japanese Translation: **概要:** Libusb の Linux ホットプラグシステムは、`linux_netlink.c` と `linux_udev.c` という 2 つのバックエンドに依存しています。デフォルトでは `--with-udev=yes` が設定されており、udev を無効にするとプレーンな netlink バックエンドが使用されます。 カーネルデバイスイベントは Netlink プロトコル 15(`NETLINK_KOBJECT_UEVENT`)を介して到達し、ヌル終端文字列として `add@/devices/...` のようなアクション行から始まり、`ACTION=add`、`SUBSYSTEM=usb` などのキー/バリュー ペアが続きます。udev はこれらのメッセージを受信し解析して、カスタムパケット形式でマルチキャストグループ 2(`MONITOR_GROUP_UDEV`)に再送信します。 udev パケットは `"libudev"` というマジック文字列から始まり、ビッグエンディアンのバージョンワード `0xfeedcafe` を持ち、次にネイティブエンディアンで格納された複数フィールド(`header_sz`、`properties_off`、`properties_len`、`subsystem_hash`、`devtype_hash`、`tag_bloom_hi`、`tag_bloom_lo`)が続きます。ハッシュは `SUBSYSTEM=` と `DEVTYPE=` の値に対して MurmurHash2 を用いて計算され、2 つの Bloom フィルタワードは `TAGS=` キーから導出されたビットをエンコードします。その後パケットには元のキー/バリュー文字列と、`SO_PASSCRED` 経由で送られる Unix 認証情報(pid/uid/gid)が含まれます。カーネルメッセージはゼロ認証情報を持つため、libudev は有効な認証情報がないパケットを拒否します。 プロトコルバージョンは固定で `0xfeedcafe` となっており、後方互換性や前方互換性に関する保証は文書化されていません。そのため、パケットレイアウト、フィルタリングロジック、または認証情報処理の変更は libusb と udev の両方で協調して更新を行う必要があり、ホットプラグイベントに依存するアプリケーションのデバイス検出、安定性、セキュリティに影響を与える可能性があります。

高速サーバー | そっか~ニュース