masakazu-takewakaのブログ

たまに書きます。

「[24時間365日]サーバー/インフラを支える技術」を読んだ

読んだのでメモ。

1章 冗長化・負荷分散の基本

コールドスタンバイ -> 予備機を使わずに、現用機に障害が発生したら切り変えること(e.g. ルーター) ホットスタンバイ -> 予備機も稼働させておくことで現用機と同じ状態を保つこと(e.g. Webサーバー) フェイルオーバー -> 現用機に障害が発生した時に自動的に処理を予備機に引き継ぐこと

Webサーバーのフェイルオーバーは2段階。

  1. ヘルスチェック
  2. 仮想IPの引き継ぎ

1. ヘルスチェック

IP引き継ぎの機会(障害の発生)を検出する。

  • ICMP監視(レイヤ3)
    • ping
    • マシンに電源が入っていることをチェックするだけなので、webサーバーが落ちているかはわからない。
  • ポート監視(レイヤ4)
    • TCPで接続
    • Webサーバーが稼働しているかチェックするだけなので、サービスのエラーはわからない。
  • サービス監視(レイヤ7)
    • HTTPリクエス
    • サービスのエラーはわかるが、他と比べると負荷が高い。

2. 仮想IPの引き継ぎ

4章 性能向上、チューニング。Linux単一ホストの負荷の見極め

負荷 -> 複数のタスク(プロセス)がリソースを奪い合う結果生じる待ち時間

  • ボトルネック見極めの基本的な流れ

  • 多くの場合Webアプリケーションにおいては 負荷分散 = ディスクI/Oの軽減

    • キャッシュを効かせることが鍵
  • AppサーバーはCPUバウンドであることが多い

    • 基本的にDBサーバーから受け取ったデータを加工してクライアントサイドに送るだけだから
  • DBサーバーはI/Oバウンドであることが多い

    • 多くのデータをディスクからフェッチするから
  • topなどで出る3つのロードアベレージの数字はそれぞれ直近1分、5分、15分の間で待ち状態にあったタスクの単位時間あたりの数

ロードアベレージカーネルコードを見る

  • run queueに存在するTASK_RUNNINGとTASK_UNINTERRUPTIBLEの2つをカウントしている
long calc_load_fold_active(struct rq *this_rq, long adjust)
{
    long nr_active, delta = 0;


    nr_active = this_rq->nr_running - adjust;
    nr_active += (long)this_rq->nr_uninterruptible;


    if (nr_active != this_rq->calc_load_active) {
        delta = nr_active - this_rq->calc_load_active;
        this_rq->calc_load_active = nr_active;
    }


    return delta;
}

https://github.com/torvalds/linux/blob/b719ae070ee2371c37d846616ef7453ec6811990/kernel/sched/loadavg.c#L79-L92

CPU使用率とI/O待ち率

  • マルチCPU(シングルコアを仮想的に複数のcpuに見せることが可能)
    • cpu負荷は分散できるがI/O負荷は分散できない
      • e.g) 2cpuだとして、全体のI/O負荷20%、cpu1のI/O負荷0%、cpu2のI/O負荷40%がありえる
        • CPU使用率、I/O待ち率はCPUごとに見ていく必要がある

CPU使用時間などを記録する構造体。sarが表示する項目が全部入っている:

enum cpu_usage_stat {
    CPUTIME_USER,
    CPUTIME_NICE,
    CPUTIME_SYSTEM,
    CPUTIME_SOFTIRQ,
    CPUTIME_IRQ,
    CPUTIME_IDLE,
    CPUTIME_IOWAIT,
    CPUTIME_STEAL,
    CPUTIME_GUEST,
    CPUTIME_GUEST_NICE,
    NR_STATS,
};

https://github.com/torvalds/linux/blob/d60ddd244215da7c695cba858427094d8e366aa7/include/linux/kernel_stat.h#L20-L32

cpu使用率のカーネルコードを見る

タイマ割り込みの度にaccount_process_tickを呼ぶ

void update_process_times(int user_tick)
{
    struct task_struct *p = current;


    /* Note: this timer irq context must be accounted for as well. */
    account_process_tick(p, user_tick);
    run_local_timers();
    rcu_sched_clock_irq(user_tick);
...

https://github.com/torvalds/linux/blob/da05b5ea12c1e50b2988a63470d6b69434796f8b/kernel/time/timer.c#L1719

user_tickが正であればaccount_user_timeを、そうでなければaccount_system_timeもしくはaccount_idle_timeを呼ぶ

void account_process_tick(struct task_struct *p, int user_tick)
{
    u64 cputime, steal;

    if (vtime_accounting_enabled_this_cpu())
        return;

    if (sched_clock_irqtime) {
        irqtime_account_process_tick(p, user_tick, 1);
        return;
    }

    cputime = TICK_NSEC;
    steal = steal_account_process_time(ULONG_MAX);

    if (steal >= cputime)
        return;

    cputime -= steal;

    if (user_tick)
        account_user_time(p, cputime);
    else if ((p != this_rq()->idle) || (irq_count() != HARDIRQ_OFFSET))
        account_system_time(p, HARDIRQ_OFFSET, cputime);
    else
        account_idle_time(cputime);
}

https://github.com/torvalds/linux/blob/c677124e631d97130e4ff7db6e10acdfb7a82321/kernel/sched/cputime.c#L471

各関数はtask_struct構造体の対応するメンバに時間を足して記録する

void account_user_time(struct task_struct *p, u64 cputime)
{
    int index;


    /* Add user time to process. */
    p->utime += cputime; /* <- ここ
   account_group_user_time(p, cputime);


   index = (task_nice(p) > 0) ? CPUTIME_NICE : CPUTIME_USER;


   /* Add user time to cpustat. */
    task_group_account_field(p, index, cputime);


    /* Account for user time used */
    acct_account_cputime(p);
}

https://github.com/torvalds/linux/blob/c677124e631d97130e4ff7db6e10acdfb7a82321/kernel/sched/cputime.c#L117

void account_system_time(struct task_struct *p, int hardirq_offset, u64 cputime)
{
    int index;


    if ((p->flags & PF_VCPU) && (irq_count() - hardirq_offset == 0)) {
        account_guest_time(p, cputime);
        return;
    }


    if (hardirq_count() - hardirq_offset)
        index = CPUTIME_IRQ;
    else if (in_serving_softirq())
        index = CPUTIME_SOFTIRQ;
    else
        index = CPUTIME_SYSTEM;


    account_system_index_time(p, cputime, index);
}

void account_system_index_time(struct task_struct *p,
                   u64 cputime, enum cpu_usage_stat index)
{
    /* Add system time to process. */
    p->stime += cputime; /* <- ここ
   account_group_system_time(p, cputime);


   /* Add system time to cpustat. */
    task_group_account_field(p, index, cputime);


    /* Account for system time used */
    acct_account_cputime(p);
}

https://github.com/torvalds/linux/blob/c677124e631d97130e4ff7db6e10acdfb7a82321/kernel/sched/cputime.c#L184

void account_idle_time(u64 cputime)
{
    u64 *cpustat = kcpustat_this_cpu->cpustat;
    struct rq *rq = this_rq();


    if (atomic_read(&rq->nr_iowait) > 0)
        cpustat[CPUTIME_IOWAIT] += cputime;
    else
        cpustat[CPUTIME_IDLE] += cputime;
}

https://github.com/torvalds/linux/blob/c677124e631d97130e4ff7db6e10acdfb7a82321/kernel/sched/cputime.c#L218

psとスレッド

  • 別名LWP(Light Weight Process)
  • カーネル内では"タスク"としてプロセスと同じ扱いを受ける
  • ps -Lで見れる
  • ps aux -L | wc -l と数字が一致するので、sar -qのplist-sz(システムに存在するプロセス数)にはスレッドの数も含まれている雰囲気

ps, sar, vmstatの使い方

  • ps
    • TIMEはCPU使用時間のことであり、プロセスが起動してからの時間ではない
  • sar
    • sadcというバックグラウンドで動く、カーネルからレポートを収集して保存するプログラムがある
    • 時間毎のOSの状態が見れるので、プログラムなどを入れ替えた直後に使ったりする
    • /var/log/saに過去の情報がある
  • vmstat
    • 仮想メモリ周りの情報
    • 発生したI/Oの絶対値が見れる
      • bi ... ブロックデバイスから受け取ったブロック(blocks/s)
      • bo ... ブロックデバイスに送られたブロック(blocks/s)

その他

  • Linuxは可能な限り空いているメモリをキャッシュに回そうとする
    • DBサーバー再起動->flushでキャッシュが空になる->データ全体に読み込みをかけることでキャッシュ構築->本番環境に戻す