数年ためた Minecraft の負荷軽減うんちくを1回放出します。
前職では今にも燃えそうなプロジェクトにアサインされては火消しを行ったり、ノート PC と PHS を抱えてデータセンターの床に這いつくばりながら仕事したり、とにもかくにも「何とかする人」の称号を得ていた訳ですが、最近 Minecraft やってても同じことやってんなと思い始めたどうのこうのジョニキです。
はじめに
Minecraft のマルチプレイでは、プレイヤー数やエンティティ数が増えるにつれてサーバーの負荷が増大してカクカクするのは周知の事実ですが、この記事では、その負荷が何故発生するのかその発生要因、その負荷を削減する案についてざっくり紹介します。分からないところは雰囲気で読んで下さい。
10人、100人、1000人のマルチプレイでどんなところに影響が出てくるのか頑張って理解しましょう。
前回書いた大規模マルチサーバうんちくはこちら↓
対象者
- 情報工学系の学生 ~ IT系エンジニア1~2年目くらいまでの人
- 大規模なマルチプレイサーバを運営したいけど重くて動かない問題に直面した人
- ファンボーイ
用語の説明
とりあえず用語を2つ。
①ボトルネック
たぶんビジネス用語だと思います。瓶の首の細い部分のように、瓶を逆さにしても水がなかなか流れ出てこないように水の流れを停滞させている部分ことです。

IT でいうところのボトルネック(bottleneck)は、システムの処理速度や性能を制限する要因となる部分のことを指します。例えばサーバのリソース(CPU、メモリ、ネットワーク、ディスクI/Oなど)が他のリソースに比べて逼迫(ひっぱく)することによって、サーバ全体のパフォーマンスが著しく低下してしまったときに、サーバのどこかに「ボトルネックがある」などと言います。
Minecraft であれば、
- CPU ボトルネック
1コアの CPU のクロック速度が最高速度に達した時点でも処理が間に合っていない状況(←コマンドとかはほぼこれ) - メモリボトルネック
エンティティやワールドデータが増えすぎてメモリが逼迫し、GC(ガベージコレクション:使っていないメモリを探して掃除する処理)が追い付かなくなった状況 - ネットワークボトルネック
プレイヤー数が増え、送受信するパケット量が増大してネットワーク帯域が不足している状況 - ディスク I/O ボトルネック
ワールドの読み書きが多く発生し、HDDやSSDの処理速度が限界に達している状況
などいくつか要因が考えられます。

計算量 O(オーダー)
O(オーダー)は数学や物理やプログラミングの文献で使われることがあります。数学では x→∞ における漸化式などで見かけます。プログラミングでは、ある特定の作業を行う場合にその手法がどれぐらい計算の回数(計算の時間)として優れているかを表す指標として用いられます。
例えば、プレイヤー数、エンティティ数、アイテム数など対象となるもののサイズ N に対して、処理に要する時間がどの程度増加するかを表す尺度として、O記法(オーダー記法)で表現すれば、それが良い手法なのかどうかを議論できます。
例をいくつか挙げます。
①N 人のプレイヤーに金リンゴを渡す
プレイヤーのインベントリの末尾に金リンゴを1つ加えるという手法を考えます(プレイヤーのインベントリが既に埋まっているかどうかは一旦無視して下さい)。
インベントリの末尾に何かアイテムを1つ与える作業は計算1回でできる(と仮定する)とすると、この作業は N 人に全員に対しては N 回でできるので、計算回数は人数 N と比例します。10 人なら 10 回、100 人なら 100 回、1000 人なら 1000 回、この場合、計算量は O(N) であると表現します。

②N 人のプレイヤーがお互いに自分のいる座標を伝え合う(という状況があったとしたら)
1 人のプレイヤーは自分以外の (N-1)人のプレイヤーに自分の座標を伝えます。これを N 人全員が行います。計算の総回数は (N-1) x N = N^2 - N 回となります。10 人なら 90 回、100 人なら 9900 回、1000 人なら 999000 回です。急に大きくなります。N が大きければ -N 回の部分は無視できるほど小さくなるので、これはほぼ N^2 に比例した計算回数と言って差し支えありません。この場合、O(N^2) と表現されます。計算回数が指数を含む数式で表されるとき、N よりも N^2 の方が最終的な数値により影響を与えます。
①のケースでは 1000 人で 1000 回でしたが、②のケースでは 1000 人で 999000 回となり N^2 がより大きな影響を与えるということが分かったと思います。

③N 人のプレイヤーを身長順に並べる(という状況があったとしたら)
詳細は述べませんが、一般的には N 人のプレイヤーを身長順に並べるには O(NlogN) の計算量が必要になります。log の底は 2 です。log10≒3.3、log100≒6.6、log1000 ≒ 10 であり、logN < N で表現されるので、N * logN は N * N よりも小さな値となります。そのため、O(NlogN) の方が O(N^2) よりも優れています(より小さい)。

以下のグラフでは、青が NlogN、赤がN^2 のグラフ(NlogN の方がより小さい)であり、N が大きくなるほどその差は大きくなっていきます。逆に言うと N が小さいときはその差が小さいので計算量を意識する必要はありません。意識する必要があるのは N が大きいときです。
並べ替えを実現する複数の手法に対して、O(NlogN) である手法は、まぁ良しとされます。O(N^2) 以上である手法は、うーんやり直し!と言われるかもしれません。
④N 人のプレイヤーから金リンゴを奪う
プレイヤーのインベントリの大きさは左手や装備欄を除くと雑に36か所あります。プレイヤー 1 人から金リンゴを全て奪うには全てのインベントリに金リンゴがあるかどうかを 1 つ 1 つ調べて奪っていく作業が必要なので、プレイヤー 1 人あたり最大で 36 回の削除を行う必要があります1。100 人なら最大で36 x 100 = 3600 となり、非常に大きな数値になります。この計算量もプレイヤー数 N に対しては、同様に O(N) と表現されます。O記法では 36 という比例係数 k は無視されますが、この比例係数 k がどの程度大きいのかは意識しておく必要があります。

この作業は Minecraft のコマンドであれば、/clear @a golden_apple の1行で表現されますが、上記の通り k が大きいため注意して使う必要があります。
計算量 O(オーダー)のまとめ
計算量には O(1)、O(logN)、O(ルートN)、O(N)、O(NlogN)、O(N^2)、O(2^N)、O(N!)やそれぞれの掛け合わせなど色々あります。
Minecraft のコマンドの計算量が多いのかどうかは、その処理が実際にどうやって作られているんだろうというのを、紙に人の絵を書きながら1,2,3,4・・・と数えてみれば何となく分かるはずです。/clear をしたければインベントリを全部調べなきゃダメだよな、ということを頭の中にイメージする必要があります。
ボトルネックの発生箇所
そろそろ Minecraft マルチプレイのボトルネックについて説明していきます。どこで問題が起きそうかを何となく把握しておけば何となくの対応ができます。プレイヤーが増えることで何のリソースを消費する可能性があるのかを知っておきましょう。ボトルネックの解消方法は後述します。
①メモリ(リスク低)
メモリというのは一時的な情報を記憶する部分です。プレイヤーの情報はログインしてからログアウトするまでメモリに保持されます。逆にメモリに記憶しない情報というのは、読み込み範囲外のチャンク、読み込み範囲外のエンティティなど、すぐに使われる予定のないもの(オブジェクト)であり、これらは例えば HDD/SSD の中にデータとして保管されており、必要になったとき/必要になりそうなときにメモリに読み込まれます。
プレイヤー自身の情報
プレイヤーがどのようなネットワークを使って接続をしているのか、プレイヤーのスキン(プロフィール)は?プレイヤーのインベントリは?スポーン地点は?経験値は?
これらのプレイヤーのステータスに関する情報の量が1人辺りどれくらいの量になるかはおおよそ決まっています。ゲーマータグがめちゃめちゃ長い人やインベントリの中にとんでもない情報量のアイテムを持っていたとしても Minecraft では入力できる情報の量が制限されているので、個別のプレイヤーの情報がメモリに問題を与えることはほとんどなく、単純に人数だけが大きな影響を与えます。
チャンクの情報
チャンクにはそこに配置されている地形やエンティティ(Mobや動物、村人、アイテム)など、プレイヤーとは比べ物にならないほど多くの情報が含まれています。プレイヤー 1 人が前進すると、プレイヤーから一定距離離れた後方のチャンクがサーバーからアンロード(削除)されるかどうかが判定され、前方のチャンクをロードするかどうかが判定されます。既に別のプレイヤーがいる場合、サーバー上ではロードされるチャンクが徐々に増えていく(メモリで保持される)可能性が高くなっていきます。
サーバーの view-distance によって読み込むチャンク数が変わります。プレイヤーがいるチャンクから放射状に読み込むチャンクが広がっているため、プレイヤーが下図の青いチャンクの場所から上方向に進むと、新たにオレンジ色の箇所のチャンクが全てロードされ、逆に後方のチャンクは一定時間後にサーバーからアンロードされます。前方と後方の1チャンクだけが対象ではなく複数のチャンクが対象である、ということを抑えてください。
固定の配布マップやアドベンチャーワールドの場合は複数のプレイヤーが近くにいるため、サーバが同時に覚えておく必要があるチャンクの数が減り、メモリの消費量は最小限に抑えられます。プレイヤー全員が別の場所にいるようなサバイバルワールドの場合は、ロードするチャンクの量が多くなるため、その分メモリ消費量が増大します。
上図は view-distance = 2 のときの図ですが、オレンジ以外のチャンク数は 13 チャンクです。100人が同じ場所にいればサーバは 13 チャンクだけをロードしておけば良いですが、バラバラな場所にいる場合は 1300 チャンク程度をメモリにロードしておく必要があります。これが多いか少ないかはあなたのサーバのメモリ容量やサーバの設定と相談する必要があります。
Mod の情報
Mod を使うとメモリを大量に消費する、などと一般的には言われます。その消費量はその Mod がどれだけ複雑な実装をしているのか、そして、プレイヤー数 N に対してどのような情報を記憶するかによって変わります。通常は、あるプレイヤーに対してバニラには存在しない追加のステータスを記憶するだけなので、その消費量は N に比例するだけであり、その量も微々たるものです。
例えば、魔法が使える Mod であれば 1 番の魔法を使える/使えない、2 番の魔法を使える/使えない、といったプレイヤー毎の YES/NO の情報だけです。それに加えて、Mod 全体としては 1 番の魔法はこれ、2 番の魔法はこれ、その習得条件は?といった魔法1つ1つに対して固定量のメモリを消費します。
また、プレイヤーに対してペットが付いてくるような Mod であればプレイヤー1人に対して YES/NO の情報と、具体的に1体のエンティティ(ペット)の情報がメモリとして消費されます。プレイヤー1人につき、ペットが2体であれば2体のエンティティ分のメモリが消費されます。
どちらの Mod も、①プレイヤー数 N に比例するメモリ + ②プレイヤー数に関係なく使う Mod 固有の情報(魔法、ペットの定義)、で表現されますが、その量はプレイヤー数1人、2人、3人とで比較してみればざっくりと計算可能であり、大した量ではありません。
しかし、Mod を作っている人はある意味素人です。メモリは一時的に使うもの、ということを忘れて永遠にメモリを使い続ける実装をしてしまうこともあります。これはメモリリークと呼ばれる問題を引き起こすため、どれだけメモリを増やして改善しようと思っても改善されません。いつかは全てのメモリを食い潰してしまいます。メモリリークはメモリの消費量を頑張って見ていれば発見できます。しかし、これはプレイヤー数によってメモリが不足するという問題ではなく、ただのバグなので今回の話とはちょっと違います。このような Mod を見つけた場合はバグの報告をして、その Mod を使うのを辞めましょう。
下図1枚目のギザギザした赤い部分はメモリの消費量を表しています。上限を 100% としたときギザギザの天辺は 100% を表しています。上限まで使いきられたメモリは、一定間隔毎/一定量毎などで自動で不要なメモリがお掃除(GC と呼ばれる)されてメモリに余裕が生まれます。
メモリリークが発生している場合、この掃除が間に合わない/お掃除するものがない、などの理由でギザギザが下がりにくくなってくる状況が生まれます。これはメモリリークの兆候を表しています。
2枚目の画像はいよいよメモリ不足が深刻な状況になっており、サーバーがクラッシュ寸前(メモリが枯渇し、数ミリ秒だったサーバ Tick が一気に 50ミリ秒を超えた)になっています。
メモリリークを除けば、メモリのボトルネックは単に「必要最低限のメモリが足りていない」それだけなので、メモリのボトルネックは簡単に観測可能です。
②インターネット帯域(リスク中)
オンラインゲームの場合は、プレイヤーが何かする度に(サーバーで何か発生する度に)サーバーとクライアントの間にパケットが流れます。
Minecraft の場合は、プレイヤーが立ち止まらずに動いている場合、1 プレイヤー辺り 1 tickで最低 1 パケットの座標同期用のパケットがクライアントからサーバ方向へ流れます(サーバ視点ではダウンロード方向)。座標の同期パケットは 80 Bytes 程度のサイズです(詳細はプロトコル仕様を参照)。サーバはプレイヤーから座標同期用のパケットを受信すると、それを視界内の残りのプレイヤー全員へ連携します(サーバ視点ではアップロード方向)。また、プレイヤーが必要なチャンクを読み込んだ場合は数キロ Bytes のチャンクデータがサーバからクライアントへ分割/圧縮されて流れます(サーバ視点ではアップロード方向)。
さて、プレイヤー 100 人が密集している場合にはどのくらいのインターネット帯域が必要でしょうか?
サーバは 1 人のプレイヤーから受け取った座標同期用のパケットを残り全てのプレイヤーに連携する必要があるため、アップロード方向のパケット総数は N^2 に比例します。ざっくり計算するとこんな感じ。
80 * 8 (bits) * 20 (tick) * 100 (人) * 99 (人) * 1 (最低パケット数) = 126,720,000 ※126Mbps
最低パケット数を 1 にしましたが、移動パケットだけではなくブロックの破壊情報など様々なパケットがサーバからクライアントへ流れるため 100 人が安定して移動するには最低でも 126Mbps、特にプレイヤー1人が1チャンク移動すればチャンクデータも送信する必要があるので瞬間的には 1Gbps 近い帯域が必要となります。
さて 1000 人の場合はどうなるでしょう・・・?計算してみてください。肝はパケット数は N^2 に比例するということです。
③CPU(リスク高)
問題になるのは大抵こいつです。Minecraft サーバのメイン処理はシングルスレッドで動作しています。脳みそが1つしかないので大量の処理は全て順番待ちになり、それがラグ(遅延)の原因になります。Minecraft では CPU ボトルネックとなる可能性のある部分にはデフォルトで制限が設けられています。
一度に送信できるチャンク数
サーバには多数のプレイヤーが接続します。1 人目に 1000 チャンクの情報を送信して、2 人目にチャンクを送れなくなってはマルチプレイサーバの意味がありません。サーバは1つ1つの仕事全てに CPU を使います。プレイヤー全員に均等にフェアにマルチプレイの環境を提供するために、1 人あたりにどのくらいの情報を送信できるかが制限されています。無尽蔵にリソースを使わないように足枷を付けている訳ですね。
1度に fill できるブロック数(=32768)
ミニゲームでこれだけのブロックを 1tick の間に置き換えることは稀でしょう。ゲームのロード時やリトライ時にステージをリセットするために大量のブロックを初期化する可能性がありますが、その負荷はその tick だけなのでカクつくのは一瞬です。もしプレイヤーが毎 tick 100 個のブロックを fill するような処理だった場合、100 人のプレイヤーがいると毎 tick 10000 個のブロックが fill されているのと同等の負荷がかかります(正確にはちょっと違う)。
1回のコマンドで連鎖できるコマンド数(=65536)
コマンドの種類によってリスクは全然変わりますが、ようするに 1 tick の間に 65536 回を超える処理をするのは気を付けてねということです。しかし、今想定しているのはマルチプレイです。処理内容がプレイヤー数 N に対して O(N^2) となるコマンドを必要とするものだった場合はどうなるでしょう?100 人の場合は、100 * 100 = 10000 の処理が必要です。つまり、データパックにたったの 6 行のコマンドを書くだけでサーバの処理限界に達する可能性があるということです。
100 人、1000 人で遊べるマルチプレイサーバでは O(NlogN) 以下の処理が要求されてきます。もっというと tick 処理で何かを行うこと自体が大きなリスクとなるため、効率的にイベント検知ができる Mod やプラグインによる処理が要求されます。
ボトルネックの特定方法
大抵の人は対策の前にそもそも特定で手が止まります。原因を特定するためには意味のあるテストが必要です。意味のあるベンチマークを行って指標となる数値を取得してください。それに尽きます。

エンティティ数が問題だと思うならエンティティ数を増やす/減らす、プレイヤー数が問題だと思うならプレイヤー数を増やす/減らす、チャンク数が問題だと思うならチャンクの読み込み範囲を増やす/減らす、などの比較を行って具体的に何がどれくらいどうなったのかを数値で定量的に出して議論しましょう。数値を見ずに「何となく重い」「何となく軽い」は何の意味もありません。
物理サーバのリソースの問題であれば、サーバのメモリ使用率や CPU 使用率を見たり、GC(メモリの掃除) がどのように行われているかを調べればよいです。稀にファイルの読み書きの回数などが上限に達することもあります。
物理サーバの問題ではなく、いざ Minecraft の中の話かも?となってくれば、Minecraft にはベンチマークを行う仕組みがもともと用意されています。サーバであれば /perf コマンド、統合サーバであれば F3 + L を使えば、サーバーのプロファイリングを行ってくれます。spark などの外部ツールを使うことでより詳細なプロファイルを行うこともできます。
通常は /perf コマンドや F3 + L で十分です。どの処理が問題なのか、何のエンティティが問題なのか、何のコマンドが問題なのかおおよそ特定できます。
特定してしまえばあとは対策するだけです。
対策
まず最初に、負荷軽減は自分たちが「何人のマルチプレイを想定しているのか」目標値を決めてから行いましょう。負荷軽減は N が大き過ぎて問題が起きているとき/起きると想定されるときに行ってください。もっというと、サーバの構築段階から N が変わってしまっていたらボトルネックが発生するのは当然です。なんせ目標としていた N が変わってしまっているんですから。目標とする N に向けてサーバを作りましょう。
加えてすごく冷たい言い方をすると、問題が起きない/起きないことが分かっている環境で負荷軽減を頑張るのはナンセンスです。問題が起きないのに「計算量を2分の1に減らしましたワーイ!」なんていうのは学生がやることです。暇な人がやることです。その成果に対して報酬なんて存在しません。つまり自己満足です。ということを声を大にして言っておきます。
①メモリ
身も蓋もないですが、メモリが足りなければ足すしかありません。10 人のマルチプレイでリソース不足が発生するとすればそれはメモリです。インターネット帯域や CPU の処理が 10 人で足りなくなることはほぼあり得ません。ハードディスクを使っていてもディスクのアクセス速度が問題にはなることは無いでしょう。最低価格のレンタルサーバは 1GB/2GB のメモリしか搭載していないこともあるので、プレイヤー+ワールドの読み込みを考えると足りなくなることがあります。
必要なメモリ容量
Minecraft はプレイヤー 1 人あたり 20 ~ 30MB 程度のメモリを消費すると言われています(バージョンによって変わるかもしれません)。加えて、ワールドの規模によって多少メモリが必要になります。大目に見て 1 人あたり 50 MB 程度とすれば、単純計算なら 100 人で 5GB なので、16GB、32GB 搭載している最近のミドルレンジ以上のゲーミング PC ならほぼほぼ問題が起きることはありません。
メモリ増強(使える上限を増やす)
32GB のメモリはものによりますが 2~3万円で買うことができます。クラウドであればレンタルするサーバのメモリを増やすのは簡単です。お金を払って必要な時だけ必要な分のメモリを借りるだけなら安く済ませることもできます。
負荷軽減 Mod、プラグインの導入(使用量を減らす)
Mod を入れるとメモリの消費量が増える。ごもっともです。しかし、負荷軽減 Mod、プラグインやメモリ消費量を減らす Mod を入れればその問題は解決するかもしれません。バニラの Minecraft はメモリの使い方が非効率な(最適では無い)部分があります。それを最適化したり、無駄な処理を行わないように処理自体を省いてしまえばサーバのメモリの使用量を最適化して減らすことができます。
チャンク/エンティティの読み込み制限(データ量を減らす)
ロードするチャンク数、エンティティ数に制限をかければメモリ消費量を減らすことができます。コンテンツの内容を減らしたくない場合には、ワールドの規模を縮小する努力をしましょう。
チャンクの読み込み制限はメモリの消費量を減らすことができますが、一方で CPU の負荷を増大させる可能性があります。より早くアンロードされたチャンクは、プレイヤーの移動によってまたすぐにロードされる可能性があるからです。頻度が早くなるということは CPU に負荷がかかるということです。
リソースの枯渇した環境において、メモリの使用量と CPU 負荷はトレードオフ(どちらかが上がるとどちらかが下がる)の関係があります。自分のサーバのボトルネックが何なのかを観測し、どちらも余力のある適切なバランスを見つけることが重要です。
②インターネット帯域
プレイヤー数が 100 人を超えてくるとインターネット帯域の問題が出始めます。これはメモリよりも深刻です。自宅のサーバでインターネット帯域を増やすことは簡単にはできません。しかもサーバの場合はアップロード帯域が安定して高速である必要があります。帯域が足りなければ大人しくレンタルサーバを借りましょう。しかし、レンタルするサービスの説明をよく読む必要があります。
帯域を増やす(使える上限を増やす)
安いサービスの場合、回線速度がベストエフォートと書かれていることがあります。記載されている速度を上限としてそのとき空いている分だけ頑張って提供しますということです。しかも回線速度の上限が 100Mbps であることもあります。
高いサービスを探せば 1Gbps を保証していることもあります。優良なクラウドサーバであれば 10Gbps、40Gbps という広帯域なネットワークを使える場合もあります。身も蓋も無いですがお金を払いましょう。
プレイヤーを分散させる(パケット数を減らす)
先に書いた通り、プレイヤーが密集している場合、プレイヤーが自分以外のプレイヤーに情報を連携するために N^2 に比例したパケットが必要となります。100 人が密集していれば最低 126Mbps 程度になるという話でした。
ですが、これが 50 人ずつの 2 グループ、25 人ずつの 4 グループと分散されていくと話は変わってきます。
80 * 8 (bits) * 20 (tick) * 50 (人) * 49 (人) * 1 (最低パケット) * 2(グループ)= 62,720,000 ※62Mbps
80 * 8 (bits) * 20 (tick) * 25 (人) * 24 (人) * 1 (最低パケット) * 4(グループ)= 30,720,000 ※30Mbps
相互にパケットを交換するプレイヤーが減るため、その分必要な帯域は減ります。このグループがお互いに干渉せず、遠い場所でアドベンチャーワールドとして遊んでいれば良いのです。もちろん、グループが分かれることによって読み込むチャンクが増える可能性があるため絶対に減るわけではありません。
プレイヤーを分散させるためにはコンテンツを分散させる必要があります。2グループ、4グループが集団でゆっくりと動き、お互いが衝突しないようなコンテンツを作り、そこにプレイヤーが向かうための導線を考えるのは、ワールドの作り手、企画の作り手の腕の見せ所です。
小さなミニゲームに飛ばしてしまうのも1つの手かもしれません。
チャンク/エンティティの送信制限(データ量を減らす)
view-distance、entity-broadcast-range-percentage を変えることでプレイヤーが視認できるチャンク/エンティティを減らすことができます。チャンクの方はサーバのデフォルト設定では 10 に設定されていますが、これを 5 に変えればチャンクのデータ量が 4 分の 1 程度に減ります。プレイヤー同士の座標の同期よりも、チャンクの送信の方が問題だなと感じる場合には、チャンクの送信レートを制限することもできます。チャンクの量、チャンクの送信レート、視認できるエンティティ数を調整することで必要なインターネット帯域を減らしましょう。
負荷軽減 Mod、プラグイン の導入(データ量を減らす)
上で書いたこととほぼ同じですが、Mod やプラグインを入れることでチャンクの送信情報をきめ細やかに制御することができます。
サーバの分割(プレイヤー数の削減)
サーバ内でプレイヤーを分散できないのであれば、コンテンツの進行状況でサーバを分割(横割り)したり、コンテンツ自体を複数サーバに分割(縦割り)して、プレイヤー数を分散させるのが一番良い方法です。全員が同じサーバで同時に集まる機会を減らす努力をしましょう。横割りした場合は、最終的にプレイヤーが行き着く場所が同じ場合、同じサーバにログインすることになるので注意してください。
プレイヤーの導線を分けるにはコンテンツの分割だけではなく、行列の演出、一度にゲートへ入れるプレイヤー数の上限などを設定することも大事です。スムーズな環境を作ることだけがボトルネックを解消する手段ではありません。制約を課すことによってあえて不自由な環境を作り、1か所に集まるプレイヤー数を減らすという方法も大切です。
③CPU
プレイヤー数が 1000 人を超えてくると、いよいよ CPU が大変なことになってきます。1 コマンドが 1000 コマンドに、10 コマンドが 10000 コマンドに増幅される可能性があります。
物理的にハイスペックな CPU に変えるということも簡単にはできませんし、その上限も目立って変わる訳ではありません。4 GHz で動作する CPU クロックをスーパー限界までオーバークロックした 6 GHz のスーパーゲーミング PC に変えたところで処理速度が 1.5 倍になるだけです。そもそもゲーミング PC ではインターネット帯域が足りないためレンタルサーバを借りる必要があります。その辺のレンタルサーバには 6GHz の CPU なんて存在しません。
軽量化サーバの利用
とはいえ、Minecraft であれば意外と何とかなります。Minecraft はバニラサーバが非常に非効率な動作をしているので、軽量化されたプラグインサーバを使えば劇的に性能は改善します。
Spigot 以降の Bukkit サーバであれば、かなりきめ細やかにサーバの負荷を減らすための設定があります。チャンクの送信レートやエンティティの生存時間、エンティティのスポーン数、アイテムの消滅時間などを変えるだけでサーバの CPU 負荷をかなり減らすことができます。しかし 1000 人だと O(N) の処理が最低でも 1000 というのはかなり気を使います。
コマンドの見直し
Mod やプラグインであれば非効率なコードかどうかは一目で分かります。コードを見れば O(オーダー)が分かるからです。しかし、コマンドの場合は先に書いた /clear コマンドのように一見すると問題なさそうなコマンドでも負荷がかかります。コマンドの見直しポイントは以下の通りです。
①tick の廃止
可能な限り tick を使ったコマンドの記載を減らしてください。
②execute as の入れ子を最小限に
下記のコマンドは O(N^2) の処理が発生します。使う場合は必ず @a に条件を設定しましょう。
execute as @a at @s run execute as @a[...]
③同時実行数の制限
同時に実行するプレイヤー数の上限を設定してください。もちろん tick 毎に違うプレイヤーが選択されるように工夫する必要はあります。同時実行数を制限して、プレイヤーを複数時間に分散すれば、分散した数だけ処理負荷を減らすことができます。
execute as @a[tag=hoge,limit=n]
④フラグの軽量化
頻繁にアクセスするフラグの保管場所を NBT(ストレージやエンティティのNBTなど)にするのはやめましょう。アクセスのコストが非常に悪いです。不自由がなければプレイヤーの tag を付け外しして tag をフラグとして利用しましょう。
⑤コマンドを 1 行毎にベンチマークする
コマンドの実行時間を出力するプラグインを作りましょう。話はそれからだ。ベンチマークできるようになればあとはテストすれば良いです。迷っている時間が無駄です。
⑥ソースコードを読む
ソースコードを読めば全ての問題は解決します。プロファイリングの結果を読むためにもソースコードを読む力が多少必要になります。
まとめ
- 定量的に評価しろ!
- 足りなければお金払って足せ!
- 目標となる N を設定しろ!
- メモリの使用量と CPU 負荷はトレードオフ!
- テストしろ!
- ボトルネックを解消するにはボトルネックを作ることも大事だ!
- ソースコードを読め!
さいごに
前回に引き続き、最後まで読んでくれた人ありがとうございました。
このコラム(うんちく)を読んだからといっていきなり大規模サーバを低負荷で運用できる訳じゃありませんが、察しが良い人ならここはこうすれば良いのね、みたいな事は感じ取って貰えたと思います(前回と同じこと書いてる)。
-
Minecraft の場合は 36 回ですが、削除処理はその要素数 M に対して O(M^2)になることがあるので注意してください ↩