Skip to content

Commit d95d1e5

Browse files
docs(blog): 검색 유입용 기술 팁 포스트 4차 배치 (5개, 3개 언어)
- Node.js --expose-gc 메모리 누수 디버깅 - Electron contextIsolation + preload 보안 패턴 - Redis key 네이밍 컨벤션 (계층적 구조) - ESM에서 __dirname 사용하기 (import.meta.url) - Vite 모노레포 상위 디렉토리 접근 허용
1 parent 8ad5532 commit d95d1e5

15 files changed

Lines changed: 634 additions & 0 deletions
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
layout: post
3+
title: "Node.js --expose-gc for Memory Leak Debugging"
4+
date: 2025-11-20 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Node.js, memory, garbage collection, debugging, performance]
7+
author: "Kevin Park"
8+
lang: en
9+
excerpt: "Use Node.js --expose-gc flag to trigger manual GC and identify real memory leaks."
10+
---
11+
12+
## Problem
13+
14+
Long-running Node.js apps (MQTT clients, batch processors) show increasing memory usage. Hard to tell if GC is working properly or if there's a real leak.
15+
16+
## Solution
17+
18+
```bash
19+
# --expose-gc enables global.gc()
20+
node --expose-gc app.js
21+
22+
# In Docker
23+
CMD ["node", "--expose-gc", "--max-old-space-size=512", "dist/index.js"]
24+
```
25+
26+
```javascript
27+
function logMemory(label) {
28+
if (global.gc) global.gc(); // force GC
29+
const usage = process.memoryUsage();
30+
console.log(`[${label}] Heap: ${Math.round(usage.heapUsed / 1024 / 1024)}MB / ${Math.round(usage.heapTotal / 1024 / 1024)}MB`);
31+
}
32+
33+
logMemory('start');
34+
await processLargeData();
35+
logMemory('after'); // if heap doesn't shrink after GC → leak
36+
```
37+
38+
## Key Points
39+
40+
- If `heapUsed` keeps growing after `global.gc()`, it's a real memory leak — objects are referenced somewhere GC can't reach.
41+
- `--max-old-space-size=512` limits heap size so leaks trigger OOM errors faster, making them easier to catch.
42+
- Don't use `--expose-gc` in production. Manual GC impacts performance. Use it only for debugging.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
layout: post
3+
title: "Node.js --expose-gcでメモリリークをデバッグする"
4+
date: 2025-11-20 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Node.js, memory, garbage collection, debugging, performance]
7+
author: "Kevin Park"
8+
lang: ja
9+
excerpt: "Node.jsで--expose-gcフラグを使って手動GCをトリガーし、メモリリークを特定する方法をご紹介します。"
10+
---
11+
12+
## 問題
13+
14+
長時間実行されるNode.jsアプリ(MQTTクライアント、バッチプロセッサなど)でメモリが増加し続けます。GCが正常に動作しているのか、本当にリークなのか区別がつきません。
15+
16+
## 解決方法
17+
18+
```bash
19+
# --expose-gcで実行するとglobal.gc()を呼び出せる
20+
node --expose-gc app.js
21+
22+
# Dockerの場合
23+
CMD ["node", "--expose-gc", "--max-old-space-size=512", "dist/index.js"]
24+
```
25+
26+
```javascript
27+
function logMemory(label) {
28+
if (global.gc) global.gc(); // 手動GC実行
29+
const usage = process.memoryUsage();
30+
console.log(`[${label}] Heap: ${Math.round(usage.heapUsed / 1024 / 1024)}MB / ${Math.round(usage.heapTotal / 1024 / 1024)}MB`);
31+
}
32+
33+
logMemory('開始');
34+
await processLargeData();
35+
logMemory('処理後'); // GC後もメモリが減らなければ → リーク
36+
```
37+
38+
## ポイント
39+
40+
- `global.gc()`を呼び出した後も`heapUsed`が増加し続ければ、本当のメモリリークです。GCが回収できないオブジェクトがどこかで参照されています。
41+
- `--max-old-space-size=512`でヒープサイズを制限すると、リークがある場合にOOMで早くクラッシュするため、問題を早期発見できます。
42+
- プロダクション環境では`--expose-gc`を使わないのが良いです。手動GCはパフォーマンスに影響します。デバッグ時のみ使用してください。
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
layout: post
3+
title: "Node.js --expose-gc로 메모리 누수 디버깅하기"
4+
date: 2025-11-20 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Node.js, memory, garbage collection, debugging, performance]
7+
author: "Kevin Park"
8+
lang: ko
9+
excerpt: "Node.js에서 --expose-gc 플래그로 수동 GC를 트리거하고 메모리 누수를 찾는 방법."
10+
---
11+
12+
## 문제
13+
14+
장시간 실행되는 Node.js 앱(MQTT 클라이언트, 배치 프로세서 등)에서 메모리가 계속 올라간다. GC가 제대로 돌고 있는 건지, 진짜 누수인 건지 구분이 안 된다.
15+
16+
## 해결
17+
18+
```bash
19+
# --expose-gc로 실행하면 global.gc()를 호출할 수 있다
20+
node --expose-gc app.js
21+
22+
# Docker에서
23+
CMD ["node", "--expose-gc", "--max-old-space-size=512", "dist/index.js"]
24+
```
25+
26+
```javascript
27+
// 메모리 상태 확인 함수
28+
function logMemory(label) {
29+
if (global.gc) global.gc(); // 수동 GC 실행
30+
const usage = process.memoryUsage();
31+
console.log(`[${label}] Heap: ${Math.round(usage.heapUsed / 1024 / 1024)}MB / ${Math.round(usage.heapTotal / 1024 / 1024)}MB`);
32+
}
33+
34+
// 사용 예시
35+
logMemory('시작');
36+
await processLargeData();
37+
logMemory('처리 후'); // GC 후에도 메모리가 안 줄면 → 누수
38+
```
39+
40+
## 핵심 포인트
41+
42+
- `global.gc()`를 호출한 후에도 `heapUsed`가 계속 증가하면 진짜 메모리 누수다. GC가 회수할 수 없는 객체가 어딘가에 참조되고 있는 거다.
43+
- `--max-old-space-size=512`로 힙 크기를 제한하면, 누수가 있을 때 빨리 OOM으로 터져서 문제를 빨리 발견할 수 있다.
44+
- 프로덕션에서는 `--expose-gc`를 쓰지 않는 게 좋다. 수동 GC는 성능에 영향을 준다. 디버깅할 때만 쓰자.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
layout: post
3+
title: "Electron contextIsolation + Preload Security Pattern"
4+
date: 2025-12-05 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Electron, security, contextIsolation, preload, IPC]
7+
author: "Kevin Park"
8+
lang: en
9+
excerpt: "Secure your Electron app by using contextIsolation and preload scripts to restrict renderer access to Node.js."
10+
---
11+
12+
## Problem
13+
14+
With `nodeIntegration: true`, the renderer (webpage) can directly access the filesystem via `require('fs')`. If loading external URLs, this is a critical security risk.
15+
16+
## Solution
17+
18+
```javascript
19+
// main.js
20+
const mainWindow = new BrowserWindow({
21+
webPreferences: {
22+
preload: path.join(__dirname, 'preload.js'),
23+
contextIsolation: true, // separate renderer and Node.js contexts
24+
nodeIntegration: false, // block require in renderer
25+
}
26+
});
27+
```
28+
29+
```javascript
30+
// preload.js - bridge between main and renderer
31+
const { contextBridge, ipcRenderer } = require('electron');
32+
33+
contextBridge.exposeInMainWorld('electronAPI', {
34+
scanNetwork: () => ipcRenderer.invoke('scan-network'),
35+
getVersion: () => ipcRenderer.invoke('get-version'),
36+
onScanResult: (callback) =>
37+
ipcRenderer.on('scan-result', (_, data) => callback(data)),
38+
});
39+
```
40+
41+
```javascript
42+
// renderer.js - use in the webpage
43+
const results = await window.electronAPI.scanNetwork();
44+
```
45+
46+
## Key Points
47+
48+
- With `contextIsolation: true`, the preload script's globals and the renderer's globals are completely separate. Only functions exposed via `contextBridge.exposeInMainWorld` are accessible.
49+
- The renderer can only use what's defined in `window.electronAPI`. Dangerous access like `require('child_process')` is impossible.
50+
- `ipcRenderer.invoke` returns a Promise for bidirectional communication. Handle it in the main process with `ipcMain.handle`.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
layout: post
3+
title: "Electron contextIsolation + preloadセキュリティパターン"
4+
date: 2025-12-05 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Electron, security, contextIsolation, preload, IPC]
7+
author: "Kevin Park"
8+
lang: ja
9+
excerpt: "ElectronでcontextIsolationとpreloadスクリプトを使い、レンダラープロセスのNode.jsアクセスを安全に制限する方法をご紹介します。"
10+
---
11+
12+
## 問題
13+
14+
Electronで`nodeIntegration: true`にすると、レンダラー(Webページ)から`require('fs')`でファイルシステムに直接アクセスできます。外部URLをロードする場合、深刻なセキュリティリスクになります。
15+
16+
## 解決方法
17+
18+
```javascript
19+
// main.js - メインプロセス
20+
const mainWindow = new BrowserWindow({
21+
webPreferences: {
22+
preload: path.join(__dirname, 'preload.js'),
23+
contextIsolation: true, // レンダラーとNode.jsコンテキストを分離
24+
nodeIntegration: false, // レンダラーでrequireをブロック
25+
}
26+
});
27+
```
28+
29+
```javascript
30+
// preload.js - ブリッジ役割
31+
const { contextBridge, ipcRenderer } = require('electron');
32+
33+
contextBridge.exposeInMainWorld('electronAPI', {
34+
scanNetwork: () => ipcRenderer.invoke('scan-network'),
35+
getVersion: () => ipcRenderer.invoke('get-version'),
36+
onScanResult: (callback) =>
37+
ipcRenderer.on('scan-result', (_, data) => callback(data)),
38+
});
39+
```
40+
41+
```javascript
42+
// renderer.js - Webページで使用
43+
const results = await window.electronAPI.scanNetwork();
44+
```
45+
46+
## ポイント
47+
48+
- `contextIsolation: true`にすると、preloadスクリプトのグローバルオブジェクトとレンダラーのグローバルオブジェクトが完全に分離されます。`contextBridge.exposeInMainWorld`で許可された関数のみが公開されます。
49+
- レンダラーでできるのは`window.electronAPI`に定義されたことだけです。`require('child_process')`のような危険なアクセスは不可能です。
50+
- `ipcRenderer.invoke`はPromiseを返す双方向通信です。メインプロセスで`ipcMain.handle`で受け取って処理します。
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
layout: post
3+
title: "Electron contextIsolation + preload 보안 패턴"
4+
date: 2025-12-05 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Electron, security, contextIsolation, preload, IPC]
7+
author: "Kevin Park"
8+
lang: ko
9+
excerpt: "Electron에서 contextIsolation과 preload 스크립트로 렌더러 프로세스의 Node.js 접근을 안전하게 제한하는 방법."
10+
---
11+
12+
## 문제
13+
14+
Electron에서 `nodeIntegration: true`로 하면 렌더러(웹페이지)에서 `require('fs')`로 파일 시스템에 직접 접근할 수 있다. 외부 URL을 로드하는 경우 심각한 보안 위험이 된다.
15+
16+
## 해결
17+
18+
```javascript
19+
// main.js - 메인 프로세스
20+
const mainWindow = new BrowserWindow({
21+
webPreferences: {
22+
preload: path.join(__dirname, 'preload.js'),
23+
contextIsolation: true, // 렌더러와 Node.js 컨텍스트 분리
24+
nodeIntegration: false, // 렌더러에서 require 차단
25+
}
26+
});
27+
```
28+
29+
```javascript
30+
// preload.js - 브릿지 역할
31+
const { contextBridge, ipcRenderer } = require('electron');
32+
33+
contextBridge.exposeInMainWorld('electronAPI', {
34+
scanNetwork: () => ipcRenderer.invoke('scan-network'),
35+
getVersion: () => ipcRenderer.invoke('get-version'),
36+
onScanResult: (callback) =>
37+
ipcRenderer.on('scan-result', (_, data) => callback(data)),
38+
});
39+
```
40+
41+
```javascript
42+
// renderer.js - 웹페이지에서 사용
43+
const results = await window.electronAPI.scanNetwork();
44+
```
45+
46+
## 핵심 포인트
47+
48+
- `contextIsolation: true`면 preload 스크립트의 전역 객체와 렌더러의 전역 객체가 완전히 분리된다. `contextBridge.exposeInMainWorld`로 허용된 함수만 노출한다.
49+
- 렌더러에서 할 수 있는 건 오직 `window.electronAPI`에 정의된 것뿐이다. `require('child_process')` 같은 위험한 접근은 불가능하다.
50+
- `ipcRenderer.invoke`는 Promise를 반환하는 양방향 통신이다. 메인 프로세스에서 `ipcMain.handle`로 받아서 처리한다.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
layout: post
3+
title: "Redis Key Naming Convention - Hierarchical Key Design"
4+
date: 2025-12-20 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Redis, naming convention, key design, TypeScript]
7+
author: "Kevin Park"
8+
lang: en
9+
excerpt: "Organize Redis keys with a hierarchical naming convention and TypeScript helper functions."
10+
---
11+
12+
## Problem
13+
14+
Redis keys like `"user_123_session"` and `"sess-user-123"` created inconsistently make it impossible to track what keys exist or search with `KEYS *`.
15+
16+
## Solution
17+
18+
```typescript
19+
const KEY_PREFIX = 'myapp';
20+
21+
export const keys = {
22+
conversation: (id: string) => `${KEY_PREFIX}:conv:${id}`,
23+
conversationMessages: (id: string) => `${KEY_PREFIX}:conv:${id}:messages`,
24+
userSettings: (id: string) => `${KEY_PREFIX}:settings:${id}`,
25+
userSession: (id: string) => `${KEY_PREFIX}:session:${id}`,
26+
allConversations: () => `${KEY_PREFIX}:conversations`,
27+
monthlyIndex: (ym: string) => `${KEY_PREFIX}:conv:month:${ym}`,
28+
cacheProduct: (id: string) => `${KEY_PREFIX}:cache:product:${id}`,
29+
};
30+
31+
// Usage
32+
await redis.set(keys.conversation('abc123'), JSON.stringify(data));
33+
await redis.del(keys.userSession('user1'));
34+
```
35+
36+
## Key Points
37+
38+
- Colons (`:`) as hierarchy separators are the Redis community standard. GUI tools like RedisInsight display this structure as a tree.
39+
- Generating keys via functions eliminates typos and enables IDE autocompletion. String literals make future refactoring impossible.
40+
- A `KEY_PREFIX` allows multiple apps to coexist on the same Redis instance without key collisions.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
layout: post
3+
title: "Redisキーのネーミング規則 - 階層的構造で管理する"
4+
date: 2025-12-20 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Redis, naming convention, key design, TypeScript]
7+
author: "Kevin Park"
8+
lang: ja
9+
excerpt: "Redisキーを体系的に管理するネーミング規則とTypeScriptヘルパー関数パターンをご紹介します。"
10+
---
11+
12+
## 問題
13+
14+
Redisキーを`"user_123_session"``"sess-user-123"`のようにバラバラに作っていると、どんなキーがあるのか把握できず、`KEYS *`検索も困難です。
15+
16+
## 解決方法
17+
18+
```typescript
19+
const KEY_PREFIX = 'myapp';
20+
21+
export const keys = {
22+
conversation: (id: string) => `${KEY_PREFIX}:conv:${id}`,
23+
conversationMessages: (id: string) => `${KEY_PREFIX}:conv:${id}:messages`,
24+
userSettings: (id: string) => `${KEY_PREFIX}:settings:${id}`,
25+
userSession: (id: string) => `${KEY_PREFIX}:session:${id}`,
26+
allConversations: () => `${KEY_PREFIX}:conversations`,
27+
monthlyIndex: (ym: string) => `${KEY_PREFIX}:conv:month:${ym}`,
28+
cacheProduct: (id: string) => `${KEY_PREFIX}:cache:product:${id}`,
29+
};
30+
31+
// 使用例
32+
await redis.set(keys.conversation('abc123'), JSON.stringify(data));
33+
await redis.del(keys.userSession('user1'));
34+
```
35+
36+
## ポイント
37+
38+
- コロン(`:`)で階層を区切るのがRedisコミュニティの標準です。RedisInsightなどのGUIツールがこの構造をツリー表示してくれます。
39+
- 関数でキーを生成するとタイプミスがなくなり、IDEの自動補完も効きます。文字列リテラルを直接使うと将来のリファクタリングが不可能になります。
40+
- プレフィックス(`KEY_PREFIX`)があれば、同じRedisインスタンスで複数のアプリがキー衝突なく共存できます。

0 commit comments

Comments
 (0)