はじめに
サーバーの健全性を判断する重要な指標のひとつが**システムロードアベレージ(load average)**です。「直近1分・5分・15分の平均的な処理待ちプロセス数」を表すこの値を、PHPから直接取得できるのが sys_getloadavg() です。
ヘルスチェックエンドポイント・負荷に応じた処理の調整(スロットリング)・監視ダッシュボードなど、サーバーサイドアプリケーションの安定運用に役立つ関数です。ただしWindowsでは使えないなどプラットフォーム依存の制約があるため、本記事ではその点も含めて詳しく解説します。
関数の基本情報
| 項目 | 内容 |
|---|---|
| 関数名 | sys_getloadavg() |
| 利用可能バージョン | PHP 5.1.3以降 |
| 所属 | システム関数(Misc Functions) |
| 戻り値 | array|false(3要素の配列、取得失敗時はfalse) |
| 拡張機能 | 不要(コア関数) |
| 対応OS | Linux・macOS・BSD系など Unix系OSのみ(Windowsは未対応) |
シグネチャ
sys_getloadavg(): array|false
パラメータ
なし。
戻り値の形式
[
0 => 1.23, // 直近1分の平均ロード
1 => 0.98, // 直近5分の平均ロード
2 => 0.75, // 直近15分の平均ロード
]
| インデックス | 意味 |
|---|---|
[0] | 直近1分間の平均ロード |
[1] | 直近5分間の平均ロード |
[2] | 直近15分間の平均ロード |
ロードアベレージとは: CPUを使用中、または使用待ち(I/O待ちを含む)のプロセス数の平均値です。値がCPUコア数を超えると、システムに処理待ちが発生していることを示します。
Windowsでの動作
$load = sys_getloadavg();
if ($load === false) {
echo "この環境ではロードアベレージを取得できません(Windowsなど)\n";
}
Windowsには「ロードアベレージ」という概念自体が存在しないため、Windows環境では常に false が返ります。クロスプラットフォーム対応のコードでは必ずこの判定が必要です。
実践サンプル集(PHP 8.x対応)
サンプル1:基本的な取得と表示
<?php
declare(strict_types=1);
$load = sys_getloadavg();
if ($load === false) {
echo "ロードアベレージを取得できません(この環境では非対応)\n";
} else {
[$load1, $load5, $load15] = $load;
printf("1分平均: %.2f\n", $load1);
printf("5分平均: %.2f\n", $load5);
printf("15分平均: %.2f\n", $load15);
// CPUコア数と比較
$cpuCount = (int) shell_exec('nproc 2>/dev/null') ?: 1;
echo "\nCPUコア数: {$cpuCount}\n";
$ratio = round($load1 / $cpuCount, 2);
echo "コアあたりの負荷率(1分): {$ratio}\n";
if ($ratio > 1.0) {
echo "⚠️ CPUコア数を超える負荷がかかっています\n";
} else {
echo "✅ 負荷は正常範囲内です\n";
}
}
実行結果(Linux環境の例):
1分平均: 0.45
5分平均: 0.62
15分平均: 0.71
CPUコア数: 4
コアあたりの負荷率(1分): 0.11
✅ 負荷は正常範囲内です
解説: ロードアベレージは絶対値だけでは判断しづらいため、CPUコア数で割った「コアあたりの負荷率」に変換すると解釈しやすくなります。一般的に1.0を超えると処理待ちが発生している状態です。
サンプル2:ヘルスチェックエンドポイントへの組み込み
Webアプリケーションの /health エンドポイントでロード状況を返すパターンです。
<?php
declare(strict_types=1);
class HealthChecker
{
public function __construct(
private readonly float $warningThreshold = 0.8,
private readonly float $criticalThreshold = 1.5,
) {}
public function check(): array
{
$load = sys_getloadavg();
if ($load === false) {
return [
'status' => 'unknown',
'message' => 'この環境ではロードアベレージを取得できません',
];
}
[$load1, $load5, $load15] = $load;
$cpuCount = $this->getCpuCount();
$ratio1 = $cpuCount > 0 ? $load1 / $cpuCount : 0;
$status = match (true) {
$ratio1 >= $this->criticalThreshold => 'critical',
$ratio1 >= $this->warningThreshold => 'warning',
default => 'healthy',
};
return [
'status' => $status,
'load' => [
'1min' => round($load1, 2),
'5min' => round($load5, 2),
'15min' => round($load15, 2),
],
'cpu_count' => $cpuCount,
'load_ratio' => round($ratio1, 2),
'timestamp' => date('c'),
];
}
private function getCpuCount(): int
{
if (is_readable('/proc/cpuinfo')) {
$cpuinfo = file_get_contents('/proc/cpuinfo');
return max(1, substr_count($cpuinfo ?: '', 'processor'));
}
$nproc = trim(shell_exec('nproc 2>/dev/null') ?? '');
return $nproc !== '' ? (int)$nproc : 1;
}
public function toHttpResponse(): array
{
$result = $this->check();
$httpStatus = match ($result['status']) {
'critical' => 503, // Service Unavailable
'warning' => 200, // 警告だが稼働中
'healthy' => 200,
default => 200,
};
return ['http_status' => $httpStatus, 'body' => $result];
}
}
// --- 使用例 ---
$checker = new HealthChecker(warningThreshold: 0.7, criticalThreshold: 1.2);
$response = $checker->toHttpResponse();
echo "HTTP Status: {$response['http_status']}\n";
echo json_encode($response['body'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
実行結果(例):
HTTP Status: 200
{
"status": "healthy",
"load": {
"1min": 0.45,
"5min": 0.62,
"15min": 0.71
},
"cpu_count": 4,
"load_ratio": 0.11,
"timestamp": "2026-06-24T10:30:00+09:00"
}
解説: critical 状態のときに HTTP 503 を返すことで、ロードバランサーやオーケストレーター(Kubernetesなど)に「このインスタンスへのリクエストを一時的に減らすべき」と伝えられます。
サンプル3:負荷に応じた処理のスロットリング
高負荷時に重い処理(バッチ・レポート生成など)を自動的に制限するパターンです。
<?php
declare(strict_types=1);
class LoadAwareThrottler
{
public function __construct(
private readonly float $maxRatio = 1.0,
private readonly int $cpuCount = 0,
) {}
/**
* 現在の負荷状況に基づいて処理を実行してよいか判定
*/
public function shouldThrottle(): bool
{
$load = sys_getloadavg();
if ($load === false) {
return false; // 判定できない場合はスロットリングしない
}
$cpuCount = $this->cpuCount > 0 ? $this->cpuCount : $this->detectCpuCount();
$ratio = $load[0] / max(1, $cpuCount);
return $ratio >= $this->maxRatio;
}
/**
* 負荷状況に応じて並列度を動的に決定
*/
public function recommendedConcurrency(int $maxConcurrency): int
{
$load = sys_getloadavg();
if ($load === false) {
return $maxConcurrency;
}
$cpuCount = $this->cpuCount > 0 ? $this->cpuCount : $this->detectCpuCount();
$ratio = $load[0] / max(1, $cpuCount);
// 負荷が高いほど並列度を下げる
$factor = match (true) {
$ratio >= 1.5 => 0.25,
$ratio >= 1.0 => 0.5,
$ratio >= 0.7 => 0.75,
default => 1.0,
};
return max(1, (int) floor($maxConcurrency * $factor));
}
private function detectCpuCount(): int
{
$nproc = trim(shell_exec('nproc 2>/dev/null') ?? '');
return $nproc !== '' ? (int)$nproc : 1;
}
}
// --- 使用例:バッチ処理の実行可否判定 ---
$throttler = new LoadAwareThrottler(maxRatio: 0.8);
function runHeavyBatchJob(LoadAwareThrottler $throttler, array $items): void
{
if ($throttler->shouldThrottle()) {
echo "⚠️ サーバー負荷が高いため、バッチ処理を延期します\n";
return;
}
$concurrency = $throttler->recommendedConcurrency(maxConcurrency: 10);
echo "バッチ処理を実行: 並列度={$concurrency} / 件数=" . count($items) . "\n";
foreach (array_chunk($items, $concurrency) as $chunkIndex => $chunk) {
echo " チャンク{$chunkIndex}: " . count($chunk) . "件処理\n";
}
}
$items = range(1, 50);
runHeavyBatchJob($throttler, $items);
実行結果(負荷が低い場合の例):
バッチ処理を実行: 並列度=10 / 件数=50
チャンク0: 10件処理
チャンク1: 10件処理
チャンク2: 10件処理
チャンク3: 10件処理
チャンク4: 10件処理
解説: ロードアベレージを参照して並列度を動的に調整することで、負荷の高いタイミングでバッチ処理がサーバーに追加負荷をかけることを防げます。CI/CDのワーカーやキュー処理システムでの応用が考えられます。
サンプル4:定期的な負荷ロギングシステム
ロードアベレージを時系列でログに記録し、後で分析できるようにするパターンです。
<?php
declare(strict_types=1);
class LoadLogger
{
public function __construct(
private readonly string $logFile
) {}
/**
* 現在の負荷状況をログに1行追記する
*/
public function record(): bool
{
$load = sys_getloadavg();
if ($load === false) {
return false;
}
$entry = [
'timestamp' => date('Y-m-d H:i:s'),
'load_1' => round($load[0], 3),
'load_5' => round($load[1], 3),
'load_15' => round($load[2], 3),
'memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 1),
];
$line = implode(',', $entry) . "\n";
return file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX) !== false;
}
/**
* ログファイルを解析して統計を出す
*/
public function analyze(): array
{
if (!file_exists($this->logFile)) {
return ['error' => 'ログファイルが存在しません'];
}
$lines = file($this->logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$load1s = [];
foreach ($lines as $line) {
$parts = explode(',', $line);
if (count($parts) >= 2 && is_numeric($parts[1])) {
$load1s[] = (float)$parts[1];
}
}
if (empty($load1s)) {
return ['error' => '有効なデータがありません'];
}
return [
'count' => count($load1s),
'min' => round(min($load1s), 3),
'max' => round(max($load1s), 3),
'avg' => round(array_sum($load1s) / count($load1s), 3),
'latest' => round(end($load1s), 3),
];
}
}
// --- 使用例 ---
$logFile = sys_get_temp_dir() . '/load_average.log';
$logger = new LoadLogger($logFile);
// 定期実行を想定(実際にはcronやワーカーループ内で呼ぶ)
for ($i = 0; $i < 5; $i++) {
$success = $logger->record();
echo "記録{$i}: " . ($success ? 'OK' : 'NG') . "\n";
usleep(100_000); // デモ用の短い待機
}
echo "\n--- 分析結果 ---\n";
print_r($logger->analyze());
// クリーンアップ(デモ用)
unlink($logFile);
実行結果:
記録0: OK
記録1: OK
記録2: OK
記録3: OK
記録4: OK
--- 分析結果 ---
Array
(
[count] => 5
[min] => 0.42
[max] => 0.48
[avg] => 0.451
[latest] => 0.44
)
解説: CSVライクな形式でログに記録し、後から min/max/avg を計算できます。cron で定期実行すれば、簡易的な負荷モニタリングシステムとして機能します。本格的な監視にはPrometheus等の専用ツールを推奨しますが、軽量な自前実装としては十分実用的です。
サンプル5:クロスプラットフォーム対応のラッパー
Windowsでも動作する(疑似的な代替値を返す)ラッパークラスです。
<?php
declare(strict_types=1);
class PortableLoadAverage
{
/**
* @return array{available: bool, load1: float|null, load5: float|null, load15: float|null, platform: string}
*/
public static function get(): array
{
$platform = PHP_OS_FAMILY; // 'Linux', 'Darwin', 'Windows', 'BSD' など
$load = sys_getloadavg();
if ($load !== false) {
return [
'available' => true,
'load1' => $load[0],
'load5' => $load[1],
'load15' => $load[2],
'platform' => $platform,
];
}
// Windows向けフォールバック:CPU使用率で近似(typeperf等を利用する例)
if ($platform === 'Windows') {
$cpuUsage = self::getWindowsCpuUsage();
return [
'available' => $cpuUsage !== null,
'load1' => $cpuUsage,
'load5' => null, // Windowsでは時間平均の取得が難しい
'load15' => null,
'platform' => $platform,
];
}
return [
'available' => false,
'load1' => null,
'load5' => null,
'load15' => null,
'platform' => $platform,
];
}
/**
* Windows環境でのCPU使用率取得(wmicを利用、利用可否は環境依存)
*/
private static function getWindowsCpuUsage(): ?float
{
if (!function_exists('shell_exec')) {
return null;
}
$output = @shell_exec('wmic cpu get loadpercentage /value');
if ($output === null) {
return null;
}
if (preg_match('/LoadPercentage=(\d+)/', $output, $matches)) {
// 0-100の値を0-1スケールに変換(ロードアベレージのイメージに近づける)
return ((float)$matches[1]) / 100;
}
return null;
}
public static function describe(): string
{
$info = self::get();
if (!$info['available']) {
return "負荷情報を取得できません(プラットフォーム: {$info['platform']})";
}
if ($info['load5'] === null) {
// Windowsの簡易CPU使用率
return sprintf(
"CPU使用率: %.1f%% (プラットフォーム: %s、簡易値)",
$info['load1'] * 100,
$info['platform']
);
}
return sprintf(
"ロードアベレージ: 1分=%.2f / 5分=%.2f / 15分=%.2f (プラットフォーム: %s)",
$info['load1'],
$info['load5'],
$info['load15'],
$info['platform']
);
}
}
// --- 使用例 ---
echo PortableLoadAverage::describe() . "\n";
print_r(PortableLoadAverage::get());
実行結果(Linux環境の例):
ロードアベレージ: 1分=0.45 / 5分=0.62 / 15分=0.71 (プラットフォーム: Linux)
Array
(
[available] => 1
[load1] => 0.45
[load5] => 0.62
[load15] => 0.71
[platform] => Linux
)
解説: PHP_OS_FAMILY でプラットフォームを判定し、Unix系では sys_getloadavg()、Windowsでは wmic コマンドによる簡易的なCPU使用率取得にフォールバックします。完全な互換性はありませんが、クロスプラットフォームなツールで「何らかの負荷指標」を提供したい場合に有効です。
サンプル6:複数サーバーの負荷を集約するダッシュボード用API
複数のアプリケーションサーバーから収集したロードアベレージを集約するパターンです(単一サーバー内のデモ実装)。
<?php
declare(strict_types=1);
class ClusterLoadDashboard
{
/** @var array<string, array> サーバー名 → 負荷情報 */
private array $servers = [];
/**
* 自サーバーの負荷情報を登録
*/
public function registerSelf(string $serverName): void
{
$load = sys_getloadavg();
$this->servers[$serverName] = [
'load' => $load !== false ? $load : null,
'hostname' => gethostname() ?: 'unknown',
'timestamp' => time(),
];
}
/**
* (デモ用)外部サーバーのデータを手動で追加
* 実際には他サーバーのヘルスチェックAPIをHTTPで叩いて取得する想定
*/
public function addExternalServer(string $name, array $loadData): void
{
$this->servers[$name] = [
'load' => $loadData,
'hostname' => $name,
'timestamp' => time(),
];
}
/**
* クラスタ全体の統計を計算
*/
public function clusterStats(): array
{
$load1Values = [];
foreach ($this->servers as $info) {
if ($info['load'] !== null) {
$load1Values[] = $info['load'][0];
}
}
if (empty($load1Values)) {
return ['error' => 'データがありません'];
}
return [
'server_count' => count($this->servers),
'reporting_count' => count($load1Values),
'avg_load1' => round(array_sum($load1Values) / count($load1Values), 3),
'max_load1' => round(max($load1Values), 3),
'min_load1' => round(min($load1Values), 3),
'busiest_server' => $this->findBusiest(),
];
}
private function findBusiest(): ?string
{
$maxLoad = -1;
$busiest = null;
foreach ($this->servers as $name => $info) {
if ($info['load'] !== null && $info['load'][0] > $maxLoad) {
$maxLoad = $info['load'][0];
$busiest = $name;
}
}
return $busiest;
}
public function render(): void
{
echo "=== クラスタ負荷ダッシュボード ===\n";
foreach ($this->servers as $name => $info) {
if ($info['load'] === null) {
echo " {$name}: データなし\n";
continue;
}
printf(
" %-15s 1分=%.2f 5分=%.2f 15分=%.2f\n",
$name,
$info['load'][0],
$info['load'][1],
$info['load'][2]
);
}
echo "\n--- 集約統計 ---\n";
print_r($this->clusterStats());
}
}
// --- 使用例 ---
$dashboard = new ClusterLoadDashboard();
// 自サーバーの実データを登録
$dashboard->registerSelf('web-server-01');
// 他サーバーのデータ(実際はAPI経由で取得)をシミュレート
$dashboard->addExternalServer('web-server-02', [0.85, 0.92, 0.78]);
$dashboard->addExternalServer('web-server-03', [1.45, 1.32, 1.10]); // 高負荷サーバー
$dashboard->addExternalServer('worker-01', [2.10, 1.95, 1.60]); // 最も高負荷
$dashboard->render();
実行結果(例):
=== クラスタ負荷ダッシュボード ===
web-server-01 1分=0.45 5分=0.62 15分=0.71
web-server-02 1分=0.85 5分=0.92 15分=0.78
web-server-03 1分=1.45 5分=1.32 15分=1.10
worker-01 1分=2.10 5分=1.95 15分=1.60
--- 集約統計 ---
Array
(
[server_count] => 4
[reporting_count] => 4
[avg_load1] => 1.213
[busiest_server] => worker-01
[max_load1] => 2.1
[min_load1] => 0.45
)
解説: 実際の運用では、各サーバーが自身のヘルスチェックAPI(サンプル2のようなもの)を提供し、ダッシュボードサーバーがweb_fetch等で巡回収集する構成になります。sys_getloadavg() 自体は単一サーバーの値しか返さないため、クラスタ監視には集約レイヤーが必要です。
よくある落とし穴
① Windowsでは常にfalse
// ❌ 戻り値を無条件に配列として扱う
$load = sys_getloadavg();
echo $load[0]; // Windowsでは警告+null
// ✅ false チェックを必ず行う
$load = sys_getloadavg();
if ($load === false) {
// Windowsまたは取得不可な環境
} else {
echo $load[0];
}
② コンテナ内では実際のホストの負荷と異なる場合がある
// Dockerコンテナ内の sys_getloadavg() は、
// コンテナ自身のリソース制限ではなく「ホストマシン全体」の負荷を返すことがある
// → コンテナ内のCPU使用率は別途 /sys/fs/cgroup などから取得する必要がある場合がある
③ 値の即時性に過度な期待をしない
// ロードアベレージは「平均値」なので急激な変化には反応が遅れる
// 1分間隔のリクエストスパイクなどはload_avgだけでは検出しづらい
// → 即時性が必要な監視には別の指標(CPU使用率の瞬間値など)を併用する
④ CPUコア数の取得方法に頼り過ぎない
// shell_exec('nproc') はセーフモードや disable_functions で無効化されている場合がある
$cpuCount = shell_exec('nproc 2>/dev/null');
if ($cpuCount === null) {
// フォールバック:/proc/cpuinfo を読む、または固定値を使う
}
まとめ
| ポイント | 内容 |
|---|---|
| 主な用途 | サーバーの負荷状況の取得・ヘルスチェック・スロットリング判断 |
| 戻り値 | [1分, 5分, 15分]の3要素配列、または取得不可時はfalse |
| 対応OS | Unix系のみ(Windowsは常にfalse) |
| 解釈の指針 | CPUコア数で割った比率(負荷率)で判断するのが実用的 |
| 活用パターン | ヘルスチェックAPI・処理のスロットリング・定期ロギング・クラスタ監視 |
| 注意点 | コンテナ内ではホスト全体の値を返す場合がある/即時性には限界がある |
sys_getloadavg() は単純な関数ですが、サーバーの状態を素早く把握するための重要な手がかりを提供します。Windows非対応という制約を踏まえつつ、ヘルスチェックや負荷ベースの動的制御に組み込むことで、より安定したアプリケーション運用が実現できます。
PHP 8.x / 執筆時点の最新安定版にて動作確認済み
