์ด๋ ค์๋๋ฐ ์ฃฝ์ด์๋ค โ Manifest V3 Service Worker์ ์ข๋น WebSocket ๋ค๋ฃจ๊ธฐ
์ค์๊ฐ ์์ธ ํฌ๋กฌ ํ์ฅ์ ๋ง๋ค๋ฉด์ ๊ฐ์ฅ ์ค๋ ์จ๋ฆํ ๊ฑด "์ค์๊ฐ ์ฐ๊ฒฐ์ด ์๊พธ ์กฐ์ฉํ ์ฃฝ๋" ๋ฌธ์ ์๋ค. Manifest V3(MV3)๋ก ๋์ด์ค๋ฉด์ ๋ธ๋ผ์ฐ์ ํ์ฅ์ ๋ฐฑ๊ทธ๋ผ์ด๋๊ฐ ๊ทผ๋ณธ์ ์ผ๋ก ๋ฐ๋์๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฌธ์ 1: Service Worker๋ ์ธ์ ๋ ๊บผ์ง๋ค
MV3์์ ๋ฐฑ๊ทธ๋ผ์ด๋๋ Service Worker๋ค. ์์ MV2์ background page์ ๋ฌ๋ฆฌ
์ ํด ์ํ๊ฐ ๋๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ๊ทธ๋ฅ ์ข
๋ฃํด๋ฒ๋ฆฐ๋ค. ๊ทธ๋ฌ๋ฉด ๊ทธ ์์์ ๋๋
WebSocket, setInterval, setTimeout์ด ์ ๋ถ ์ฌ๋ผ์ง๋ค. ์ค์๊ฐ ์์ธ๋ฅผ
๋ฐ์์ผ ํ๋ ํ์ฅ ์
์ฅ์์ ์น๋ช
์ ์ด๋ค.
(์ ํํ ํ๋ฉด, Chrome 116๋ถํฐ๋ ํ์ฑ WebSocket์ผ๋ก ๋ฉ์์ง๊ฐ ์ค๊ฐ๋ฉด SW์ idle ํ์ด๋จธ๊ฐ ๋ฆฌ์ ๋์ด ์ฐ๊ฒฐ์ด SW๋ฅผ ๊ณ์ ์ด๋ ค๋ ์ ์๋ค. ํ์ง๋ง ์ด๊ฑด "๋ฉ์์ง๊ฐ ์ค์ ๋ก ํ๋ฅผ ๋" ์๊ธฐ๋ค. ์์ผ์ด ์กฐ์ฉํด์ง๊ฑฐ๋ ๋ค์์ ๋ค๋ฃฐ ์ข๋น ์ํ๊ฐ ๋๋ฉด SW๋ ์ฌ์ ํ ์ข ๋ฃ๋๋ค. ๊ฒฐ๊ตญ "์ธ์ ๋ ๊บผ์ง๋ค"๋ ์ ์ ๋ ์ ํจํ๋ค.)
ํด๊ฒฐ์ ์ค๋ง๋ฆฌ๋ chrome.alarms์๋ค. ์ด๊ฑด SW๊ฐ ๊บผ์ ธ ์์ด๋ ๋ธ๋ผ์ฐ์ ๊ฐ
๊นจ์์ค๋ค. ๊ทธ๋์ 1๋ถ๋ง๋ค SW๋ฅผ ๊นจ์์ ๋ชจ๋ ๊ฑธ ๋ณต๊ตฌํ๊ฒ ํ๋ค.
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name !== REFRESH_ALARM) return;
// SW๊ฐ ์ฃฝ์๋ค๊ฐ ์๋์ผ๋ก ๋ค์ ๋จ๋ฉด ์ ์ญ ์ํ(๊ตฌ๋
ํค)๊ฐ ์ด๊ธฐํ๋ผ ์๋ค.
// ๊ทธ๋์ syncRealtimeTargets๊ฐ "๊ตฌ๋
์ด ์๋ค" ํ๊ณ WebSocket์ ์๋ก ๋ง๋ ๋ค.
refreshQuotes();
syncRealtimeTargets(); // โ SW ์ฌ๊ธฐ๋ ์ WebSocket ์ฌ๊ตฌ์ถ
scheduleForegroundRefresh(); // โ ํด๋ง ํ์ด๋จธ ๋ณต๊ตฌ
});
์ฆ, ์๋ ์ฝ๋ฐฑ์ "์์ธ ํ ๋ฒ ์๋ก๊ณ ์นจ"์ด ์๋๋ผ ์ฃฝ์์์ง ๋ชจ๋ฅผ ์์คํ ์ ์ฒด๋ฅผ ๋์ด๋ฆฌ๋ ๋ถํ ์ง์ ์ด๋ค.
ํ ๊ฐ์ง ์ง๊ณ ๊ฐ ๊ฒ ์๋๋ฐ, syncRealtimeTargets๋ ๊ตฌ๋
ํ ์ฌ๋ณผ ๋ชฉ๋ก์ด
์ด์ ๊ณผ ๊ฐ์ผ๋ฉด early-return ํ๋ค. ๊ทธ๋์ SW๊ฐ ์ด์์๋ ์ฑ๋ก ์์ผ๋ง ์ฃฝ์
๊ฒฝ์ฐ์ ์๋์ด ๋ชป ์ด๋ฆฐ๋ค. ์๋์ ์ญํ ์ ์ด๋๊น์ง๋ SW ์์ฒด๊ฐ ์ฃฝ์๋ค
๋์ด์๋ฌ์ ๋์ ์ฌ๊ตฌ์ถ์ด๋ค. ์ด์์๋ SW ์์ ์ฃฝ์ ์์ผ์ ๋ค์์ ๋ค๋ฃฐ
15์ด ํฌ์ค์ฒดํฌ ํ์ด๋จธ๊ฐ ์ก๋๋ค. ์ด ํ์ด๋จธ๋ ๋งค๋ฒ "์ต๊ทผ์ ๋ฐ์ดํฐ๋ฅผ
๋ฐ์๋๊ฐ"๋ฅผ ํ์ธํ๊ณ , ์๋๋ฉด ์ฌ์ฐ๊ฒฐ์ ์๋ํ๋ค. ์ฌ๊ธฐ์ ์์ผ์ด ๋ซํ ๋
๊ตฌ๋
ํค๋ฅผ ์ด๊ธฐํํด์, ๋ค์ syncRealtimeTargets๊ฐ early-return์ผ๋ก ์ฃฝ์
์ฐ๊ฒฐ์ ๋ฐฉ์นํ์ง ์๊ฒ ํ๋ค.
๋ฌธ์ 2: ์ฐ๊ฒฐ์ด "์ด๋ ค์๋๋ฐ ์ฃฝ์ด์๋ค" (์ข๋น)
๋ ๊น๋ค๋ก์ด ๊ฑด ์ด๊ฑฐ์๋ค. socket.readyState๊ฐ OPEN์ธ๋ฐ๋ ๋ฐ์ดํฐ๊ฐ ์ ์จ๋ค.
๋คํธ์ํฌ๊ฐ ๋๊ฒผ๋๋ฐ onclose๊ฐ ์ ๋ถ๋ฆฌ๋ half-open ์ํ. TCP ํน์ฑ์
ํํ ์๊ธด๋ค. readyState๋ง ๋ฏฟ์ผ๋ฉด "์ฐ๊ฒฐ๋ผ ์๋ค" ํ๊ณ ๋ฐฉ์นํ๊ฒ ๋๋ค.
๊ทธ๋์ "๋ง์ง๋ง ์์ ์๊ฐ" ์ ๊ธฐ์ค์ผ๋ก ์ข๋น๋ฅผ ํ์ ํ๋ค.
const REALTIME_STALE_MS = 40 * 1000; // 40์ด ๋์ ์๋ฌด๊ฒ๋ ์ ์ค๋ฉด ์ข๋น
async function ensureRealtimeConnection() {
if (realtimeSocket?.readyState === WebSocket.OPEN) {
const stale =
lastRealtimeEventAt && Date.now() - lastRealtimeEventAt > REALTIME_STALE_MS;
if (!stale) {
sendRealtimeSubscription("replace");
return;
}
closeRealtimeSocket(); // OPEN์ด์ด๋ ์ฃฝ์๋ค ํ๋จ โ ๊ฐ์ ์ฌ์ฐ๊ฒฐ
}
// ... ์ ์์ผ ์์ฑ
}
๋ฉ์์ง๋ฅผ ๋ฐ์ ๋๋ง๋ค lastRealtimeEventAt = Date.now()๋ฅผ ์ฐ๊ณ , 40์ด ๋๊ฒ
์์์ด ์์ผ๋ฉด readyState๊ฐ ๋ญ๋ผ ํ๋ ๋๊ณ ๋ค์ ์ฐ๊ฒฐํ๋ค.
๋ฌธ์ 3: ์ฌ์ฐ๊ฒฐ ํญํ (thundering herd)
์๋ฒ๊ฐ ์ ๊น ์ฌ์์๋๋ฉด, ๋ชจ๋ ํด๋ผ์ด์ธํธ๊ฐ ๋์์ ์ฌ์ฐ๊ฒฐ์ ์๋ํ๋ค. ๊ฐ๋ฉ์ด๋ ํ๋ ์๋ฒ์ ์๊ฐ ํญํ์ด ๋จ์ด์ง๋ค. ๊ทธ๋์ ์ง์ ๋ฐฑ์คํ + ์งํฐ๋ฅผ ๋ฃ์๋ค.
const RECONNECT_BASE_MS = 1000; // 1์ด์์ ์์
const RECONNECT_MAX_MS = 60 * 1000; // ์ต๋ 60์ด
function scheduleRealtimeReconnect() {
const backoff = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** attempt);
const jitter = Math.floor(Math.random() * (backoff * 0.3)); // โ ๋ฌด์์ ์งํฐ
const delay = backoff + jitter;
attempt++;
setTimeout(() => ensureRealtimeConnection(), delay);
}
- ์ง์ ๋ฐฑ์คํ: 1์ด โ 2์ด โ 4์ด โฆ ์คํจํ ์๋ก ๊ฐ๊ฒฉ์ ๋ฒ๋ฆฐ๋ค.
- ์งํฐ: ์ฌ๊ธฐ์ ๋ฌด์์ ์๊ฐ์ ๋ํด, ํด๋ผ์ด์ธํธ๋ค์ ์ฌ์ฐ๊ฒฐ ์์ ์ ํฉ๋ฟ๋ฆฐ๋ค. ๋ชจ๋๊ฐ ์ ํํ ๊ฐ์ ์๊ฐ์ ๋ชฐ๋ฆฌ๋ ๊ฑธ ๋ง๋๋ค.
์ข ํฉ: ์ธ ๊ฒน์ผ๋ก ์ด์๋จ๊ธฐ
๊ฒฐ๊ตญ ์ค์๊ฐ์ ์ด๋ ๊ฒ ์ธ ๊ฒน์ผ๋ก ์์๋ค.
- WebSocket โ ์ ์์ผ ๋ ์ด๊ฒ ์ค์๊ฐ์ ๋ด๋น
- 15์ด ํฌ์ค์ฒดํฌ ํด๋ง โ WS๊ฐ ๋๊ธฐ๊ฑฐ๋ ์ข๋น๋ฉด ์ฌ์ฐ๊ฒฐ์ ์๋ํ๊ณ , ๊ทธ๋์ REST๋ก ๋ฉ์ด๋ค
- 1๋ถ ์๋ โ SW ์์ฒด๊ฐ ์ฃฝ์๋ค ๋์ด์๋ฌ์ ๋ ์ ๋ถ ์ฌ๊ตฌ์ถํ๋ค
๊ฐ ๊ณ์ธต์ด ์๋ก์ ์คํจ๋ฅผ ๋ฎ์ด์ค๋ค. "์ฐ๊ฒฐ์ ์ธ์ ๊ฐ ๋ฐ๋์ ๋๊ธด๋ค"๋ฅผ ์ ์ ๋ก ์ค๊ณํ๋, ์คํ๋ ค ์์ ์ ์ผ๋ก ๊ตด๋ฌ๊ฐ๋ค.
๋ฐฐ์ด ๊ฒ
MV3 ํ์ฅ ๊ฐ๋ฐ์ "๋ฐฑ๊ทธ๋ผ์ด๋๊ฐ ํญ์ ์ด์์๋ค"๋ ๊ฐ์ ์ ๋ฒ๋ฆฌ๋ ๋ฐ์ ์์ํ๋ค.
๊ทธ๋ฆฌ๊ณ readyState === OPEN์ "์ฐ๊ฒฐ์ด ์ด์์๋ค"๋ ๋ณด์ฅ์ด ์๋๋ค. ์ง์ง
์ด์์๋์ง๋ ์ค์ ๋ก ์ต๊ทผ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์๋๊ฐ๋ก ํ๋จํด์ผ ํ๋ค. ๋คํธ์ํฌ๋ฅผ
๋ค๋ฃจ๋ ์ฝ๋๋ ์ ๋ถ "์ฃฝ์์ ๋ ์ด๋ป๊ฒ ๋์ด๋ฆด๊น"๋ฅผ ๋จผ์ ์ค๊ณํด์ผ ํ๋ค๋ ๊ฑธ
๋ชธ์ผ๋ก ๋ฐฐ์ ๋ค.
- ๊ด๋ จ ํ๋ก์ ํธ: Stock Price Widget