なぜこのソルバーが実務で重要なのか
データセンター運用において「どのオブジェクトをどのビンに割り当てるか」という問いは、想像以上に頻繁に登場します。ラックをデータセンターに配置する、サーバーをサービスに割り当てる、タスクをサーバーに分散する、トラフィックを地域別データセンターにルーティングする——これらはすべて実質的に**割り当て問題(Assignment Problem)**です。
問題はこれがNP-hardであるという点です。商用ソルバーでもハイパースケール規模は扱いきれません。Metaはこの問題を9年以上にわたり社内ツールで解いてきましたが、先日Rebalancerをオープンソースとして公開しました。根拠資料はMeta Engineeringブログで確認できます。
設計思想はシンプルです。問題定義(Specification)と解決(Solving)を分離する。 この分離がユーザビリティ、拡張性、保守性をすべて両立させる鍵となっています。

核心構造: Expression GraphとSpec API
Rebalancerは問題を3段階で抽象化します。
- モデリング構成要素: dimensions(オブジェクト/ビンの属性)、partitions(オブジェクトのグループ)、scopes(ビンのグループ)、utilization(ビンに割り当てられたオブジェクトの寄与度)
- Expression API: SUM、MAX、SQUAREなどの演算で構成要素を再帰的に組み合わせる
- Spec API: 数十種類の目的関数・制約を定義済みレシピとして提供
例えば、タスクをサーバーに配置する問題は次のように表現できます。
from rebalancer import (
Problem, Dimension, Partition, Scope,
CapacitySpec, GroupCountSpec, BalanceSpec
)
# サーバー(ビン)とタスク(オブジェクト)を定義
problem = Problem()
server = problem.add_bin_set("server")
task = problem.add_object_set("task")
# CPU/ストレージをdimensionとしてモデリング
cpu = Dimension("cpu", objects=task, bins=server)
storage = Dimension("storage", objects=task, bins=server)
# ラック単位のscope、job単位のpartition
rack = Scope("rack", bins=server)
job = Partition("job", objects=task)
# 制約: サーバー容量の上限
problem.add_spec(CapacitySpec(dimensions=[cpu, storage]))
# 制約: ラックごとに1つのjobのみ許可
problem.add_spec(GroupCountSpec(partition=job, scope=rack, max_count=1))
# 目的: CPU/ストレージ使用率のバランス
problem.add_spec(BalanceSpec(dimensions=[cpu, storage]))
# 初期割り当てと終了条件を設定して求解
solution = problem.solve(initial=current_assignment, time_limit_sec=30)
print(solution.objective_value)
このように定義された問題は内部でexpression graphに変換されます。リーフノードがutilizationであり、その上にMax/Sum/Square/Absといった集約・変換ノードが積み重なる構造です。割り当てが変わるたびに各ノードの値を更新する必要がある点が、性能上の要点となります。

2種類のソルバー、どちらを使うべきか
Rebalancerは状況に応じて2つの解法を提供します。
| 区分 | Optimal Solver (MIP) | Local Search Solver |
|---|---|---|
| 方式 | Expression graphをMIPに変換しFICO Xpress/Gurobi/HiGHSで求解 | Expression graph上で直接近傍探索 |
| 最悪モデルサイズ | O(|objects| × |bins|) (二次) | O(|objects| + |bins|) (線形) |
| 適する規模 | 中小規模、短い求解時間 | 大規模、ハイパースケール |
| Metaでの実利用 | プロトタイピング、ローカルサーチのチューニング | 大規模プロダクション問題の大半 |
注意点として、MIP変換時にはutilizationを二値決定変数の重み付き和で表現する必要があるため、モデルが急速に肥大化します。Rebalancerはvariable aggregation、interchangeability、symmetry breakingでモデルを削減しますが、最悪ケースでは依然として二次の複雑度です。
一方、ローカルサーチは現在の割り当て周辺の近傍のみを探索するためメモリ制約を回避でき、毎秒数百万回の評価が可能なほど最適化されています。実務の指針は次の通りです。
- プロトタイピング: Optimal Solverで素早くベースラインを確保
- プロダクション大規模: Local Searchへ移行
- オフラインチューニング: Optimal SolverでLocal Searchのパラメータを調整
Metaの実利用データも参考になります。1日あたり約4,000万件の割り当て問題を30種類以上の問題定式で解いており、P99求解時間は26万5千オブジェクト/3,200ビンで12秒です。100万オブジェクト/5千ビン規模では平均171秒を要します。

実務適用と注意事項
日本の開発エコシステムにおける適用文脈: 国内のSI/クラウド環境では、Kubernetesスケジューラやバッチジョブの割り当て、物流最適化などに応用できます。ただしRebalancerはPythonパッケージとして提供されるため、既存のJava/Goベースのインフラに組み込む場合は別マイクロサービスとして分離するのが現実的です。
この技術の限界:
- MIPソルバーは商用ライセンス(FICO Xpress、Gurobi)がない場合、HiGHSでは性能面で制約を受ける可能性があります。
- ローカルサーチは大域最適解を保証しません。初期割り当ての品質が結果に大きく影響します。
- デバッグUIのRebalancer ExplorerはDockerイメージで提供されますが、大規模問題では可視化自体が重くなる可能性があります。
次のステップ学習方向:
- Rebalancer公式ドキュメントのチュートリアルで小規模問題から再現
- OSDI'24論文で理論的背景を補強
- Kubernetesスケジューラプラグインと比較しながら実務適用シナリオを設計
割り当て問題は物流、ヘルスケア、エネルギーなど、ほぼすべての産業に存在します。Metaがインフラ問題のために作ったツールですが、汎用ライブラリとして設計されているため、読者のドメインにも十分適用できるはずです。
あわせて読みたい記事