> For the complete documentation index, see [llms.txt](https://ayakaleaf-pro.ayaka.space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ayakaleaf-pro.ayaka.space/blog/ja/2026/overleaf-benchmark.md).

# Overleafベンチマーク：Overleafにおける同時LaTeXコンパイルの深掘り調査（SaaSおよびセルフホスト）

{% file src="/files/69c37d8ed615190f2101c6317859bbd1ef4ddc83" %}

## 要旨

セルフホスト型の Overleaf 導入では、一般に「同時ユーザー 5〜10 人あたり CPU コア 1 個、メモリ 1 GiB」という一つの経験則で規模を決める。本稿では、この規則は単に不正確なだけでなく構造的にも誤っていることを示す。というのも、容量は単一の資源次元で決まると仮定しているが、実際には独立した 2 つの壁が支配し、しかも 2 つのソフトウェア・パラメータ — どちらもハードウェアではない — が結果を最大 4 倍も左右するからである。

QEMU/KVM ゲスト上で、ホストコアを 3.0 GHz にクロック固定した 21 通りの CPU/メモリ構成にわたり、標準の Ayakaleaf Pro v6.2.2 導入をサンドボックス化コンパイル（TeX Live 2025）付きで測定した。ワークロードは実在の 63 ページの XeLaTeX 学位論文で、数百に及ぶ異なるユーザーアカウントから同時にコンパイルされる。本稿では、ゲストメモリが 32 GiB 未満ではコア数はほぼ無関係であり — 16 GiB では 4、8、16 vCPU ゲストの測定容量の差は 8% 未満である —、その代わり容量は TeX Live ツリー上の共有ページキャッシュに起因する超線形のメモリ壁によって支配されることを示す。

これらの法則が 1 桁のスケール変化を経ても成り立つかを確かめるため、単一の 64 コア・995 GiB サーバーで同じスイープを繰り返した。このサーバーは、スレッド数の 8 倍にあたる 1024 個の同時コールドコンパイルを 100% の成功率で処理でき、我々はその上限に達しなかった。重要なのはその上限ではなく、その手前の屈曲点である。テールレイテンシは 20〜40% ずつ増加し、〜まで $$N=256$$、その後は 190% $$N=512$$したがって、「失敗しない最大同時実行数」として報告された容量は、実際に使える動作点を 4 倍も過大評価することになる。そのマシンではメモリは決して制約資源ではなく、制限は CPU と、コンテナデーモンが新しいサンドボックスを受け入れられる速度であり、この速度は要求されるコンパイル数に関係なく 200 付近で飽和する。

さらに、容量計画では見えない実装レベルの効果を 2 つ特定した。第 1 に、CLSI はハードコードされた 65 個の同時コンパイル上限を課しており、これはどの環境変数からも公開されていない。その上限を超えると、ユーザーはキューに入れられるのではなく即座に HTTP 503 を受け取る。第 2 に、Docker ランナーのコンテナごとのメモリ上限は 2018 年の導入以来、値の大きさも適用箇所も機能していないため、メモリ不足イベントは 1 つのコンパイルではなくホスト全体を落とす。並行実行上限を引き上げ、既定のコンパイルタイムアウトを 180 秒から 300 秒へ伸ばすと、8 vCPU / 48 GiB ゲストの測定容量は 64 から 268 同時コンパイルへ増加する — ハードウェアコスト 0 で 4.2 倍である。

最後に、このシステムにおける同時実行は単なるタイムシェアリングにすぎず、ワークロードはクロックだけで上限づけられることを示す。推定した劣化則 $$T(N)=T\_1\max(1,N/C)^{b}$$ は $$b=0.914$$は、ほぼ完全な比例的低下を与え、機械の 1.0〜5.5 GHz の全範囲にわたるクロックスイープは 30 個の測定値を〜へ収束させる $$T=(k/f)\max(1,N/C)$$ で $$k=27.9 GHz·s$$ 残差のばらつきは 5.1% であった。5.5 倍のクロックは 5.5 倍の高速化をもたらし、逓減効果はない。クロックとコアが異なるものをもたらす、というのはこの意味である。クロックは各ユーザーのコンパイルを高速化し、コアはより多くのユーザーを受け入れるだけである。

## 1. はじめに

Overleaf は支配的な共同 LaTeX エディタであり、そのオンプレミス版は、未公開原稿を第三者のクラウドに送れない大学や研究グループで広く導入されている。この種の導入規模の決定は繰り返し生じる実務上の問いである。固定されたハードウェア予算のもとで、実際に何人が同時に「再コンパイル」を押せるのか。

公式の指針は線形の規則であり — おおむね同時ユーザー 5〜10 人あたり 1 コアと 1 GiB —、容量は両資源の双方で滑らかかつ共同にスケールすると仮定している。我々の測定は、これに 3 つの点で反する。

### 1.1 容量は 1 つではなく 2 つの独立した壁によって支配される

構成は、メモリが枯渇して Overleaf スタック自体が落ち HTTP 502 を返すか、あるいはコンパイルがサーバー側タイムアウトを超え、その場合 CLSI が〜と報告するかのいずれかで失敗する `タイムアウト` 一方で、ギガバイト単位のメモリは使われないまま残る。 *低下させる* 容量を。というのも、コア数が増えると同時コンパイルが歩調を合わせて進み、ピークメモリ需要が時間的にずれずに一致してしまうからである。

### 1.2 ソフトウェア・パラメータがハードウェアを支配する

コンパイルタイムアウトは MongoDB のユーザーごとのフィールドで、既定値の 180 秒が CPU バウンドな構成を静かに上限づけている。これを 300 秒に引き上げると、ハードウェアを変えずに測定容量は最大 4.2 倍になる。さらに別途、CLSI はハードコードされた定数により 65 個を超える同時コンパイルを拒否する。これら両方を考慮しない容量調査 — そして導入 — が測っているのは機械ではなくソフトウェアである。

### 1.3 同時実行は並列処理ではなくタイムシェアリングである

LaTeX のコンパイルは単一スレッドであるため、 $$N$$ 上の同時ユーザーを $$C$$ コアで処理してもシステムが早く終わるわけではなく、各ユーザーの待ち時間が比例して長くなるだけである。したがって、「何人の同時ユーザーをサポートできるか」という問いは、ユーザーがどれだけ待てるかを定めるまで適切ではない。我々はこの依存関係を明示し、定量化する。

### 1.4 貢献

* クロック固定・再現確認済みの条件下で測定した 21 通りの CPU/メモリ構成にわたる容量行列。各構成について、失敗シグネチャから支配的制約を特定した。
* 2 つの適合モデル。超線形のメモリ壁を CPU 上限から切り分ける容量モデルと、純粋なタイムシェアリング挙動を示すレイテンシモデル。
* 稼働システムにおける 2 つの実装上の問題の特定と実験的確認。うち 1 つは 2018 年以来機能していないコンテナメモリ制限である。
* コンパイルタイムアウトと容量のトレードオフの定量化。これらは、いかなる同時実行数の数値と併記されるべきだと我々は主張する。

## 2. 背景

### 2.1 コンパイル経路

Overleaf のコンパイル要求は〜をたどる `web` $$\rightarrow$$ `clsi` $$\rightarrow$$ コンパイルコンテナに至る。サンドボックス化コンパイルの導入では（`SIBLING_CONTAINERS_ENABLED=true`）CLSI は〜を実行しない `latexmk` 同一プロセス内ではなく、バインドマウントされたソケット経由で到達可能なホストの Docker デーモンに、新しいコンテナを TeX Live イメージから起動させ、プロジェクトディレクトリを〜にバインドマウントするよう依頼する `/compile`。したがって 1 回のコンパイルは、1 つの短命なコンテナが 1 つの `latexmk` プロセスを実行することに相当する。

3 つの帰結があり、その 3 つすべてが本稿の測定を形作る。第 1 に、作業単位は単一スレッドのプロセスである。XeLaTeX は並列化しない。第 2 に、1 回のコンパイルあたりの資源分離は Docker ランナーが要求する内容次第であり、§6.2 で示すように実質的には何も要求していない。第 3 に、ワーキングセットを支配するのは文書ではなく TeX Live ツリーである。これは約 32 GiB の読み取り専用コーパスであり、各同時コンパイルがそこから読み出すため、ホストのページキャッシュを通じて共有される。この共有が、我々が観測する超線形メモリスケーリングの起源である。

### 2.2 サンドボックス化コンパイルを有効にする

コミュニティ版 Overleaf は〜で動作する `latexmk` アプリケーションコンテナ自体の内部で動作する。Ayakaleaf Pro は Overleaf Server Pro と同様に、各コンパイルを代わりに〜で実行できる *シブリング* コンテナ — すなわちアプリケーションが〜上の *ホストの* Docker デーモンによって起動するコンテナであり、アプリケーションコンテナの内部に入れ子にはしない。これを有効化するツールキット設定は 2 つある。

```ini
# config/overleaf.rc
SERVER_PRO=true
SIBLING_CONTAINERS_ENABLED=true
DOCKER_SOCKET_PATH=/var/run/docker.sock

# config/variables.env
TEX_LIVE_DOCKER_IMAGE=ghcr.io/ayaka-notes/texlive-full:2025.1
ALL_TEX_LIVE_DOCKER_IMAGES=ghcr.io/ayaka-notes/texlive-full:2025.1
```

ツールキットはホストの Docker ソケットをアプリケーションコンテナ内にバインドマウントし、これらを CLSI が読む環境変数に変換する。 `SANDBOXED_COMPILES=true`, `SANDBOXED_COMPILES_SIBLING_CONTAINERS=true`、および `SANDBOXED_COMPILES_HOST_DIR`、その最後のものが *ホストの* コンパイルディレクトリのパスである。このパスが重要なのは、コンパイルコンテナを起動するデーモンがホスト側のものなので、与えられるバインドマウントはアプリケーションコンテナの名前空間ではなくホストの名前空間で解決可能でなければならないからである。Server-Pro の `config/env.sh` さらに強制する `TEXLIVE_IMAGE_USER=www-data` をこのモードで有効にし、コンパイルコンテナが書き込むファイルの所有者を一貫させる。

検証は直接的である。コンパイル中、ホストには〜という名前のコンテナが表示される `project-{projectId}-{userId}-{hash}` が実行中である `latexmk` TeX Live イメージから起動され、終了コード 0 で終了する。これが本稿全体でその個数を測定する単位であり、資源制限が完全に存在しないことを §6.2 で報告する。

**この研究でこれが重要なのはなぜか。**

シブリングコンテナは測定を明快にする — 各コンパイルは観測可能で独立にスケジュールされる OS 実体だからである — が、同時に、コンパイル間の CPU とメモリの配分を Overleaf ではなくゲストカーネルが仲裁することも意味する。したがって、本稿のすべてのスケーリング則は、Linux スケジューラが〜に適用されたときの性質である $$N$$ 単一スレッドプロセスに対するものであり、そのためこれほど規則的なのである。

<figure><img src="/files/2017120ff8cb5401b98f7e846a9337a1c4e522a5" alt=""><figcaption></figcaption></figure>

**図1.** コミュニティ版のマイクロサービスを経由して追跡した 1 回のコンパイル要求。steps と matters での分岐は容量に関わる。文書テキストはリクエスト本文にコピーされる一方、バイナリアセットは参照渡しされ、〜によって取得される `clsi`。どちらも支配的ではない。プロジェクトのコンパイルコストは、同時コンパイルごとに共有ページキャッシュ経由で読み込まれる 32 GiB の TeX Live ツリーによって決まる。

<figure><img src="/files/77e8246c400b22eaea5bb2619dcbc058241440ab" alt=""><figcaption></figcaption></figure>

**図2.** 3 つの導入トポロジーと、それぞれでインスタンスごとのコンパイル上限がどこに落ちるか。積み重ねられたパネルはレプリケーションを示す。65 コンパイルの定数が守るのは *1 つの* CLSI である。そのため SaaS フリートではインスタンス数とゾーン数でこれが掛け算される (a) が、Server Pro と Ayakaleaf Pro が支える水平スケーリングではインスタンス数で掛け算される (c) — その代償として、中央の MongoDB、Redis、S3 互換ストレージ、クッキーセッションアフィニティを持つロードバランサー（コンパイル出力はインスタンスローカルのディスクに書かれるため、コンパイルとその後の PDF ダウンロードは同じインスタンスに到達しなければならない）、および単一の `git-bridge`。ツールキットの既定値 (b) — 我々が測定するのはこれである — の倍率は 1 なので、フリートの 1 台向けに設定された定数が導入全体の上限になる。

<figure><img src="/files/29324b78fbd2cd8a6081b1f630d77281dc260221" alt=""><figcaption></figcaption></figure>

**図3.** 〜におけるシャード選択 `clsi-cache`。プロジェクトは〜によってマッピングされる $$\operatorname{crc32}(\text{projectId}\text{-}i)\bmod|\text{shards}|$$、すなわちハッシュ空間はシャード数と同数の等しい区画に分割される。これは *剰余* ハッシュ法であり、リングベースの一貫性ハッシュではない。フリートを 3 シャードから 4 シャードへ増やすと空間全体が再分割され、実質的にすべてのプロジェクトが再マップされる (a, b)。まさにそのため、実装には明示的なオンライン再シャーディングの移行が必要であり、直線的に増える割合のプロジェクトを〜から移す `currentShards` から `desiredShards` 時間窓にわたって行う必要がある。これは、〜ではなく $$K/n$$ 一貫性ハッシュのリングなら得られる移動である。シャードの回路遮断器が作動すると、salt は $$i$$ 増分され、そのシャードは候補リストから除外されるため、ルックアップは失敗する代わりに次へプローブを進める (c)。

### 2.3 2 つの失敗モード

測定した各構成の失敗の仕方はちょうど 2 通りのどちらかであり、その違いは推測ではなく応答ステータスに現れる。

* **メモリ枯渇** — Overleaf スタック自体が応答不能になり、要求は〜を返す **HTTP 502**。失敗したレベルでの利用可能なゲストメモリは通常 500 MiB 未満である。
* **コンパイルタイムアウト** — CLSI はユーザーごとのタイムアウトでコンパイルを終了し、ステータスを報告する `タイムアウト`。失敗したレベルでの利用可能メモリはしばしば数 GiB に及ぶ。

我々は、資源比に関するヒューリスティックではなくこのシグネチャで各構成を分類する。これにより、「どちらの壁に当たったか」という問いにデータ自体から答えられる。

## 3. 方法

### 3.1 テストベッドとクロック制御

すべてのゲストは、62 GiB の RAM と NVMe ストレージを備えた単一の Intel Core i9-14900K ホスト上で QEMU/KVM により実行される。ゲストは Ubuntu 24.04、Docker 29.7、そして Overleaf Toolkit により、〜に対してサンドボックス化コンパイル付きの Ayakaleaf Pro v6.2.2 を導入している `texlive-full:2025.1`.

クロックを制御しない限り、一般的なデスクトップ CPU はサーバーの代理としては不十分である。KVM には仮想クロックを設定する仕組みがない。vCPU はホストのスレッドであり、ホストコアが動作する周波数でそのまま動く。そこで我々はホストを直接制約し、ターボを無効化して〜を固定する `scaling_max_freq` 各コアを 3.0 GHz にし、ゲストの vCPU を物理 P コアに〜で固定する `taskset`。 *〜できない* ターボを無効にすると 3.0 GHz に到達できないため、そこに乗ってしまった実行は、気づかれないまま遅いマシンを測定することになる。フルロードでは、固定した 16 スレッドすべてで 3000 MHz  בדיוק を確認する。ガードスクリプトは各ベンチマーク前にこの不変条件を検証し、満たさない場合は起動を拒否する。本研究中に、このスクリプトが governor の静かなリセットを 1 回検出した。

### 3.2 2 つ目のテストベッド: 1 台の大規模ランナー

QEMU の行列は 1 変数ずつ分離するが、固定できるスレッド数は 16 までである。同じ法則が 1 桁大きくなっても成り立つかを確かめるため、同じ Ayakaleaf Pro v6.2.2 イメージを同じ〜に対して実行する単一の大規模サーバーで、同時実行スイープを繰り返した。これは 1 台の AMD EPYC 7773X（Milan-X、64 コア / 128 スレッド、768 MiB の L3）で、995 GiB の RAM を搭載している。QEMU ゲストとは異なり、このマシンはクロック固定されていない。生産クラスのサーバーであり、1 台として測定する。 `texlive-full:2025.1`。QEMU ゲストとは異なり、このマシンはクロック固定されていない。生産クラスのサーバーであり、1 台として測定する。

2 つの運用上の予防策が必要であり、明記する価値がある。これらがないと、実験はサーバーではなくハーネスを測ってしまうからである。まず、すべてのコンテナは〜に閉じ込められた `systemd` スライスに `MemoryMax=940 GiB`、暴走したスイープがホストではなく cgroup を枯渇させるようにした。第二に、サンドボックス化コンパイルはホストのデーモンによって作成され、各コンテナはそれぞれ独自のコピーオンライト層を汚す — 20.6 GiB のベースイメージが共有されていても 1 コンテナあたり 116 MiB と測定された — ため、Docker のデータルートを専用 NVMe デバイスへ移した。〜でのスイープは $$N=1024$$ およそ 119 GiB のスクラッチ層を書き込み、標準のルートファイルシステムには収まらない。

### 3.3 ワークロード

文書は実在の 63 ページの修士論文（SJTU テンプレート）で、XeLaTeX により〜を介してコンパイルされている `latexmk`、TikZ 図を含み、 `biblatex` 文献処理および埋め込み PDF アセットを含む — すなわち、合成ではなく現実的な負荷である。負荷のないゲストでの単一コンパイルは、すべての構成で 8.6〜9.8 秒かかり、これを無負荷ベースラインとして用いる $$T\_1$$.

### 3.4 負荷生成

我々は 512 個の実在ユーザーアカウントを作成し、それぞれにプロジェクトの独立したコピーを与える。これにより、同時コンパイルはプロジェクトロックを共有するのではなく、独立ユーザーと同じように正確に競合する。要求はホストからゲストの転送ポートに対して発行されるため、負荷生成はゲスト CPU を消費しない。

同時実行は *同時*ではなく段階的ではない。各セッションはまず確立される — ログイン、CSRF トークン、コンパイラ選択 — その後、各スレッドは一度だけ計算され共有された共通の実時刻まで待機してから、〜を発行する `POST /project/:id/compile`。この違いは些末ではない。段階的なランプは安定したキュー下でのスループットを測る。一方、同時バーストは、講義室の学生たちが同じ締め切り告知の後に同じボタンを押したときに何が起こるかを測る。運用者が実際に恐れるのは後者である。後者はデーモンが排出できる速度よりも速くコンパイルキューを埋めるため、2 つは定数倍を超えて異なる。

そのバーストを忠実に実行する前に、4 つの実務上の障害を取り除く必要があった。いずれも記録する価値がある。なぜなら、どれも気づかれないうちに実験をサーバーではなくハーネスの測定に変えてしまうからである。

#### 3.4.1 レート制限は 1 つではなく 2 つ

Overleaf は送信元アドレスごとにログインを抑制する — 1 分あたり 20 回まで — そして我々のトラフィックはすべて 1 台のホストから発生する。各シミュレートユーザーに異なる `X-Forwarded-For` アドレスを割り当てればこの制限は外れるが、すぐに 2 つ目のより粗い制限にぶつかる。サブネットごとの予算はおおむね 1 分あたり 200 だからである。したがって、連続したブロックにユーザーを分散させると 201 番目のアカウントで失敗する。そこで我々は、連続するユーザーが別々のサブネットに入るよう、ユーザーインデックスから合成アドレスを生成する `/24`別々のサブネットに

$$
\texttt{203.};\big\lfloor i/250 \big\rfloor \bmod 100 + 1\texttt{.};
i \bmod 250 + 1\texttt{.}; i \bmod 200 + 10 ,
$$

これにより、1024 全員に対して両方の制限に余裕を持たせられる。

#### 3.4.2 挿入したヘッダーは既定で破棄される

ヘッダーを設定するだけでは不十分である。Express が信頼するよう指示されたピアに対してのみ `X-Forwarded-For` を認めるのであり、Overleaf の `trustedProxyIps` ループバック `ループバック`。 `HTTP 429` ちょうど 20 回目のログインで発生し、サーバー過負荷と誤読しやすい。ゲートウェイネットワークは明示的に信頼チェーンに追加しなければならず、§4.3 のクラスタ導入では pod と service の CIDR も追加する必要がある。

#### 3.4.3 ロードバランサーは保持するよう指示されたヘッダーを上書きする

インスタンスがプロキシの背後にあるとき、一般的な `option forwardfor` *追加する* 実際のクライアントアドレスをチェーンに追加する。これは本番では正しい挙動だが、ここではまさに誤りである。合成アドレスがロードジェネレータ自身のアドレスに置き換わってしまうからだ。ディレクティブは次のように指定しなければならない `option forwardfor if-none`、つまりクライアントが値を送ってこなかった場合にのみプロキシが値を追加する。

#### 3.4.4 サーバーの容量が尽きる前にクライアントのファイルディスクリプタが尽きる

〜では $$N=1024$$ その生成器は 1000 を超える同時ソケットを保持しており、既定のソフト上限 1024 個のディスクリプタには測定中ではなくセッション設定中に到達する。失敗は静かである。3 つのセッションの確立に失敗し、実行結果は 1024 ではなく 1021 を報告する一方、コンテナ数を取得するためにシェルアウトするサンプリングスレッドは〜で死ぬ `EMFILE` そしてテレメトリを気づかれないうちに切り詰める。ソフト上限は生成器側で引き上げる必要がある — 我々のホストのハード上限はすでに 1048576 だった — ため、実行を繰り返した。§4.3 では両方の実行を報告する。修正版は 1024/1024 を完了し、その中央値は切り詰め版と 1.2 秒以内で一致する。したがって、前者は有用だが権威的ではないとみなす。

### 3.5 測定プロトコル

再現性のため、いくつかの方法論上の選択が必要であることが分かった。

#### 3.5.1 ウォームアップ

起動直後のゲストではページキャッシュが空であり、最初のコンパイルは定常状態の容量ではなくコールドスタート I/O を測る。同じ 2 vCPU / 2 GiB 構成でも、コールドでは 36.5 秒、ウォームでは 9.8 秒となり、3.7 倍である。したがって各構成は起動後に 2 回、単一コンパイルのウォームアップを捨てる。

#### 3.5.2 合格基準

同時実行レベルが合格するのは、 *すべての* コンパイルが成功し、かつそのレベルが再試行に耐える場合に限る。これは成功率しきい値より厳しく、重要である。4 vCPU / 16 GiB では 32 のレベルが中央値 80.2 秒で 1 回は合格したが、繰り返すと 32 件すべてのコンパイルがタイムアウトしたため、我々は 31 を報告する。

#### 3.5.3 探索

レベルは、モデルが予測したシードから指数的ブラケットを行い、その後に厳密な整数二分探索で決定する。基準が全か無かであるため、レベルは最初の失敗で決まり、1 つ失敗した時点で残りの進行中要求は打ち切る — ただし小さなレベルでは、打ち切ったコンパイルが小さなゲストをひどく固定してしまい、そこから回復しなくなる。

#### 3.5.4 レベル間の分離

次のレベルを始める前に、コンパイルコンテナを空にし、Web アプリケーションが再び応答するまでポーリングする。これがないと、クラッシュ後のレベルが誤ってゼロセッション失敗として記録される。

#### 3.5.5 ホストの健全性

ホスト上の無関係な仮想マシンは停止した。ホストメモリ 24 GiB が他に割り当てられていると、同じゲスト構成でも、同一の同時実行数で load average は 3.2 ではなく 11.7 を示した。ホスト側のメモリ圧迫はゲストに伝播し、測定を無効にする。

## 4. 結果

### 4.1 容量行列

表 1 と図 4 は、各構成で測定された上限を示す。行方向に読むと最初の驚きがある。4 GiB では 2、4、8 vCPU のゲストはいずれも正確に 9 に達し、コアを 4 倍にしてもまったく変わらない。16 GiB では 54、45、57 に達する。4 コアから 16 コアへの増加で得られるのは 6% であり、8 コアのゲストは実際には *より悪い* 4 コアのものよりも (§5.2)。48 GiB になって初めて、コア数が構成を निर्ण然と分ける。143、268、331 である。

<figure><img src="/files/a1ee2a3c39fb4dd6dc3373c5b3375d8a68932c66" alt=""><figcaption></figcaption></figure>

**図4.** 構成行列全体での測定容量。(a) 各構成をバーで示し、メモリごとにグループ化し、コア数で色分けした。塗りつぶしバーはメモリバウンド（メモリ枯渇でゲストが死ぬ）、ハッチバーは CPU バウンド（メモリに余裕があるのにコンパイルがタイムアウトする）である。グループを左から右へ読むと、16 GiB 未満ではコア数の効果がいかに小さいかが分かる。グループ間を読むと、メモリの超線形な伸びが分かる。(b) 適合モデルに対する同じ点 $$N\_{\max}=\min(0.69R^{1.60},,26.4C)$$；破線はメモリ壁、点線の水平線はコア数ごとの CPU 上限である。構成は、この 2 つのうち最初に当たった方に制約される。

列方向に読むと 2 つ目のことが分かる。コア数を固定すると、容量はメモリとともに超線形に増え、おおよそ〜として $$R^{1.6}$$増加する。これは §5.1 で述べるページキャッシュの理由による。

|    メモリ | 2 vCPU |  4 vCPU |  8 vCPU | 16 vCPU |
| -----: | -----: | ------: | ------: | ------: |
|  2 GiB |      1 |       1 |       1 |       — |
|  3 GiB |      4 |       5 |       6 |       — |
|  4 GiB |      9 |       9 |       9 |       — |
|  8 GiB |     21 |      22 |      26 |       — |
| 16 GiB |      — |      54 |  **45** |      57 |
| 32 GiB |      — | **145** |     135 |     141 |
| 48 GiB |      — | **143** | **268** | **331** |

**表1.** CLSI の同時実行上限を解除し、コンパイルタイムアウトを 300 秒にした状態で測定した、正常終了する最大同時コンパイル数。 **太字** は CPU バウンドな構成を示す（メモリに余裕があるのにコンパイルがタイムアウトする）。それ以外はメモリバウンドである（スタックが HTTP 502 で死ぬ）。2 GiB 行には §5.2 で述べる補正が反映されている。

### 4.2 同時実行はタイムシェアリングである

図 5 は、固定した 8 vCPU / 16 GiB ゲスト上であらゆる同時実行レベルをスイープする。2 つのレジームは、ちょうど 1 コアあたり 1 コンパイルの鋭い屈曲点で分かれる。それより下では平均コンパイル時間は平坦である — 8.7 秒から $$N=1$$ 9.1 秒へと $$N=C=8$$5% の変化である。 $$N/C$$それを超えると、時間は〜に厳密に比例して増加する $$N=16,24$$ 我々は 18.5 秒と 27.1 秒を測定し、比は $$1:2.13:3.12$$ 理想的な〜に対して $$1:2:3$$.

<figure><img src="/files/2e6faf4d1318fdca81331887afcc1774f67a79c3" alt=""><figcaption></figcaption></figure>

**図5.** 固定ハードウェアにおける同時実行数に対するコンパイルレイテンシ。屈曲点は〜にある $$N=C$$；それを超えると、測定された遅延は〜に追随する $$N/C$$ 5〜7% 以内である。15 レベルすべてが完全に成功した。

<figure><img src="/files/d4eb83e65254f09991c665101a574eb39abb09b4" alt=""><figcaption></figcaption></figure>

**図6.** いくつかの構成における同時実行数に対するコンパイルレイテンシ。各パネルではハードウェアを固定し、提示負荷をスイープする。縦線は〜を示す $$N=C$$。曲線はその左では平坦で、右では〜に対して線形である $$N/C$$ 。これは競合ではなくタイムシェアリングの特徴である。作業が高くつくようになるのではなく、順番待ちをするだけである。

当てはめると $$T(N)=T\_1\max(1,N/C)^{b}$$ 本研究のすべての成功測定について $$b=0.914$$ ($$R^2\_{\log}=0.904$$, $$n=81$$）。指数が 1 と区別できないということは、コンパイルが単一スレッドの CPU バウンドな作業単位であり、同時実行はコアを分ける以上には助けにも妨げにもならないことを定量的に示している。実務上の帰結は容量計画にとって厄介である。構成は、〜なしに任意の数のユーザーを吸収できる *失敗することなく* 一方で、全員を比例して長く待たせる。〜では $$N=56$$ このゲストではすべてのコンパイルが依然として成功するが、各ユーザーの待ち時間は 8.7 秒ではなく 64.8 秒になる。

### 4.3 1024 同時コンパイルへの垂直スケーリング

表 2 と図 7 は、大規模ランナー上でのスイープを報告する。各レベルは *コールド* コンパイルである。各レベルの前に、以下を介してコンパイルディレクトリと参加する各プロジェクトの CLSI キャッシュを消去する `DELETE /project/:id/output`、そのため下位レベルで行われた作業の恩恵を受けるレベルはない。このマシンでの単一コンパイルのベースラインは 28.8 秒で、これはコールド値であり、先に用いた 8.6〜9.8 秒の定常状態ベースラインと比較すべきではない。QEMU ゲストでのコールドベースラインは 28.3 秒なので、このワークロードではスレッド当たりで両マシンの差は 2% 以内である。

| $$N$$ |        成功 | $$p\_{50}$$ | $$p\_{95}$$ | $$p\_{50}/T\_1$$ | ピークコンテナ数 |
| ----: | --------: | ----------: | ----------: | ---------------: | -------: |
|    64 |     64/64 |      55.0 秒 |      55.1 秒 |             1.9× |        — |
|   128 |   128/128 |      73.6 秒 |      74.4 秒 |             2.6× |        — |
|   192 |   192/192 |      80.2 秒 |      87.3 秒 |             2.8× |        — |
|   256 |   256/256 |      69.8 秒 |     121.2 秒 |             2.4× |      151 |
|   512 |   512/512 |     179.2 秒 |     344.6 秒 |             6.2× |      205 |
|  1024 | 1024/1024 |     280.2 秒 |     468.9 秒 |             9.7× |      179 |

**表2.** 1 台の EPYC 7773X（64 コア / 128 スレッド、995 GiB）での同時実行スイープ。すべてのレベルはコールド。ベースラインは 28.8 秒。ピークコンテナ数は、同時に生存しているサンドボックスの最大数である。

<figure><img src="/files/03c7f826fec0beefca9f298465424dc0a0279c5e" alt=""><figcaption></figcaption></figure>

**図7.** 1 台の大規模ランナーにおける垂直スケーリング。(a) 提示同時実行数に対するレイテンシ。陰影部は屈曲点を越えたレジームを示す。(b) 実際に生存しているサンドボックス数は要求数に追随せず — 200 付近で飽和する — 一方、コンパイル cgroup は上限の 5 分の 1 以上を決して使わない。

#### 4.3.1 このマシンは決して失敗しない

すべてのレベルが 100% で完了する。〜を含めて $$N=1024$$ — スレッド数の 8 倍である。我々はこのマシンの容量上限を見つけられなかった。余裕が尽きる前にこちらの忍耐が尽きたからである。これは、本研究で制約条件がメモリではない最初の構成である。〜では $$N=1024$$ コンパイル cgroup のピークは 184 GiB で、その 940 GiB 上限の 5 分の 1 にすぎない一方、CPU は使用率 100% で load average は 166 である。

#### 4.3.2 受け付けがレート制限されているため劣化は亜線形である

素朴なタイムシェアリングでは、〜と予測される $$8\times$$ スレッド数が $$8\times$$ だけレイテンシが増える。測定された増加は $$9.7\times$$ 単一コンパイルに対しては、しかし $$3.8\times$$ 〜に対してはわずか $$N=128$$ — 提示負荷が 8 倍になってもである。その理由は図 7(b) と表 2 の最後の列に見える。1024 件の要求が同時に発行されても、実際に生存しているサンドボックス数は 205 を超えない。デーモンはクライアントの要求速度に合わせてコンテナを作成できないため、要求は CPU 内で競合するのではなく、受け付け時にキューに並ぶ。ここでテールを救っているのはキューイングであり、それは偶然そうなっている。

#### 4.3.3 屈曲点は失敗点ではなく 512 にある

〜と〜の間で $$N=256$$ と $$N=512$$ その $$p\_{95}$$ レイテンシは〜だけ増加する $$2.9\times$$ 負荷が 2 倍になるたびに上昇する。以前の各 2 倍化では、 $$1.2\times$$ と $$1.4\times$$。容量を「最大の〜」として述べると $$N$$ 失敗しないものは 1024 と報告されるが、運用者には役に立たない。その時点ではテール待ちがほぼ 8 分に達するからである。

### 4.4 コンパイル時間はクロックに反比例する

ワークロードは CPU バウンドなので、そのコストは〜としてスケールするはずである $$1/f$$。我々はこれを、他は変えないゲスト上で、ホストクロックをマシンの全範囲 1.0〜5.5 GHz にわたり 10 段階で掃引して直接検証した（図 8）。単一コンパイル時間は 26.5 秒から 4.8 秒へ変化する。5.5 倍のクロックは、範囲のどこでも逓減なしに 5.5 倍の高速化をもたらす。積 $$T!\cdot!f$$ は 10 段階すべてのクロックで 2% 以内の誤差で一定である。

コア配分で正規化すると、30 個すべての測定 — 10 段階のクロックにおける 3 つの同時実行レベル — は 1 つの定数に収束する。

$$
T(N,f) ;=; \frac{k}{f},\max!\left(1,\frac{N}{C}\right),
\qquad k = 27.9\ \mathrm{GHz\cdot s}
$$

クロック自体が 5.5 倍変化する範囲で、残差のばらつきは 5.1% である。曲率がまったくないこと自体が結果である。もしワークロードがメモリ帯域幅バウンドまたは I/O バウンドであれば、 $$T$$ CPU が他の資源を追い越すにつれて、高クロック域で〜は平坦になるはずである。

<figure><img src="/files/aa8b91b9c7abc8d3e77a0e0a134dcf4a4966467d" alt=""><figcaption></figcaption></figure>

**図8.** クロックスイープ。(a) $$T=k/f$$ 適合した双曲線とともに。(b) で割ると $$\max(1,N/C)$$ すべての点が 1 つの定数に収束し、式 (1) を確認する。

式 (1) には、言うのは簡単だが間違えやすい直接的な調達上の帰結がある。 *クロックは各個人ユーザーの体験を改善し、コア数はより多くのユーザーを受け入れるだけである*。クロックが 20% 高いマシンは、誰にとっても 20% 速くコンパイルでき、逓減効果はない。コア数を 2 倍にしても、誰のコンパイルもまったく速くならない。

## 5. 分析

### 5.1 2 つの壁を別々に当てはめる

各構成は失敗シグネチャ（§2.3）によって分類され、その後、メモリ壁と CPU 上限は、実際にそれらに当たった構成に対してのみ当てはめられる。

$$
N\_{\max} = \min\left(A R^{p},; k\_c C\right)
$$

で $$R$$ GiB で $$C$$ vCPU で

メモリ壁の指数は一貫して超線形で、 $$p>1$$：追加の同時コンパイル1件にかかる限界メモリコストは *低下する* 。総メモリが増えるにつれて、3 GiBのゲストではコンパイル1回あたり約312 MiBだったものが、32 GiBのゲストでは約194 MiBまで下がる。仕組みは §2.1 で述べた TeX Live ツリー上の共有ページキャッシュである。並行コンパイルは重なるフォントファイルやマクロファイルを読み込むため、より大きなキャッシュがそれらのより多くにまたがって償却される。これが、素朴な「ユーザー5人あたり1 GiB」という規則が大きいマシンを過小評価し、小さいマシンを過大評価する理由である。

<figure><img src="/files/c91aff9dcea0daea237ae5098867e0d427be195d" alt=""><figcaption></figcaption></figure>

**図9。** 同じデータを、 $$(C,R)$$ 平面上の2つの曲面として表したもの。(a) 容量：当てはめた曲面は尾根であって平面ではない――メモリに対して急勾配で上昇し、メモリが制約しなくなるまではコア軸に沿ってほぼ平坦である。だからこそ、48 GiBの行だけがコア数によって構成が分かれる。(b) すべての構成について同時実行数に対するレイテンシを示し、当てはめた $$T=12.6,(N/C)^{0.91}$$ 曲面は破線で示し、180 sのタイムアウトを平面として描いている。固体曲線がその平面を突き抜ける場所で構成は失敗し、それによってタイムアウト設定が報告される容量をどれだけ直接決めるかが可視化される。

### 5.2 コアを増やすと悪化する場合

式(2)は2項の最小値であるため、 $$C$$に関して単調だが、測定結果はそうではない。コア数を増やすことで *容量が* 低下する逆転を2回観測した。16 GiB（54対45）と32 GiB（145対135）である。どちらもメモリ制約の領域で起きており、その仕組みはどちらも同じだ。コアが多いほど、並行コンパイルは足並みをそろえて進み、常駐サイズのピークに同時に達する。一方、コアが少ない場合はスケジューラがそれらを交互に実行し、ピークがずれる。メモリの余裕がすでに限界的なゲストでは、このずれこそが生き延びさせる要因である。平均的な資源使用量に基づく容量モデルではこれを表現できない。これは、 *偶然の* ピークの一致

3回目の見かけ上の逆転、2 GiBでのものは、いまは除外する。検索記録では2 vCPUで容量2、4および8 vCPUで容量1 $$N=2$$ となっており、同じ効果のように読める。生のスイープを再検討すると、もっと単純なことが分かる。2 GiBでは、そのレベルは3つのコア数すべてで最初の試行に成功したが、確認実行では3つのうち2つで失敗していた。このレベルは容量ではなくコイントスであり、2 vCPUのエントリはたまたま表が出た投げである。したがって、再現可能な値である1を3つのコア数すべてに対して報告し、その違いからは何も結論づけない。ここでこの訂正を記録するのは、捨てた読みが興味深い主張を支えていた可能性があるからであり、表を黙って言い換えるだけにはしない。

## 6. 実装上の発見

### 6.1 ハードコードされた同時実行上限

十分に大きいゲストでは、要求された同時実行数にかかわらず、容量はちょうど65の同時コンパイルで頭打ちになった。 $$N=66,80,96,128$$ では $$65$$ 成功数を、 $$1,15,31,63$$ 即時の `利用不可` 応答を測定し、コンテナ数は65に固定されたまま、数ギガバイトのメモリが未使用で、コンパイル時間の中央値は77 sで安定していた――どのタイムアウトよりもはるかに短い。

原因はCLSI内の定数である：

```javascript
// services/clsi/config/settings.defaults.cjs:110
compileConcurrencyLimit: isSpotInstance ? 32 : 64,

// services/clsi/app/js/LockManager.js
if (LOCKS.size <= Settings.compileConcurrencyLimit) return   // <= 64+1 を許容
throw new Errors.TooManyCompileRequestsError(...)
```

比較は厳密でないため、有効な上限は $$64+1=65$$であり、測定結果とぴったり一致する。超過した要求には **HTTP 503** が返される――つまり *拒否される*のであって、キューに入れられるのではない。したがってユーザー側ではコンパイルボタンが単に失敗するだけである。同じファイル内の他のどの調整項目とも異なり、これは環境変数を一切読み込まない。2024年8月に上流で導入され、イメージを改変しない限り変更できない。制限を引き上げると、同じ16 vCPU / 32 GiBのゲストは `success=65, unavailable=15` と報告したものが、 $$N=80$$ では代わりに `success=80`.

### 6.2 動作しないコンテナメモリ制限

実行中のコンパイルコンテナを調べると、資源隔離はまったく存在しない：

```
Memory=0  NanoCpus=0  CpuShares=0  CpuQuota=0  CpusetCpus=[]
Ulimits=[{Name:cpu Soft:305 Hard:310}]
```

CPUクォータがないのは意図的な設計であり、§4.2 の時間分担指数がこれほどきれいなのもそのためだ。コンパイル間の競争を歪めるものが何もない。一方で、 *メモリ* 制限がないのは意図したものではない。ただし Docker ランナーはそれを要求している：

```javascript
// services/clsi/app/js/DockerRunner.mjs:262
Memory: 1024 * 1024 * 1024 * 1024, // 1 Gb
```

これは二重に誤っている。値は $$1024^4=1 tebiB$$ であり、コメントが意図しているのは $$1024^3$$だが、フィールドは create options のトップレベルに置かれており、 `HostConfig`の内側ではない。Docker API が期待する場所ではないため破棄される――そして観測された `Memory=0` がそれを裏付けている。どちらの誤りも、そのファイルを導入したコミット（`9a519f0d3d`、2018年3月）に含まれており、CoffeeScript からの変換、リポジトリ全体の再整形、CJS から ESM への移行を経ても生き残ったが、そのどれも意味論を見直していない。特に `MAX_OUTPUT = 1024 * 1024 // 1MB` が同じコミット内で正しいことは、誤解ではなく単なるうっかりミスであることを示している。

その結果は低メモリ測定で明らかである。コンパイルに上限がないため、メモリ枯渇は Docker が問題のある1つのコンテナを終了させる形では現れず、ゲスト全体を落とす。2 vCPU / 2 GiBの構成では、監視用 SSH セッションが300 sブロックされ、2コア上でロードアベレージは68に達し、最終的にゲストは自力で再起動した。コンテナごとの制限が機能していれば、はるかに穏当に劣化する。つまり、過大なコンパイルは失敗し、サービスは生き残る。

実際に効く唯一の制限は `RLIMIT_CPU`で、 $$\text{timeout}+5$$ 秒に設定されている。これは *CPU* 時間を制限し、壁時計時間ではない。そして1回のコンパイルが消費するCPU時間は約9 sにすぎないため、どの同時実行数でも決して上限に達しない。暴走マクロのような病的入力から守るためのものである。ただし、これは有用なオラクルでもある。 `Soft:305` を観測すれば、300 s のタイムアウト設定が実際にコンテナへ伝播していることが確認できる。

### 6.3 コンパイルタイムアウトが最重要の調整項目である

ユーザーごとのフィールド `features.compileTimeout` の既定値は180 sである。CPU負荷が支配的な構成では、これは安全余裕ではなく容量設定である。正しく計算を続けているマシンが失敗と宣言されるからだ。これを300 sに引き上げる――MongoDB の更新1回でできる――と、測定容量は最大で4.2倍変わる（表3）。上限は600 sであり、 `RequestParser.MAX_TIMEOUT`によって強制され、それを超える値は黙って切り捨てられる。

| 構成               | 180 s | 300 s |              比 | 拘束条件         |
| ---------------- | ----: | ----: | -------------: | ------------ |
| 8 vCPU / 48 GiB  |    64 |   268 | $$4.19\times$$ | CPU、28 GiB空き |
| 4 vCPU / 32 GiB  |    47 |   145 | $$3.09\times$$ | CPU、30 GiB空き |
| 4 vCPU / 48 GiB  |    63 |   143 | $$2.27\times$$ | CPU          |
| 4 vCPU / 16 GiB  |    31 |    54 | $$1.74\times$$ | CPU          |
| 2 vCPU / 8 GiB   |    15 |    21 | $$1.40\times$$ | CPU          |
| 8 vCPU / 32 GiB  |   127 |   135 | $$1.06\times$$ | 次にメモリ壁に突き当たる |
| 8 vCPU / 16 GiB  |    56 |    45 | $$0.80\times$$ | メモリ          |
| 16 vCPU / 32 GiB |   159 |   141 | $$0.89\times$$ | メモリ          |

**表3。** コンパイルタイムアウトが測定容量に与える影響。

最後の2行は結果の逆説的な半分であり、すべての構成を1つのタイムアウト下で再測定した理由である。 *メモリ*制約の構成では、より長いタイムアウトは *低下させる* 容量を増やす。各コンパイルが常駐セットをより長く保持し、それらの重なりが増えるからである。したがって、容量の数値は、それがどのタイムアウトの下で測定されたかを明示しない限り無意味であり、両者は同じ表の中で混ぜることはできない。

## 7. 関連研究

### 7.1 ベンダーの指針

Overleaf 自身のハードウェア文書は、ここで定量化した質的事実を述べている。すなわち、LaTeX は単一スレッドであり、したがって単一コア性能がコンパイル時間を左右し、「より多くのコアが役立つのは、自由なCPUコア数より多くの文書をコンパイルしようとしている場合だけだ」\[1]ということだ。さらに、線形のサイジング規則――2コア/3 GiB を基準に、同時ユーザー5～10人ごとに1コアと1 GiBを追加する――を示しており、それが本研究の動機となった。私たちの貢献は、これらの記述を測定された法則（式(1)と(2)）へと変え、線形則がどこで破綻するかを示すことである。そこには、メモリ壁を超線形にする共有ページキャッシュの項も、結果を支配する2つのソフトウェアパラメータの項もない。

### 7.2 ビルドおよびCIの容量研究

並行下でのビルドシステムの測定は、LaTeX の文脈以外では十分に確立している。LightSys は、Docker コンテナ内でビルドする従来型CIシステムが、プルリクエストの到着率の上昇に伴ってI/O面で劣化し、約11件の同時リクエスト付近でボトルネックが現れると報告している\[17]。TAOS-CI は、コンパイルがCIの壁時計時間を支配し、大規模プロジェクトではパイプライン総時間の60～67%を占めると観測している\[18]。私たちのシステムが決定的な違いを持つのは1点である。LaTeX のコンパイルは対話的だということだ。2倍長くかかるCIジョブは不便で済むが、2倍長くかかるコンパイルはプレビュー画面を待っているユーザーに直接見える。だからこそ、タイムアウトを失敗閾値ではなく容量パラメータとして扱う。

### 7.3 コンテナのオーバーヘッド

最近の研究では、ストレージ層をまたいだ Docker コンテナ起動遅延の分解\[19]や、エッジでのコンテナ性能の特徴づけ\[20]が行われている。私たちの設定では、各コンパイルごとのコンテナ起動は償却される。9 s のコンパイルに対しては小さな定数であり、 $$T\_1$$ 当てはめたフリーフライト時間 *の欠如* という、資源制限の欠如 (§6.2) が実際に重要なのだ。それが1回のコンパイルでのメモリ超過をホスト全体の障害へと変えてしまう。

### 7.4 信頼できない入力としてのLaTeX

サンドボックス化されたコンパイルが存在するのは、TeX がプログラミング言語であり、文書が信頼できない入力だからである\[21, 22]。この設計選択が本研究を可能にしている――各コンパイルは観測可能な資源挙動を持つ孤立したコンテナである――と同時に、欠落したメモリ制限を重大なものにしている。というのも、運用者はそれが隔離を前提としているからである。

### 7.5 研究対象としてのコンパイラ

TeX 自体は言語としてよく文書化されているが、その振る舞いは *ビルド対象* として注目を集めたのはごく最近である。Tan と Rigger \[8] は、エンジンとディストリビューションの版をまたいで大規模な arXiv ソース群をコンパイルし、エンジン選択は代替可能ではないことを見いだした。XeTeX と pdfTeX の両方でバイト単位で同一の出力を生成する文書はごく一部の割合にすぎない。この結果は私たちの方法論に直接関わる。容量は文書 *と* の`texlive-full:2025.1`とエンジンの属性であるため、両方を固定しないベンチマークは再現可能ではない。したがって、私たちは1つの文書、1つのエンジン、1つのディストリビューション（

LaTeX ビルド *システムの研究* は主として実務者主導である。LaTeX3 プロジェクト `の` l3build `latexmk` \[13] は回帰テストとパッケージングを標準化し、独立したベンチマークではラッパーツールを比較している――26のビルドシステムを調査したところ、事前コンパイル済みのプレアンブルは単純な実行より約20%高く、 *単一の* コンパイル。これは、私たちが測定したものと直交し、かつ組み合わせ可能である。プレアンブルキャッシュは $$T\_1$$を短縮し、この論文のすべての容量値は $$T\_1$$.

### 7.6 コンパイラではなくエディタにおける同時実行制御

Overleaf の共同編集部分は、よく確立された研究系譜に支えられている。オペレーショナル変換は Ellis と Gibbs \[9] に端を発し、Jupiter システム \[10] によって高遅延クライアントに対して実用化された。その設計は `document-updater`に見て取れる。すなわち、操作を順序付けるサーバと、クライアントが同期する各文書ごとのバッファである。コンフリクトフリー複製データ型\[11]は、中央のシーケンサなしで同じ問題を解決する。この区別こそが §4.3 のトポロジーを成立させている。保留中更新バッファはインスタンスのメモリではなく共有Redisに置かれているため、どのレプリカにルーティングされたコンパイルでも最新のキー入力を観測でき、コンパイルのアフィニティは正しさではなくキャッシュ局所性のために選べる。

### 7.7 容量モデル

Amdahl の法則\[24]は並列化による高速化を上限づけ、Little の法則\[23]は在庫量を到着率とサービス時間に関連づける。上でも両方を用いた。Gunther の普遍スケーラビリティ法則\[12]は、最初のものに整合性遅延の逆戻り項を加え、スループットがピークに達した後で低下すると予測する。ここで注記しておくと、私たちのシステムは *その* 逆戻り領域を $$N=1024$$まで示さない。スループットは飽和し、レイテンシは増大するが、何も崩壊しない。その理由は幸運ではなく構造的である――コンパイル間で整合性を保つ状態を共有していないので、法則が加える項はゼロに近く、§4.3 の受け入れプラトーが、それが問題になる前に競合を抑え込むからだ。

## 8. 運用者への推奨

{% stepper %}
{% step %}

## ハードウェアを買う前に2つのソフトウェアパラメータを固定せよ

どちらも無料であり、私たちが測定したどの単一ハードウェアアップグレードよりも価値がある。 `features.compileTimeout` を、実際にユーザーが許容する値まで引き上げよ――CLSI が受け入れる最大値は600 sである――そして、65を超える同時コンパイルを見込むなら、 `compileConcurrencyLimit` を派生イメージで引き上げるか、あるいは水平スケールせよ。どちらもしないということは、ソフトウェアが使うことを拒否するコアに金を払うことを意味する。
{% endstep %}

{% step %}

## マシンは上限ではなく膝で選べ

大規模実行のスイープ (§4.3) は、日常的に混同される2つの数を切り分ける。 *上限* ――すべてのPDFを返せる最大同時実行数――は64コアサーバーで少なくとも1024であり、私たちはそれに到達しなかった。 *膝* ――尾部レイテンシが穏やかに増えるのをやめ、倍増し始める点――は512にあり、その直下で最後に快適に運用できる点は256である。 $$N=256$$ と $$N=512$$ その $$p\_{95}$$ 512から1024の間では8分に達する。上限に合わせてサイズを決める運用者は、技術的には動くが誰も使いたがらないシステムを出荷する。

したがって、このマシンとこの文書に対する推奨運用点は **256の同時コンパイル**であり、これは $$4\times$$ 物理コア数であり $$2\times$$ スレッド数であり、 $$p\_{95}$$ 約120 sに保たれる。 `compileConcurrencyLimit` に設定することを提案する。高いままにしておくのではなく、この値に設定すべきだ。1024個のコンパイルを一度に受け入れると全員が8分待つが、256個だけ受け入れて残りをキューに回せば、ほとんどのユーザーは2分で済む。キューイングは遅れて来た人を悪化させるが、競合は全員を悪化させる。
{% endstep %}

{% step %}

## これらを最悪値として

表2の各レベルは、同時に発火させたコールドコンパイルである。実運用ではどちらの条件も満たされない。同じ文書のウォームコンパイルはコールド時の28.3 sに対して8.6 sであり、係数は $$3.3$$、実ユーザーは同じ秒にボタンを押すわけではない。したがって、典型的なキャッシュヒット率で2分ごとに再コンパイルする定常状態の利用者群は、同時実行数だけから推測されるよりもかなり多くの作成者を支えられる――256の運用点では、およそ千人以上のアクティブ著者である。同時実行数は瞬間的なバーストの上限であって、着席数ではない。
{% endstep %}

{% step %}

## まずレイテンシ予算を決め、それからサイズを読み取れ

式(1)は直接反転できる。目標待ち時間 $$T$$ を $$f$$ クロックで $$C$$ コア上で実現する同時実行数は $$N \le C,fT/k$$ で $$k\approx28 GHz·s$$ である。この文書では。 $$N\le51$$8コア、3 GHzで60 sの予算なら
{% endstep %}

{% step %}

## まずメモリを買い、それからコアを買い、どちらの壁にいるかを確認せよ

32 GiB未満では、追加コアによる利益はほとんど測定されなかった。診断は簡単だ。失敗がメモリ不足のゲストで HTTP 502 として現れるならメモリを増やす。 `タイムアウト` で、かつメモリに余裕があるなら、コアを増やすかタイムアウトを引き上げる。運用者は、私たちが構成を分類したのと同じ失敗シグネチャからこれを読み取れる。
{% endstep %}

{% step %}

## 経験にはクロックを、人口にはコアを優先せよ

なぜなら $$T\propto 1/f$$ は曲率なしに成り立つからだ（1.0～5.5 GHzで2%）。より速いクロックは、すべてのユーザーに対してすべてのコンパイルを速くする。コアを増やしても個々のコンパイルは速くならない。ただ、同時に受け入れられる数が増えるだけだ。「コンパイルが遅い」が悩みのデプロイではクロックを買うべきであり、「締切時にコンパイルが失敗する」が悩みのデプロイではメモリとコアを買うべきである。
{% endstep %}

{% step %}

## 上限を超えるなら、スケールアップではなくスケールアウトせよ

65を超える同時コンパイルでは、推奨される経路は水平スケーリングである（図2cおよび10、§9で詳述）：複数のアプリケーションインスタンスをロードバランサーの背後に置き、Cookieセッションアフィニティを持たせ、中央の MongoDB、Redis、および S3互換ストレージを共有する。 `git-bridge` はシングルトンのままにしておく。これによってインスタンスごとの上限はインスタンス数倍になる。SaaS デプロイが自分自身の容量に到達する仕組みはまさにそれである。
{% endstep %}

{% step %}

## コンパイルごとの隔離に頼るな

Docker ランナーのメモリ制限が修正されるまで（§6.2）、単一の問題のある文書が、その文書だけが殺されるのではなくホスト全体のメモリを使い果たしうる。その保証が必要な運用者は、待つのではなく自分でそれを課すべきである。大きなホストで私たちが使った仕組みは、ハード上限を持つ systemd slice であり、Docker デーモンはその上を指すことで、作成するすべてのコンテナがその中で会計されるようにした：

```ini
[Slice]
MemoryMax=940G
```

見落とすと午後を1つ失う細かな点がある。 `docker-capped.slice` という名前の slice は `docker.slice`の隣に置かれるのではなく、 *内側* に置かれる。ハイフンは名前の一部ではなく階層の区切りだからである。効いていないように見える上限は、たいていコンテナが実際に存在する場所から1階層離れたところに適用されている。 `memory.max_usage_in_bytes` からピークを読み返して確認せよ――設定ファイルを信じるのではなく。私たちのホストでは、1024個の同時コンパイルでもコンパイル用cgroupはその上限の5分の1を超えなかった。これ自体が、メモリではなくデーモンが拘束条件だった証拠である。
{% endstep %}
{% endstepper %}

<figure><img src="/files/27876559c15388311cf68f7aa193016cd1ef0836" alt=""><figcaption></figcaption></figure>

**図10。** 確認した設定から描いた、水平スケールしたデプロイの参照トポロジー。アプリケーションレプリカは交換可能で持続的な状態を何も持たないので、自由に追加・削除できる。そうでないものが3つある。どのレプリカにルーティングされたコンパイルでも最新のキー入力を見られるようにする文書バッファを持つ Redis、1レプリカを超えると任意ではなく必須となるオブジェクトストア、そして `git-bridge`、これはリポジトリをローカルディスク上に保持し、複製経路を持たず、指定された1つのレプリカの横でシングルトンとして実行しなければならない。

## 9. 参照用のマルチマシンデプロイ

上ではすべて1台のマシンを測定した。この節では、構築できるだけの十分な詳細を持つ分散形態を示す。そして――運用者が実際に直面する問いは *どのように* ではなく *それを行う価値があるかどうか* なので――まず、その手間をかける価値が生じる点を示す。

### 9.1 分散形態が正当化されるとき

1台のマシンは、重要なあらゆる面で運用コストが低い。故障領域は1つで、整合性を保つ共有状態はなく、ルーティングを誤ることもない。私たちのデータは、それを離れる3つの閾値を示す。

#### 9.1.1 65個未満の同時コンパイルでは、行うな

インスタンスごとの上限はハードウェアではなくソフトウェアの定数である（§6.1）。負荷がそれに近づくまでは、2台目のマシンは失敗モードを増やすだけで何も得られない。64コアホストは、 `compileConcurrencyLimit` が引き上げられた後に初めて、256個の同時コンパイルを完全成功で処理できた。まだその値を変えていない運用者はハードウェア制約下にはなく、ハードウェアを買うべきではない。

#### 9.1.2 65からおよそ500までは、まずスケールアップせよ

垂直スケーリングは私たちの範囲全体で線形のままで、逆戻り領域に入ることは一度もなかった。1つの大きなホストは、100%成功で1024の同時コールドコンパイルに達した（§4.3）。レイテンシの膝は512で現れ、それ以前ではなかった。その帯域内では、より大きなマシンは複数の小さなマシンよりも本質的に単純であり、§4.4に従えば、より高速なマシンは単により多く受け入れるだけでなく、すべてのユーザーの体験を改善する。

#### 9.1.3 スループットではなく可用性のために分散せよ

上限未満で1つ以上のアプリケーションレプリカを運用する正直な理由は、1台のマシンが1つの電源、1つのカーネル、1つのアップグレード時間窓だからである。それは正当な理由であり、私たちならそう説明する。ただしそれは容量の議論ではなく、この2つを混同すると、運用者はメモリが必要だったのにレプリカを買うことになる。

### 9.2 階層とそのサイズ決定

図10がそのトポロジーを示す。そこには4つの階層があり、それぞれ異なる量に応じてスケールする――それこそが、それらを分けている理由である。

#### 9.2.1 エッジ

ロードバランサ1台、可用性のためなら2台。TLSを終端し、重い処理は何もしない。スケールするのはコンパイル数ではなく接続数であり、ここで研究した負荷には小さなインスタンスで十分である。重要なのはサイズではなく設定である（§9.3）。

#### 9.2.2 アプリケーションレプリカ

ここがコンパイル負荷を担い、同時実行数に応じてスケールする唯一の階層である。各インスタンスのサイズは§8の規則――メモリをコアより先、そしてクロック――に従って決め、レプリカ数はピーク同時実行数をインスタンスごとの上限で割ってカバーするように設定する。レプリカは永続的なものを何も保持しない。ローカルディスクにはコンパイル用の一時領域と出力キャッシュがあり、どちらも再構成可能である。これが自由に追加・削除してよい理由であり、1つでも誤って設定された `filestore` パスは、黙ってその階層をステートフルなものに変えてしまう。

#### 9.2.3 状態

Redis、MongoDB、そしてS3互換オブジェクトストアを別ホストに置く。Redis は最も負荷を負うのに、最も見えにくい。セッションストアとライブ文書バッファを保持しており、そのおかげで、どのレプリカにルーティングされたコンパイルでも別のレプリカに対して入力されたキーストロークを観測できる。Redis をキャッシュとして扱い、エビクション前提でサイズを決める運用者は、非常に診断しにくい古い文書のコンパイルを生み出すだろう。失敗するものは何もなく――単に出力が間違っているだけだからだ。MongoDB はコンパイル率ではなくプロジェクト数に応じてスケールする。オブジェクトストアは1レプリカでは任意だが、それを超えると必須である。

#### 9.2.4 シングルトン

`git-bridge` はリポジトリをローカルディスクに保持し、ローカルインデックスを維持し、複製経路を持たない。ちょうど1インスタンスとして、指定された1つのレプリカの横に固定して実行しなければならず、デプロイをほぼステートレスでないものにしている構成要素である。そのホストはそれに合わせて計画せよ。バックアップが必要なのはそのディスクである。

| **階層**       | **数**                | **スケールするもの**  |
| ------------ | -------------------- | ------------- |
| ロードバランサ      | 1–2                  | 同時接続          |
| アプリケーション     | $$\lceil N/C\rceil$$ | ピーク同時コンパイル数   |
| Redis        | 1（+レプリカ）             | アクティブな編集セッション |
| MongoDB      | 1（+レプリカ）             | 保存済みプロジェクト    |
| オブジェクトストア    | 1クラスタ                | プロジェクトの総バイト数  |
| `git-bridge` | ちょうど1つ               | ディスク上のリポジトリ   |

**表4。** 参照用の階層。並行実行数に応じてスケールするのはアプリケーション階層だけであり、そのサイズ決定が§8の主題である。

### 9.3 ルーティングは間違えやすい部分である

3種類のリクエストは3つの異なる場所に届かなければならず、既定の単一ルール設定ではそのうち最大2つしか満たせない。

/project/以下のコンパイルトラフィックは `/project/` プロジェクト識別子に基づく一貫ハッシュで分散すべきであり、それによってプロジェクトのコンパイルキャッシュが1つのレプリカに留まる。私たちは HAProxy の `balance hash path,field(3,/)` で `hash-type consistent` と `hash-balance-factor 150`を使う。この選択はスケールアウト時に重要である。Cookieアフィニティでは、既存セッションは元のレプリカに無期限に固定されたままで、新しく追加したレプリカには新規ユーザーしか来ないため、運用者がまさに対価を払って追加したマシンが、その購入理由となった負荷をまったく吸収しない。一貫ハッシュでは、私たちの構成でスケールアウト時に35%のプロジェクトが再分配されたのに対し、Cookieでは0%だった。

セッショントラフィックは別物である。WebSocket のアップグレードが失敗し `socket.io` が XHR ポーリングにフォールバックすると、1つのセッションの連続するポーリングは1つのレプリカに届かなければならず、パス中にはハッシュするためのプロジェクト識別子がない。このトラフィックには Cookie アフィニティ付きの別バックエンドが必要である。私たちはこの分割を設計したが、デプロイはしなかった。主張ではなく不足としてここに記す。

最後に、 `/git/` は `git-bridge` を実行するレプリカに届かなければならない。 `git-bridge` ブリッジがアプリケーションの OAuth エンドポイントに対してコールバックを認証し、blob URL をそこ経由で解決するため、直接そちらをバイパスすると何も改善せず認証が壊れるからである。

### 9.4 スケールインにはドレイン用バッファが必要

レプリカの削除は追加と対称ではない。進行中のコンパイルは失われ、ユーザーは自分の責任ではない失敗を見ることになる。実行可能な手順は、まず新しいトラフィックを止め、待ち、そしてその後で終了することだ。私たちはこれを pre-stop フックとして実装し、バランサがバックエンドをドレイン中とマークしている間、Pod を設定可能な間隔だけ保持させた――数分で試せるほど短く、本番ではセッションの自然終了まで待てるほど長く、つまり秒ではなく時間単位である。この間隔こそが、弾力性を目に見えなくするか、苛立たしいものにするかを決める調整つまみである。

もう1つ、設計ではなく測定によって見つけた制約がある。このワークロードではCPUによるオートスケーリングは機能しない。アプリケーションPod自体の利用率はノード総計3997 m coreに対して22 m coreにとどまっていた。コンパイル作業はPodが勘定しない兄弟コンテナで起きるからだ。この階層をスケールするために使う信号は、稼働中のコンパイルコンテナを数えるのでなければならず、PodのCPUではない。

## 10. Overleaf を超えた含意

§4.2 でも §4.3 でも、Overleaf のコードに固有なものは何もない。測定された法則は、ホスト型 LaTeX サービスならどれでも共有する3つの性質から導かれる。仕事の単位が単一スレッドのプロセスであること、コンテナ内で隔離されていること、そしてそのワーキングセットがページキャッシュに保持されなければならない大きな読み取り専用ツリーであることだ。3つの帰結は、このようなサービスを構築する誰にでも直接移る。

### 10.1 まずメモリを、その後コアを

マトリクスの最も強い結果は否定的である。16 GiB未満ではコア数はほぼ無関係で、48 GiBになって初めて4、8、16 vCPU構成が分かれる（143、268、331）。「5人ごとに1コア追加」という慣用ルールを読む運用者は、間違った資源を買うことになる。仕組みは配布ツリー上の共有ページキャッシュであり、TeX Live のサイズという性質であって、個々のフロントエンドの性質ではない。

### 10.2 受け入れ率

〜では $$N=1024$$ は資源であり、通常は忘れられている

### 10.3 コンパイルより長生きするサンドボックスはモデルを無効にする

ここでの容量値はすべて、コンテナが作成され、1回コンパイルし、終了する――寿命は数十秒で、実行中にのみ稼働率が1に近づく――ことを前提にしている。最近の2つの設計パターンはその前提を壊し、しかも同じ壊し方をする。

1つ目はユーザーごとの永続サンドボックスである。各ユーザーに固定のプライベート環境を割り当てると、統計的に多重化されたプールは予約の集合に変わる。時間分割なら64コアから256の同時コンパイルを処理できるサービスが、各ユーザーに4つの専用コアを与えると16ユーザーしか処理できない。つまり同じハードウェアで1桁少ない。私たちのデータはその選択のコストを定量化しているのであって、それに反対しているわけではない――予約は予測可能性を買うのであり、その交換レートはおよそ $$16\times$$ 私たちが推奨する運用点で

ある。2つ目で、より新しいのは、コンパイラとサンドボックスを共有するAIエージェントである。エージェント支援執筆プラットフォームでは、XeLaTeX を実行する同じコンテナが長時間稼働するコーディングエージェントもホストするかもしれず、その結果、バースト的ではなく連続的に占有される。実務家は、そうしたデプロイに対してモデルが予測するまさにその症状――少人数のユーザー数でも持続的な鈍化\[15]――を報告している。この相互作用は正確に述べる価値がある。単に「負荷が多い」だけではないからだ。私たちの3つの発見が相乗する。占有はバースト的でなくなり、§4.2 の時間分割則は現在コンパイル中の割合ではなく全人口に一度に適用される。§4.1 の超線形メモリ増加の源であるページキャッシュは、エージェント自身のワーキングセットと共有され、TeX にとって温かい状態でなくなる。そして、§6.2 の欠落したコンテナメモリ制限はさらに危険になる。なぜなら、決して終了しないコンテナはメモリを返さないからだ。

私たちはそのようなプラットフォームを測定しておらず、特定の製品についても何も主張しない。言えるのは、私たちの数値が設計について示唆することである。各ユーザーに長寿命のマルチコアサンドボックスを与えるアーキテクチャは、ここで報告した同時実行数ではなく予約システムとしてサイズを決めるべきであり、期待できる容量は表1のどれよりも、ユーザー1人あたりコア数で割ったコア数に近い。

## 11. 妥当性への脅威

### 11.1 単一文書

すべての測定は63ページの XeLaTeX 文書1つを用いる。絶対容量は他の文書では異なるだろうが、比率であるスケーリング則は変わらないはずだ。常駐セットがかなり大きい文書なら、その超線形性を変えずにメモリ壁を移動させるだろう。

### 11.2 仮想化ホスト

ゲストは1台の物理マシン上で KVM の下で動作するため、絶対値には仮想化のオーバーヘッドが含まれ、ゲストはホストのページキャッシュと NVMe デバイスを共有する。最も大きな交絡要因は、ホストのメモリ圧力がゲスト内のロードアベレージを $$3\times$$ と同じ同時実行数で

### 11.3 同時到着

各コンパイルは同じ瞬間に発行され、これが最悪条件である。実ユーザーは確率過程として到着するので、私たちの数値でサイズを決めたデプロイには余裕があり不足ではない――しかし提出締切末尾でのピークは、ポアソン過程よりも私たちのモデルに近い。

### 11.4 エッジ構成

2 GiBではシステムが崩壊にかなり近く、同じ構成を繰り返し実行すると1つのコンパイル分だけ結果が違うことがある。保守的な値を報告し、その領域での差 $$\pm 1$$ からは結論を導かない。

## 12. 入手性

試験対象システム、デプロイ用ツール、そしてその上流プロジェクトはすべて公開されている：

* Ayakaleaf Pro — <https://github.com/ayaka-notes/ayakaleaf-pro>
* デプロイツールキット — <https://github.com/ayaka-notes/toolkit>
* ドキュメント — [https://ayakaleaf-pro.ayaka.space](https://ayakaleaf-pro.ayaka.space/)
* 上流 Overleaf — <https://github.com/overleaf/overleaf>
* TeX Live コンパイルイメージ — `ghcr.io/ayaka-notes/texlive-full:2025.1`

私たちが引用するソース位置はすべて、Ayakaleaf Pro v6.2.2 に対する行番号付きのリポジトリ相対パスとして示し、日付を付した2つの上流コミット（`9a519f0d3d`, `5d472e9b38`）は Overleaf の履歴でたどれる。

## 13. 貢献

Musicminion が研究を設計し、テストベッドを提供・運用し、調査の方針を指揮し、ここで報告するすべての測定を検証した。Claude Opus 5（Anthropic）はベンチマークハーネスを構築・運用し、デプロイを自動化し、ソースコード考古学を行い、図を作成し、原稿を起草した。両著者は最終文をレビューした。ある実行が汚染ありと報告される場合――§4.3 の 1021 セッションのスイープと、§4.1 の異常な $$N=256$$ レベル――その欠陥は査読中に発見され、公開前にその実行は繰り返されたのであって、黙って削除されたのではない。

読者は、ACM、IEEE、および ICMJE の著者資格方針が現在、仕事に対して説明責任を負える当事者に著者資格を留保しており、2人目の著者の貢献はバイラインではなく開示として記録されることを要求する点に注意すべきである。どちらの慣例でも記録が正確になるよう、ここで役割分担を明示する。

## 14. 結論

セルフホスト版 Overleaf の容量計画は、1つの資源をスケールする問題ではない。3つの発見が、そのやり方を変えるべきである。

第一に、ゲストメモリが32 GiB未満では、コア数はほとんど重要ではない。16 GiBでは4、8、16 vCPUゲストの容量差は8%未満である。TeX Live ツリー上の共有ページキャッシュを通じたメモリが上限を決め、コアが重要になり始めるのはメモリが十分に豊富になってからである。

第二に、2つのソフトウェアパラメータはハードウェアより重い。CLSI のハードコードされた65コンパイル上限を引き上げ、既定の180 sコンパイルタイムアウトを増やしたところ、8 vCPU / 48 GiB のゲストは追加ハードウェアなしで64から268の同時コンパイルに達した――4.2倍である。どちらも設定文書からは見つけられない。片方はそもそも設定可能ですらない。

第三に、「このマシンは何人の同時ユーザーをサポートするか」という問いは、仕様が不十分である。このシステムにおける並行処理は純粋なタイムシェアリングであり、容量はタイムアウトが許す範囲そのものである。率直な答え方では、次の両方を述べる： *このマシンは* $$N$$ *ユーザーが待ってくれるなら同時にコンパイルを* $$T$$ *秒*、ただし $$N$$ と $$T$$ は式(1)で関連づけられる。

また、潜在的な欠陥も報告する。Dockerランナーにおけるコンテナごとのメモリ上限は、2018年以来、その値の大きさの面でも配置の面でも機能していない。その実際の影響は、小規模なデプロイでメモリ枯渇が起きると、原因となった1件のコンパイルだけでなくサービス全体が停止してしまうことである。

## 参考文献

\[1] Overleaf. *ハードウェア要件*、オンプレミス版ドキュメント。 <https://docs.overleaf.com/on-premises/getting-started/requirements/hardware-requirements>

\[2] Overleaf. *水平スケーリング*、オンプレミス版ドキュメント。 <https://docs.overleaf.com/on-premises/maintenance/horizontal-scaling>

\[3] Overleaf. *マイクロサービス*、オンプレミス版ドキュメント。 <https://docs.overleaf.com/on-premises/getting-started/microservices>

\[4] Overleaf. *ソースリポジトリ*. <https://github.com/overleaf/overleaf>

\[5] Ayaka-notes. *Ayakaleaf Pro*. <https://github.com/ayaka-notes/ayakaleaf-pro>

\[6] Ayaka-notes. *Overleaf Toolkit*. <https://github.com/ayaka-notes/toolkit>

\[7] D. Karger, E. Lehman, T. Leighton, R. Panigrahy, M. Levine and D. Lewin. *一貫ハッシュとランダムツリー：World Wide Web 上のホットスポットを緩和するための分散キャッシュプロトコル*. STOC, 1997.

\[8] J. Tan and M. Rigger. *TeX生成文書における不整合*. In *Proc. 第33回 ACM SIGSOFT ソフトウェアテストおよび解析国際シンポジウム (ISSTA)*、ウィーン、2024年。doi: <https://doi.org/10.1145/3650212.3680370>

\[9] C. A. Ellis and S. J. Gibbs. *グループウェアシステムにおける並行制御*. In *Proc. ACM SIGMOD*、pp. 399–407、1989年。

\[10] D. A. Nichols, P. Curtis, M. Dixon and J. Lamping. *Jupiterコラボレーションシステムにおける高遅延・低帯域幅のウィンドウ処理*. In *Proc. ACM UIST*、pp. 111–120、1995年。

\[11] M. Shapiro, N. Preguiça, C. Baquero and M. Zawirski. *コンフリクトフリー複製データ型*. In *Proc. SSS*、pp. 386–400、2011年。

\[12] N. J. Gunther. *ゲリラ・キャパシティプランニング：高いスケーラビリティを持つアプリケーションとサービスの計画に向けた実践的アプローチ*. Springer, 2007.

\[13] The LaTeX3 Project. *l3build — (La)TeXのためのテストおよびビルドシステム*. CTAN.

\[14] M. Isaksson. *どのLaTeXビルドシステムが最速か？ ベンチマーク*. <https://blog.martisak.se/latex-build-systems-comparison/>

\[15] 永続的なコーディングエージェントをユーザーごとのサンドボックス内でLaTeXコンパイラと同居させる、エージェント支援型執筆プラットフォームにおいて持続的な遅延があるとする、実務者による報告。これは統制された計測ではなく、報告された運用経験として引用している。私たちはそのようなプラットフォームをベンチマークしていない。

\[16] D. E. Knuth. *The TeXbook*. Addison-Wesley, 1984.

\[17] G. Lim, M. Ham, J. Moon and W. Song. *LightSys：ソフトウェアの統合速度向上のための軽量で効率的なCIシステム*. arXiv:2101.07961 \[cs.SE], 2021. プレプリント。

\[18] G. Lim, M. Ham, J. Moon, W. Song, S. Woo and S. Oh. *TAOS-CI：エッジコンピューティング向け軽量・モジュラーな継続的インテグレーションシステム*. arXiv:2101.08889 \[cs.SE], 2021. プレプリント。

\[19] S. Khan. *Dockerコンテナ起動性能の分解：異種インフラ上での3層測定研究*. arXiv:2602.15214, 2026. プレプリント。

\[20] R. Gupta and K. Nahrstedt. *エッジコンピューティングにおけるコンテナの性能特性評価*. arXiv:2505.02082, 2025. プレプリント。

\[21] S. Checkoway, H. Shacham and E. Rescorla. *テキストのみのデータ形式は安全か？ あるいは、このLaTeXクラスファイルを使ってあなたのコンピュータを乗っ取れ*. In *Proc. USENIX 大規模エクスプロイトおよび新興脅威ワークショップ (LEET)*, 2010.

\[22] G. Lacombe, K. Masalygina, A. Tahiri, C. Adam and C. Lauradoux. *見知らぬ人からLaTeXファイルを受け取れますか？ 10年後*. arXiv:2102.00856 \[cs.CR], 2021. プレプリント。

\[23] J. D. C. Little. *待ち行列公式の証明* $$L=\lambda W$$. Operations Research, 9(3):383–387, 1961.

\[24] G. M. Amdahl. *大規模計算能力の達成に向けた単一プロセッサ方式の妥当性*. AFIPS, 1967.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://ayakaleaf-pro.ayaka.space/blog/ja/2026/overleaf-benchmark.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
