前半の「基礎編」で得た知識を、実際のサービス開発・運用に応用するための「実践編」となります。
第7章:アルゴリズムとデータ構造の実践
〜計算量を見極め、適切な道具を選ぶ〜
「動けばいい」コードから卒業し、数千万リクエストに耐えうるコードを書くための章です。エンジニアが選ぶべきデータ構造は、その特性(計算量)によって決定されます。
- オーダー記法(Big O Notation)の再確認:
- $O(n)$(線形探索)と $O(\log n)$(二分探索)の差は、データ量が100万倍になれば100万倍の性能差になります。ループを書くときは常にオーダーを意識する必要があります。
- ハッシュ vs ツリー:
- ハッシュテーブル ($O(1)$): 最速でデータを取り出せますが、順序を持ちません。
- ツリー構造 ($O(\log n)$): 検索はハッシュより劣りますが、順序を維持でき、範囲検索(Range Scan)が可能です。
- ブルームフィルタ (Bloom Filter):
- 「ある要素がセットに含まれていないこと」を確率的かつ高速に判定するデータ構造。
- メモリ効率が極めて高く、キャッシュミスを防ぐフィルタとして大規模システムで重宝されます。
graph LR
Input("入力値: 'Hello'")
subgraph HashFunctions ["ハッシュ関数群"]
H1["Hash 1"]
H2["Hash 2"]
H3["Hash 3"]
end
subgraph BitArray ["ビット配列 (0 or 1)"]
direction LR
B0[0]
B1[1]
B2[0]
B3[1]
B4[0]
B5[1]
B6[0]
end
%% Flow
Input --> H1
Input --> H2
Input --> H3
H1 -->|"map"| B1
H2 -->|"map"| B3
H3 -->|"map"| B5
style B1 fill:#ffcc80,stroke:#f57c00
style B3 fill:#ffcc80,stroke:#f57c00
style B5 fill:#ffcc80,stroke:#f57c00
style HashFunctions fill:#f5f5f5,stroke:#333,stroke-dasharray: 5 5第8章:ファイルシステムとストレージ
〜データを物理的にどう配置するか〜
DBの裏側にあるファイルシステムの挙動を理解し、ハードウェア選定のミスを防ぎます。
- ジャーナリングファイルシステム:
- ファイルシステム(ext4, XFSなど)がクラッシュから復旧する仕組みです。メタデータの整合性を保つ「ジャーナル(ログ)」の書き込みコストが発生することを理解しておきましょう。
- RAIDの選定:
- RAID 10 (1+0): 大規模DBで推奨されます。RAID 1(ミラーリング)の安全性とRAID 0(ストライピング)の高速性を兼ね備えます。
- RAID 5: パリティ計算コストが高く、書き込み性能が落ちるため、頻繁に更新されるDBには不向きです。
| 規模・用途 | 推奨ストレージ | 推奨 RAID | 選定理由・アーキテクチャのポイント |
| 小〜中規模 DB (スタートアップ・社内ツール) | SATA/SAS SSD | RAID 1 (ミラーリング) | 【コスト重視】 ディスク2本で構築可能。書き込みペナルティがなく、SSDの速度で十分なパフォーマンスが出る。最も無難な構成。 |
| 大規模 DB (高トラフィックWebサービス) | NVMe SSD (PCIe接続) | RAID 10 (1+0) | 【速度・安定性重視】 I/Oボトルネックを極限まで排除する構成。NVMeの爆速性能+RAID 10の分散書き込みで、数万〜数十万IOPSを捌く。 |
| ファイルサーバ / アーカイブ (ログ保存、画像置き場) | 大容量 HDD | RAID 6 (または RAID 5) | 【容量単価重視】 書き込みは遅いが、安価に巨大な容量を確保できる。HDDは大容量化しているため、リビルド失敗のリスクを考慮してRAID 5よりRAID 6が安全。 |
| キャッシュ / 一時作業領域 (消えても良いデータ) | NVMe SSD | RAID 0 (ストライピング) | 【速度特化】 冗長性ゼロだが、書き込み・読み込み共に最速。データが飛んでも再生成できるキャッシュや、計算用の一時領域に限定して使用。 |
| クラウド環境 (AWS, GCP, Azure) | EBS / PD (ブロックストレージ) | RAID 0 (または設定なし) | 【クラウド流】 冗長化はクラウド側が保証しているため、OSレベルではRAID 1/5等は組まない。容量やIOPSを稼ぐために複数ボリュームをRAID 0で束ねることはある。 |
RAID0
graph TD
Data[書き込みデータ]
subgraph RAID0_Array ["RAID 0 アレイ (冗長性なし)"]
direction TB
Stripe["データをブロック単位で分割"]
Disk1[("Disk 1<br/>(Block 1)")]
Disk2[("Disk 2<br/>(Block 2)")]
Disk3[("Disk 3<br/>(Block 3)")]
end
%% Flow
Data --> Stripe
Stripe -->|"書き込み"| Disk1
Stripe -->|"書き込み"| Disk2
Stripe -->|"書き込み"| Disk3
%% Styling
style RAID0_Array fill:#ffebee,stroke:#ef5350
style Disk1 fill:#ffffff,stroke:#333
style Disk2 fill:#ffffff,stroke:#333
style Disk3 fill:#ffffff,stroke:#333RAID1
graph TD
Data[書き込みデータ]
subgraph RAID1_Array ["RAID 1 アレイ (冗長性あり)"]
Mirror["データを複製 (コピー)"]
Disk1[("Disk 1<br/>(データ A)")]
Disk2[("Disk 2<br/>(データ A)")]
end
%% Flow
Data --> Mirror
Mirror -->|"書き込み"| Disk1
Mirror -->|"書き込み"| Disk2
%% Styling
style RAID1_Array fill:#e8f5e9,stroke:#66bb6a
style Disk1 fill:#ffffff,stroke:#333
style Disk2 fill:#ffffff,stroke:#333RAID5
graph TD
Data[書き込みデータ]
subgraph RAID5_Controller ["RAID 5 コントローラ"]
Calc["データ分割 &<br/>パリティ(誤り訂正符号)計算"]
end
subgraph RAID5_Array ["RAID 5 アレイ (パリティ分散)"]
Disk1[("Disk 1<br/>[データA]")]
Disk2[("Disk 2<br/>[データB]")]
Disk3[("Disk 3<br/>[パリティP]")]
end
%% Flow
Data --> Calc
Calc -.->|"計算負荷(大)"| Calc
Calc -->|"書き込み"| Disk1
Calc -->|"書き込み"| Disk2
Calc -->|"書き込み"| Disk3
%% Annotation
Disk1 --- Disk2
Disk2 --- Disk3
style RAID5_Controller fill:#fff3e0,stroke:#ff9800
style RAID5_Array fill:#e3f2fd,stroke:#2196f3
style Disk3 fill:#fff9c4,stroke:#fbc02d,stroke-dasharray: 5 5RAID10
graph TD
Data[書き込みデータ]
subgraph RAID0 ["RAID 0 (ストライピング層)"]
Stripe["データを分割して分散書き込み<br/>(高速化)"]
end
subgraph RAID1_GroupA ["RAID 1 グループA (ミラー)"]
Disk1[("Disk 1")]
Disk2[("Disk 2")]
end
subgraph RAID1_GroupB ["RAID 1 グループB (ミラー)"]
Disk3[("Disk 3")]
Disk4[("Disk 4")]
end
%% Flow
Data --> Stripe
Stripe -->|"ブロック1"| RAID1_GroupA
Stripe -->|"ブロック2"| RAID1_GroupB
%% Mirroring details
RAID1_GroupA -.->|"複製"| Disk1
RAID1_GroupA -.->|"複製"| Disk2
RAID1_GroupB -.->|"複製"| Disk3
RAID1_GroupB -.->|"複製"| Disk4
style RAID0 fill:#e3f2fd,stroke:#2196f3
style RAID1_GroupA fill:#fff3e0,stroke:#ff9800
style RAID1_GroupB fill:#fff3e0,stroke:#ff9800第9章:検索エンジンのアーキテクチャ詳細
少し情報として古かったため書籍の内容を元に2026年1月現在利用可能なサービスの車輪の再発明はしない方針でリライトしています
1. なぜ「独自」で作る必要がないのか?(でも仕組みは知るべきか)
Googleやメルカリ級の規模、あるいは特殊なベクトル検索特化などの要件がない限り、独自実装はコストに見合いません。理由は単純で、Lucene(Elasticsearchのコアライブラリ)の完成度が高すぎるからです。
しかし、第9章で語られている**「バッファリングとマージ」や「スコアリング」**の仕組みを知らないと、以下のようなトラブルに対処できません。
- 「データを入れた直後に検索してもヒットしないのはなぜ?」
- 「大量更新をかけたら検索が激重になった(I/Oが死んだ)、なぜ?」
- 「”東京都”で検索しているのに、”東京”だけのドキュメントより順位が低い、なぜ?」
これらは全て、第9章の理論を知っていれば即答できる問題です。
2. 「バッファリングとマージ」の実戦応用
〜Elasticsearchの「セグメント」と「LSM Tree」を理解する〜
本書で言う「メモリ上の小さなインデックス」と「ディスク上の巨大なインデックス」の関係は、実務では Log-Structured Merge-Tree (LSM Tree) というデータ構造として実装されています。
アーキテクチャ図解 (Mermaid)
Elasticsearchが内部でどうやってデータを書き込んでいるかの図です。
graph TD
subgraph Write_Path [書き込みプロセス]
Doc[ドキュメント登録] --> Buffer["メモリバッファ<br/>(Indexing Buffer)"]
Buffer -- "Refresh (デフォルト1秒)" --> Segment1["小さなセグメント<br/>(検索可能・変更不可)"]
subgraph Disk [ディスク領域]
Segment1
Segment2[既存セグメントA]
Segment3[既存セグメントB]
end
%% '&' を使わず個別に記述(互換性向上)
Segment1 -- "Merge (定期実行)" --> BigSegment["マージされた巨大セグメント<br/>(物理削除・最適化)"]
Segment2 --> BigSegment
Segment3 --> BigSegment
end
search((検索リクエスト)) -.-> Segment1
search -.-> Segment2
search -.-> BigSegment
%% スタイリング
style Write_Path fill:#f9fbe7,stroke:#cddc39
style Disk fill:#eceff1,stroke:#cfd8dc
style Buffer fill:#fff9c4,stroke:#fbc02d
style BigSegment fill:#c8e6c9,stroke:#4caf50
style search fill:#e1f5fe,stroke:#039be5実務で使える知識:
- 「ニアリアルタイム」の正体 (Refresh Interval)
- メモリバッファからディスク(ファイルシステムのキャッシュ)に書き込まれて「検索可能」になるまでの時間を
refresh_intervalと言います(デフォルト1秒)。 - アクション: 大量データを一括投入(Bulk Insert)する際は、この設定を
-1(無効) または30sなどに伸ばしてください。バッファがあふれるたびに小さなセグメントが大量生産され、マージ処理でCPU/Diskが死ぬのを防げます。
- メモリバッファからディスク(ファイルシステムのキャッシュ)に書き込まれて「検索可能」になるまでの時間を
- マージのコスト (I/O Throttling)
- 増えすぎたセグメントはバックグラウンドでマージ(合体)されます。これは強烈なディスクI/Oを伴います。
- アクション: 検索が重い時、
iostatでディスク負荷を見てください。もし書き込みもしていないのにI/Oが高いなら、裏で巨大なマージが走っています。Elasticsearchの設定でマージスレッド数を制限する必要があります。
- 論理削除の罠
- インデックスは「追記型」です。削除リクエストを送っても、裏では「削除フラグ」がつくだけでディスク容量は減りません。マージ処理が走ったタイミングで初めて物理削除されます。
- アクション: 頻繁に更新・削除するインデックスは、見た目のレコード数よりディスク使用量が肥大化します。ディスク容量アラートが出たら
Force MergeAPIを叩いて強制的に掃除・圧縮することが解決策になります。
3. 「ランキングアルゴリズム」の実戦応用
〜TF-IDFの限界と、現代の標準「BM25」〜
実務では、純粋なTF-IDFはもう使われていません。現在は BM25 というアルゴリズムが標準です。これはTF-IDFの進化版です。
TF-IDFの弱点とBM25の改良点
- TF-IDFの弱点: 「単語の出現回数」に比例してスコアが無限に上がり続ける。
- 例:「寿司」が10回出る記事は、1回出る記事より10倍重要か? → 恐らく違う。ある程度で飽和するはず。
- BM25の改良: **「TF飽和(Saturation)」**という概念を導入。出現頻度が上がっても、ある地点でスコアの伸びが緩やかになります。
実務で即使える知識:
- 「短いフィールド」は強い (Field Length Norm)
- 同じ「Python」という単語でも、5000文字の記事に含まれる1回と、タイトル(30文字)に含まれる1回では、後者の方が圧倒的に重要です。検索エンジンはこれを自動計算しています。
- アクション: タイトル、見出し、本文でフィールドを分け、検索時に
title^3のように重み付け(Boosting)を行うのがSEOの基本です。
- Function Score Query の活用
- テキストの適合度(TF-IDF/BM25)だけで並べると、古い記事や品質の低い記事が上位に来ることがあります。
- アクション: 「適合度スコア」×「ビジネス指標(ページビュー数、商品売上、情報の鮮度)」を掛け合わせて最終スコアを決定します。
- Elasticsearchの
function_scoreクエリを使い、「1日古くなるごとにスコアを0.9倍する(Decay Function)」といった実装を入れるのが、ユーザー満足度を上げる鉄板テクニックです。
第10章:インフラの構築と自動化 (Provisioning)
ここでのテーマは、「サーバーを『愛着のあるペット』として扱うのをやめ、『交換可能な家畜』として扱え」 というパラダイムシフトです。
物理的なセットアップから、OSの中身の管理まで、徹底的に深掘りして解説します。
1. 「ペット(Pet)」から「家畜(Cattle)」へ
この章の根底にある哲学です。
- ペット(従来の運用):
- サーバーに「Zeus」「Apollo」など固有の名前をつける。
- 調子が悪くなったら、ログインしてログを見たり、再起動したり、薬をあげるように手厚く看病(修理)する。
- 問題点: 台数が100台を超えると、看病しきれずにサービスが死ぬ。
- 家畜(大規模運用):
- サーバーには「Web-001」「Web-002」と番号をつける。
- 調子が悪くなったら、修理せずにその場で廃棄(Terminate)し、新しい個体(Web-003)を即座に投入する。
- メリット: 個体に依存しないため、無限にスケールできる。
この「家畜」モデルを実現するために必須な技術が、以下の「自動インストール」と「構成管理」です。
2. 物理層の自動化:PXEブートとKickstart
「新品のサーバー100台をデータセンターに搬入した。OSをインストールせよ」と言われた時、1台ずつUSBメモリを挿して回るのは不可能です。これをネットワーク経由で全自動で行う仕組みです。
仕組みの図解 (Mermaid)
電源を入れたサーバーが、誰の手も借りずにOSがインストールされて起動するまでのフローです。
sequenceDiagram
participant Server as 新規サーバー(BIOS)
participant DHCP as DHCPサーバー
participant TFTP as TFTPサーバー
participant Repo as OSリポジトリ(HTTP/NFS)
Note over Server: 1. 電源ON<br/>(OSなし)
Server->>DHCP: 誰かIPアドレスをください!(DHCP Discover)
DHCP-->>Server: IPはこれを使って。あとブートローダはTFTPにあるよ。
Note over Server: 2. PXEブート開始
Server->>TFTP: ブートローダ(pxelinux.0)をください。
TFTP-->>Server: はい、どうぞ。
Note over Server: 3. インストーラ起動
Server->>Repo: OSのインストーラ(Kernel)をください。
Repo-->>Server: 起動します。
Note over Server: 4. Kickstart (自動回答)
Server->>Repo: インストール設定ファイル(ks.cfg)をください。
Note right of Repo: ここに「パーティション構成」「rootパスワード」<br/>「入れるパッケージ」等の回答が全て書いてある
Repo-->>Server: これに従ってインストールして。
Note over Server: 5. 完全自動インストール実行- PXE (Preboot eXecution Environment): ネットワークインターフェース(LANカード)だけでブートする規格。
- Kickstart: Red Hat系Linux(CentOSなど)のインストーラに対する「回答ファイル」。タイムゾーン設定、ディスクパーティション、初期パッケージなどを記述しておくと、インストーラが人間に質問せずに勝手に進めてくれます。
現代(クラウド)での対応:
AWSなどのクラウドでは、このプロセスは 「AMI(Amazon Machine Image)の作成」 や 「User Data(起動時スクリプト)」 に置き換わっています。概念は同じです。
3. 構成管理と「冪等性(Idempotency)」の魔法
OSが入った後、「Webサーバー(Nginx)を入れて、設定ファイルを置いて、起動する」という作業をコード化します。
ここで最も重要な概念が**「冪等性(べきとうせい)」**です。
冪等性とは?
数学用語で f(f(x)) = f(x) のこと。
エンジニア用語では「ある操作を1回やっても、100回やっても、結果が必ず同じ状態になること」を指します。
❌ 冪等性がないスクリプト (シェルスクリプトの例)
# apacheをインストールする
yum install -y httpd
# 設定ファイルの末尾に設定を追記する
echo "ServerName example.com" >> /etc/httpd/conf/httpd.conf
# 起動する
systemctl start httpd- 問題点: これを誤って2回実行すると、設定ファイルに
ServerNameが2行追記されてしまい、設定ファイルが壊れます。 怖くて自動実行できません。
⭕ 冪等性があるコード (Ansibleの例)
- name: Apacheがインストールされていることを保証する
yum:
name: httpd
state: present
- name: 設定ファイルにServerNameが記述されていることを保証する
lineinfile:
path: /etc/httpd/conf/httpd.conf
line: "ServerName example.com"
state: present
- name: Apacheが起動していることを保証する
service:
name: httpd
state: started- メリット: これを100回実行しても、ツールが「あ、もう書いてあるから何もしないでおくね」と判断します。結果は常に正常な状態に収束します。
- Ansible: 何もインストールしなくてOKです。 管理する側のPCから、SSH(Linuxに標準で入っている遠隔操作機能)を使ってログインし、命令を実行して帰ってきます。
- 必要な条件は「Pythonが入っていること」だけ(Linuxならほぼ確実に入っています)。
なぜこれが重要か?
サーバーが1000台あると、途中でネットワークが切れたりして処理が失敗する台が出てきます。
冪等性があれば、「失敗したかわからないから、とりあえずもう一回全員に流しておこう」 という雑な運用が可能になります。これが安心感の源泉です。
4. 現代のツールチェーンへの翻訳
書籍執筆当時からツールは進化していますが、考え方は継承されています。現在は以下の使い分けが主流です。
| レイヤー | 役割 | 書籍時代のツール | 現代の主流 (2026年) |
| Bootstrapping | OSを入れる | PXE / Kickstart | Cloud Init / Packer (イメージ作成) |
| Provisioning | インフラを作る | (手動 / AWS Console) | Terraform (インフラのコード化) |
| Configuration | 設定を入れる | Puppet / Chef | Ansible (エージェントレスで軽量) |
| Immutable | 状態を固定する | (概念のみ) | Docker / Kubernetes (コンテナ化) |
第11章:サービスの運用とモニタリング
〜推測ではなく計測で語る〜
- リソースモニタリング:
- CPU、メモリ、ディスクI/O、ネットワークトラフィックの可視化(MRTG, Cacti, 現在ならPrometheus/Grafana, Datadogなど)。
- サービスモニタリング:
- 「死活監視(Pingが通るか)」だけでは不十分です。「ユーザー体験(レスポンスタイム)」や「ビジネス指標(注文数、会員登録数)」を監視し、異常をいち早く検知する必要があります。
第12章:Webアプリケーションの設計
〜フレームワークの向こう側〜
- MVCとO/Rマッパーの功罪:
- O/Rマッパーは便利ですが、意識せずにループ内でデータ取得を行うと「N+1問題」を引き起こし、DBを過負荷でダウンさせます。発行されるSQLを常に意識(プロファイリング)する必要があります。
- リバースプロキシの活用:
- ApacheやNginxをアプリの前に配置し、静的コンテンツの配信、SSL終端、バッファリングを任せることで、アプリケーションサーバー(APサーバー)の負荷を大幅に削減できます。
第13章:データベースの水平分散(Sharding)
〜限界を超えるための最終手段〜
レプリケーション(参照分散)だけでは書き込み負荷に耐えられなくなった時の対応策です。
- シャーディング(Sharding):
- ユーザーIDなどをキーにして、データを物理的に異なるサーバー群に分割します。
- 課題: JOINができなくなる、トランザクション管理が複雑になる、運用コストが激増する。導入は慎重に行うべき「最後の切り札」です。
- コンシステント・ハッシング (Consistent Hashing):
- サーバーの追加・削除時に、データの再配置(リバランス)を最小限に抑えるアルゴリズム。キャッシュサーバー(Memcached/Redis)の分散において必須の技術です。
図解:コンシステント・ハッシングとシャーディング
graph TD
subgraph Sharding ["シャーディング (水平分割)"]
Router{"User ID % 2 ?"}
DB1[("Shard A (偶数)")]
DB2[("Shard B (奇数)")]
Router -->|"偶数"| DB1
Router -->|"奇数"| DB2
end
subgraph ConsistentHashing ["コンシステントハッシング"]
direction TB
ServerA((Server A))
ServerB((Server B))
ServerC((Server C))
Data1[Key: User1]
ServerA --- ServerB
ServerB --- ServerC
ServerC --- ServerA
Data1 -.->|"時計回りに探索"| ServerA
end
style Sharding fill:#e3f2fd,stroke:#2196f3
style ConsistentHashing fill:#f9fbe7,stroke:#cddc39第14章:サービスの安定性と冗長化
〜SPOFを排除する〜
- SPOF (Single Point of Failure):
- システム構成図の中で、「そこが壊れたら全停止する」箇所(単一障害点)を排除します。
- フェイルオーバーの仕組み:
- HeartbeatやVRRP(Virtual Router Redundancy Protocol)を用いて、主系サーバーがダウンしたらミリ秒単位で副系にIPアドレス(VIP)を引き継ぐ設計を行います。
graph TD
User((User))
subgraph Network ["仮想IP (VIP) の動き"]
VIP(("VIP: 192.168.0.1<br/>(浮動IP)"))
end
subgraph Servers ["サーバー構成"]
Master["Master Server<br/>(実IP: .101)"]
Backup["Backup Server<br/>(実IP: .102)"]
end
%% Normal State
User ==>|"アクセス"| VIP
VIP -.->|"通常時はMasterへ"| Master
%% Heartbeat
Master <-->|"Heartbeat (死活監視)"| Backup
%% Failover
Master -.->|"× 故障発生"| Fail[障害]
VIP -.->|"瞬時に切り替え (Failover)"| Backup
style VIP fill:#ffeb3b,stroke:#fbc02d,stroke-width:2px
style Master fill:#a5d6a7,stroke:#2e7d32
style Backup fill:#cfd8dc,stroke:#607d8b
linkStyle 4 stroke:red,stroke-width:2px;第15章:これからの大規模サービス技術
〜クラウドと仮想化の時代へ〜
- 仮想化技術の進化:
- 物理サーバーから仮想化(Virtualization)、そしてクラウドへのパラダイムシフト。
- ハードウェアを意識しなくて済む時代だからこそ、逆に「下回りで何が起きているか(OS、ディスクI/O、ネットワーク)」を知っているエンジニアが、トラブルシューティングやパフォーマンスチューニングにおいて圧倒的な優位性を持ちます。
エンジニアへの3つのキーメッセージ (後半編)
- 「N+1問題を殺せ」Webサービスの遅延原因のトップクラスは、非効率なSQLです。フレームワークのログを有効にし、1リクエストで大量のSQLが発行されていないか、Slow Query Log に長いクエリが出ていないかを確認してください。
- 「冪等性(べきとうせい)を愛せ」インフラ操作やAPI設計において、「何度リトライしても安全」な設計は神です。ネットワークは必ず失敗します。失敗した時に安全にリトライできる設計が、深夜の緊急呼び出しからあなたを救います。
- 「単一障害点 (SPOF) に怯えろ」システム図を書き、「この1台が燃えたらどうなる?」と自問してください。DBのMaster、ロードバランサ、共有ストレージ。SPOFを見つけたら、それを冗長化することが次の四半期の最優先タスクです。
明日から使えるアクションプラン
読者が直ちに実践できる具体的なステップです。
- 「スロークエリログ」の設定時間を見直す
- MySQLなどの
long_query_time設定を確認してください。もし「10秒」などになっているなら、「0.5秒」や「1秒」まで下げて、隠れている「プチ重いクエリ」を炙り出し、インデックスを検討しましょう。
- MySQLなどの
- アプリケーションのログに「実行時間」を含める
- Webサーバーのアクセスログに、リクエスト処理にかかった時間(Time Taken)を出力するように設定してください。平均値ではなく、99パーセンタイル値(遅い方の1%)を見ることで、ユーザーの痛みがわかります。
- 手作業の手順書を「スクリプト」にする
- 「手順書を見ながらコマンドを打つ」作業が一つでもあるなら、それをシェルスクリプト化するか、Makefileなどにまとめてください。それがIaC(Infrastructure as Code)への第一歩です。

コメント