“모든 사용자 기기가 고성능 데스크톱은 아니다. 하드웨어가 뜨거워지면 웹앱 스스로 연산량을 낮춰야 한다.” 브라우저 내 로컬 AI 모델 실행, 실시간 화상 처리, 고화질 WebGPU 캔버스가 일상화된 시대에 기기 과열과 배터리 고갈을 방지하는 차세대 웹 표준 API를 다룹니다.
과거의 웹 애플리케이션은 서버에서 렌더링된 HTML을 받아 보여주거나 가벼운 DOM 조작을 처리하는 것이 대부분이었습니다. 그러나 오늘날의 웹은 완전히 다릅니다.
이러한 워크로드는 CPU와 GPU를 극한까지 활용합니다. 하지만 모바일 디바이스, 씬(Thin) 노트북, 팬리스(Fanless) 태블릿은 지속적인 고부하 작업 시 **서멀 스로틀링(Thermal Throttling)**이 발생합니다. CPU 클록이 강제로 50% 이하로 떨어지며, 웹 애플리케이션은 즉각적으로 초당 프레임 수(FPS)가 급락하고 탭이 얼어붙는 현상(Jank)을 겪습니다.
Compute Pressure API는 웹 애플리케이션이 운영체제와 하드웨어의 연산 압력(부하 및 발열)을 표준화된 방식으로 관측하여, **시스템이 한계에 도달하기 전에 선제적으로 작업량을 줄이고, 환경이 안정화되면 다시 품질을 높이는 ‘적응형 성능(Graceful Degradation & Dynamic Quality Adjustment)‘**을 구현할 수 있도록 W3C에서 제정한 표준 API입니다.
많은 개발자가 프레임 드롭이나 지연을 감지하기 위해 기존의 웹 API를 조합해 왔습니다.
| 측정 방식 | 측정 대상 | 한계점 |
|---|---|---|
requestAnimationFrame 델타 측정 |
메인 스레드 렌더링 주기 | 이미 프레임 드롭이 발생한 사후(Post-facto) 감지만 가능 |
PerformanceObserver (Long Tasks) |
50ms 초과 메인 스레드 블로킹 | 하드웨어 발열이나 백그라운드 스레드(Worker) 부하 미반영 |
navigator.hardwareConcurrency |
논리 코어 수 | 고정된 값으로, 현재 CPU 점유율이나 발열 상태 확인 불가 |
Battery Status API (사용 제한) |
배터리 잔량 및 충전 상태 | 보안/개인정보 이슈로 사장되었으며, CPU 부하와 직결되지 않음 |
기존 방식은 메인 스레드가 이미 멈춘 뒤에야 문제를 알아챕니다. 또한 Web Worker나 WebGPU 파이프라인에서 막대한 연산이 돌아가고 있을 때 메인 스레드는 한가해 보일 수 있지만, 하드웨어 칩셋 전체는 과열되어 쓰로틀링 직전일 수 있습니다.
Compute Pressure API는 시스템 전체(System-wide)의 하드웨어 수준 부하를 브라우저 엔진이 OS로부터 직접 조회하여 알려주므로, 선제적 방어가 가능합니다.
Compute Pressure API의 핵심은 PressureObserver 인터페이스입니다. 관측 대상 소스(source)는 현재 표준에서 "cpu"를 지원합니다.
브라우저는 시스템 상태를 분석하여 다음 4가지 표준 압력 상태(PressureState) 중 하나를 전달합니다.
[ nominal ] ──(부하 증가)──> [ fair ] ──(서멀 경고)──> [ serious ] ──(한계치)──> [ critical ]
│ │ │ │
정상 안정적 부하 심각한 발열 치명적 과부하
최상의 품질 유지 일부 비필수 작업 유예 해상도/연산 주기 대폭 감소 최소 기능만 유지nominal (정상): 시스템 부하가 매우 낮고 발열이 없는 최적의 상태입니다. 모든 고사양 기능(고해상도 비디오, 60fps 렌더링, 정밀 AI 추론)을 제한 없이 구동합니다.fair (안정적 부하): 부하가 다소 발생했으나 시스템이 안정적으로 처리 가능한 범위입니다. 사용자 경험을 해치지 않는 선에서 불필요한 백그라운드 프리페칭이나 부가 이펙트를 중단할 수 있습니다.serious (심각한 부하): 하드웨어 발열이 심해지고 쿨링팬이 최대 속도로 돌며, 곧 서멀 스로틀링이 일어날 가능성이 큽니다. 비디오 해상도를 낮추고 AI 추론 프레임을 반감시켜야 합니다.critical (치명적 과부하): 시스템이 한계에 도달하여 극심한 스로틀링이 발생 중입니다. 비디오 필터를 완전히 끄거나 오디오 전용 모드로 전환하고, 즉각적인 연산 감축 조치를 취해야 합니다.브라우저에 하드웨어 센서나 CPU 사용률을 그대로 노출하면 심각한 보안 문제가 발생합니다. 악의적인 스크립트가 정밀한 CPU 부하 곡선을 추적하여 사용자의 키 입력 패턴을 유추(Side-channel timing attack)하거나, CPU 쿨링 특성을 분석해 고유 기기를 식별(Fingerprinting)할 수 있기 때문입니다.
Compute Pressure API는 이를 방지하기 위해 엄격한 보안 완화 조치를 표준 레벨에 내장했습니다.
nominal, fair, serious, critical)만 전달합니다.compute-pressure 권한 정책을 통해 iframe 삽입 시 명시적 허용(allow="compute-pressure")이 필요합니다.브라우저 지원 여부를 확인하고 관측을 시작하는 기본 코드 패턴입니다.
// 1. 브라우저 지원 여부 확인
if ('PressureObserver' in window) {
try {
// 2. 콜백 함수 정의
const handlePressureChange = (records, observer) => {
for (const record of records) {
console.log(`[Pressure Source]: ${record.source}`);
console.log(`[Current State]: ${record.state}`);
console.log(`[Timestamp]: ${record.time}`);
switch (record.state) {
case 'nominal':
applyQualityProfile('ultra');
break;
case 'fair':
applyQualityProfile('high');
break;
case 'serious':
applyQualityProfile('medium');
break;
case 'critical':
applyQualityProfile('low');
break;
}
}
};
// 3. 옵저버 생성
const observer = new PressureObserver(handlePressureChange);
// 4. CPU 소스 관측 시작 (샘플링 간격 설정 가능: 단위 ms)
await observer.observe('cpu', {
sampleInterval: 1000 // 최소 1000ms 간격으로 수집 (브라우저 정책에 따라 더 길어질 수 있음)
});
console.log('Compute Pressure 관측이 시작되었습니다.');
} catch (error) {
console.error('PressureObserver 시작 실패:', error);
}
} else {
console.warn('이 브라우저는 Compute Pressure API를 지원하지 않습니다.');
}관측을 중단하려면 unobserve() 또는 disconnect()를 호출합니다.
// 특정 소스 해제
observer.unobserve('cpu');
// 전체 옵저버 해제
observer.disconnect();WebRTC 화상회의나 캔버스 기반 비디오 배경 블러(Canvas Background Blur) 처리 시, 발열이 발생하면 필터 렌더링 해상도를 단계별로 낮추는 실무 패턴입니다.
class AdaptiveVideoProcessor {
constructor(videoElement, canvasElement) {
this.video = videoElement;
this.canvas = canvasElement;
this.ctx = canvasElement.getContext('2d');
// 해상도 스케일 계수 (1.0 = 원본 해상도)
this.scaleFactor = 1.0;
this.blurRadius = 15;
this.isRunning = false;
this.observer = null;
}
async init() {
if ('PressureObserver' in window) {
this.observer = new PressureObserver((records) => {
const latest = records[records.length - 1];
this.adjustQuality(latest.state);
});
await this.observer.observe('cpu', { sampleInterval: 1500 });
}
this.startRenderLoop();
}
adjustQuality(state) {
console.info(`[VideoEngine] 시스템 압력 변경 감지: ${state}`);
switch (state) {
case 'nominal':
this.scaleFactor = 1.0; // 1080p 전체 해상도 처리
this.blurRadius = 15;
break;
case 'fair':
this.scaleFactor = 0.75; // 75% 스케일 다운
this.blurRadius = 10;
break;
case 'serious':
this.scaleFactor = 0.5; // 50% 스케일 다운 (연산량 1/4)
this.blurRadius = 6;
break;
case 'critical':
this.scaleFactor = 0.25; // 필터 연산 최소화 혹은 바이패스
this.blurRadius = 0; // 블러 효과 끔
break;
}
}
startRenderLoop() {
this.isRunning = true;
const render = () => {
if (!this.isRunning) return;
const targetWidth = Math.floor(this.video.videoWidth * this.scaleFactor);
const targetHeight = Math.floor(this.video.videoHeight * this.scaleFactor);
if (this.canvas.width !== targetWidth && targetWidth > 0) {
this.canvas.width = targetWidth;
this.canvas.height = targetHeight;
}
if (this.blurRadius > 0) {
this.ctx.filter = `blur(${this.blurRadius}px)`;
} else {
this.ctx.filter = 'none';
}
this.ctx.drawImage(this.video, 0, 0, targetWidth, targetHeight);
requestAnimationFrame(render);
};
requestAnimationFrame(render);
}
destroy() {
this.isRunning = false;
if (this.observer) {
this.observer.disconnect();
}
}
}사용자는 쿨링팬이 굉음을 내며 브라우저 탭이 멈추는 대신, 비디오 필터 품질이 살짝 낮아지면서도 통화가 매끄럽게 지속되는 경험을 누리게 됩니다.
브라우저에서 WebGPU나 WASM 기반 소형 언어 모델(SLM)을 실행할 때, 연속적인 토큰 생성은 CPU/GPU를 100% 점유하여 다른 UI 반응성을 마비시킵니다. 시스템 부하에 따라 추론 딜레이(Yielding)를 적응형으로 주입하는 아키텍처입니다.
class AdaptiveModelScheduler {
constructor(inferenceEngine) {
this.engine = inferenceEngine;
this.yieldDelayMs = 0;
this.batchChunkSize = 8;
this.observer = null;
}
async startMonitoring() {
if (!('PressureObserver' in window)) return;
this.observer = new PressureObserver((records) => {
const state = records[records.length - 1].state;
this.reschedule(state);
});
await this.observer.observe('cpu', { sampleInterval: 2000 });
}
reschedule(state) {
switch (state) {
case 'nominal':
this.yieldDelayMs = 0; // 풀 스피드 추론
this.batchChunkSize = 8;
break;
case 'fair':
this.yieldDelayMs = 5; // 이벤트 루프에 짧은 양보
this.batchChunkSize = 4;
break;
case 'serious':
this.yieldDelayMs = 25; // UI 프레임 확보를 위한 명시적 슬립
this.batchChunkSize = 2;
break;
case 'critical':
this.yieldDelayMs = 80; // 심각한 쿨다운 딜레이
this.batchChunkSize = 1;
break;
}
}
async *generateStream(promptTokens) {
let processed = 0;
while (processed < promptTokens.length) {
const chunk = promptTokens.slice(processed, processed + this.batchChunkSize);
const output = await this.engine.forward(chunk);
processed += chunk.length;
yield output;
// 시스템 압력 상태에 따라 메인 스레드에 제어권 반환 및 열 방출 유도
if (this.yieldDelayMs > 0) {
await new Promise((r) => setTimeout(r, this.yieldDelayMs));
}
}
}
}Compute Pressure API를 적용할 때 가장 자주 범하는 실수는 상태가 바뀔 때마다 즉각 품질을 올렸다 내리는 진동(Oscillation/Flapping) 현상입니다.
[부하 높음: serious] ──> 품질 다운그레이드 ──> [즉시 부하 하강: nominal] ──> 즉시 품질 업그레이드 ──> [다시 발열: serious] (무한 반복!)품질을 낮출 때는 즉시 반응하되, 품질을 다시 올릴 때는 시스템이 충분히 식었는지 확인하는 히스테리시스(Hysteresis) 쿨다운 타이머가 반드시 필요합니다.
class PressureHysteresisManager {
constructor(onQualityChange) {
this.onQualityChange = onQualityChange;
this.currentState = 'nominal';
this.upgradeTimer = null;
this.COOLDOWN_PERIOD_MS = 10000; // 10초간 안정 상태가 유지되어야 업그레이드
}
processRecord(record) {
const newState = record.state;
const severityOrder = { nominal: 0, fair: 1, serious: 2, critical: 3 };
const currentSeverity = severityOrder[this.currentState];
const newSeverity = severityOrder[newState];
if (newSeverity > currentSeverity) {
// 1. 부하가 심해지면 지체 없이 즉각 다운그레이드
if (this.upgradeTimer) {
clearTimeout(this.upgradeTimer);
this.upgradeTimer = null;
}
this.currentState = newState;
this.onQualityChange(this.currentState);
} else if (newSeverity < currentSeverity) {
// 2. 부하가 낮아졌다면 즉시 올리지 않고 쿨다운 대기
if (!this.upgradeTimer) {
this.upgradeTimer = setTimeout(() => {
this.currentState = newState;
this.onQualityChange(this.currentState);
this.upgradeTimer = null;
console.log(`[Hysteresis] 하드웨어가 10초간 안정되어 품질을 ${newState}로 복구합니다.`);
}, this.COOLDOWN_PERIOD_MS);
}
}
}
}이 패턴을 적용해야 화면 해상도가 1~2초마다 깜빡거리며 요동치는 치명적인 UX 퇴행을 막을 수 있습니다.
Compute Pressure API는 기존의 성능 및 하드웨어 API와 상호 보완적으로 작동합니다.
| 비교 항목 | Compute Pressure API | PerformanceObserver (Long Tasks) | Navigator.hardwareConcurrency | Web Workers |
|---|---|---|---|---|
| 주요 역할 | 하드웨어 서멀/CPU 부하 관측 | 메인 스레드 지연 블록 감지 | 기기의 논리 코어 수 확인 | 작업을 멀티스레드로 분산 |
| 시점 | 선제적/진행형 (부하 상태 감지) | 사후적 (이미 밀린 작업 측정) | 정적 (초기 실행 시 결정) | 상시 (실행 메커니즘) |
| 측정 범위 | 시스템 전반 (OS/하드웨어) | 브라우저 메인 스레드 | CPU 스펙 | 스레드 분리 |
| 피드백 형태 | 4단계 정성적 상태 | 작업 시간(ms) 메트릭 | 숫자 (예: 8, 16) | N/A |
| 최적 조합 | Worker 스케줄링 조율에 활용 | 메인 스레드 렌더링 병목 분석 | 초기 워커 풀 크기 결정 | 백그라운드 연산 실행 |
sampleInterval 옵션에 100ms를 넘겨도 브라우저는 프라이버시 보호와 배터리 절약을 위해 자체 최소 간격(통상 1,000ms 수준) 미만으로 콜백을 주지 않습니다. 초고빈도 센서처럼 기대해서는 안 됩니다.allow="compute-pressure"를 명시해야 합니다.'PressureObserver' in window 기능 탐지를 감싸고 폴백(Fallback)을 준비해야 합니다.Network Information API (navigator.connection)를 사용해야 합니다.nominal, fair, serious, critical을 통해 시스템이 한계에 도달하기 전 선제적 조치를 취할 수 있습니다.✨ 한 줄 요약 Compute Pressure API는 브라우저가 기기의 발열과 CPU 부하를 실시간으로 감지하여 스스로 연산량을 조절하게 해주는 차세대 적응형 웹의 핵심 열쇠다.
