Node.js를 사용한 분산 비디오 트랜스코딩 시스템 구축
Source: Dev.to

왜 브로커인가?
브로커는 두 가지 이유로 분산 시스템의 그 “hello world”입니다:
-
쉽게 시작하고 실행할 수 있음 – 이들은 hive / master‑node 패턴을 강제하여 자연스럽게 확장됩니다.
node node hive / broker client‑facing server client node client
순수 JavaScript 브로커 for Node.js
// broker.js
import Bunny from "bunnimq";
import path from "path";
import { fileURLToPath } from "url";
Bunny({
port: 3000,
DEBUG: true,
cwd: path.dirname(fileURLToPath(import.meta.url)), // path to the .auth file
queue: {
Durable: true,
MessageExpiry: 60 // 1 hour
}
});
로우레벨 Node.js 최적화
- Object → 바이너리 컴파일러
SharedArrayBuffers 및 워커 스레드
const buffer = new SharedArrayBuffer();
const worker = new Worker(); // **Prerequisite:** FFmpeg는 설치되어 있어야 하며 `PATH`에 있어야 합니다. 테스트해 보세요:
ffmpeg -i img.jpg img.png
Source: …
분산 비디오 트랜스코딩 예제
프로젝트 초기화
npm init -y && npm i bunnimq bunnimq-driver
폴더 구조
ffmpegserver/
server.js # ← 하이브(브로커)
producer.js # 클라이언트용 서버
consumer.js # 노드 서버 / 워커
.auth # 프로듀서와 컨슈머 인증을 위한 자격 증명(.env와 유사)
.auth
다음 형식으로 비밀 자격 증명을 입력하세요: username:password:privileges (특권은 레포지토리에서 확인).
sk:mypassword:4
jane:doeeee:1
john:doees:3
Server (Hive) – server.js
간단한 비‑TLS 설정 (TLS는 지원됩니다 – GitHub 저장소를 참고하세요):
import Bunny from "bunnimq";
import path from "path";
import { fileURLToPath } from "url";
Bunny({
port: 3000,
DEBUG: true,
cwd: path.dirname(fileURLToPath(import.meta.url)), // for .auth file
queue: {
Durable: true,
QueueExpiry: 0,
MessageExpiry: 3600
}
});
Producer – producer.js
서버 브라우저와 기타 클라이언트가 통신하는 대상입니다. 요청을 받아 작업을 hive에 푸시합니다.
import BunnyMQ from "bunnimq-driver";
import fs from "node:fs/promises";
const bunny = new BunnyMQ({
port: 3000,
host: "localhost",
username: "sk",
password: "mypassword",
});
큐 선언 (존재하지 않을 경우)
bunny.queueDeclare(
{
name: "transcode_queue",
config: {
QueueExpiry: 60,
MessageExpiry: 20,
AckExpiry: 10,
Durable: true,
noAck: false,
},
},
(res) => {
console.log("Queue creation:", res);
}
);
비디오 트랜스코딩 작업 퍼블리시
데모에서는 로컬 폴더에서 비디오를 읽어옵니다 (경로를 본인 환경에 맞게 교체하세요):
async function processVideos() {
const videos = await fs.readdir(
"C:/Users/[path to a folder with videos]/Videos/Capcut/test"
); // 보통은 스토리지 버킷 링크
for (const video of videos) {
const job = {
id: Date.now() + Math.random().toString(36).substring(2),
input: `C:/Users/[path to a folder with videos]/Videos/Capcut/test/${video}`,
outputFormat: "webm",
};
// 큐에 넣기
bunny.publish("transcode_queue", JSON.stringify(job), (res) => {
console.log(`Job ${job.id} published:`, res ? "ok" : "400");
});
}
}
processVideos();
소비자 – consumer.js
이들은 작업을 가져오고, 비디오를 트랜스코딩하며, 완료를 확인하는 워커 노드입니다.
transcode_queue를 소비
import BunnyMQ from "bunnimq-driver";
import { spawn } from "child_process";
import path from "path";
const bunny = new BunnyMQ({
port: 3000,
host: "localhost",
username: "john",
password: "doees",
});
bunny.consume("transcode_queue", async (msg) => {
console.log("Received message:", msg);
try {
const { input, outputFormat } = JSON.parse(msg);
// Normalise paths
const absInput = path.resolve(input);
const output = absInput.replace(/\.[^.]+$/, `.${outputFormat}`);
console.log(
`Spawning: ffmpeg -i "${absInput}" -f ${outputFormat} "${output}" -y`
);
await new Promise((resolve, reject) => {
const ffmpeg = spawn(
"ffmpeg",
["-i", absInput, "-f", outputFormat, output, "-y"],
{ shell: true } // helps Windows find ffmpeg.exe
);
ffmpeg.on("error", reject);
// FFmpeg logs to stderr
ffmpeg.stderr.on("data", (chunk) => {
process.stderr.write(chunk);
});
ffmpeg.on("close", (code) => {
if (code === 0) {
console.log(`Transcoding complete: ${output}`);
// Acknowledge the message
bunny.Ack((ok) => console.log("Ack sent:", ok));
resolve();
} else {
reject(new Error(`FFmpeg exited with code ${code}`));
}
});
});
} catch (err) {
console.error("Failed to process job:", err);
// Optionally reject / requeue the message here
}
});
시스템 실행
-
브로커 시작
node server.js -
하나 이상의 컨슈머 시작 (별도의 터미널에서)
node consumer.js -
작업 게시
node producer.js
각 컨슈머가 큐에서 작업을 가져와 FFmpeg를 호출하고 완료를 확인하는 것을 볼 수 있습니다. 스케일링은 브로커에 접근할 수 있는 동일한 머신이나 다른 머신에서 추가 consumer.js 프로세스를 실행하는 것만큼 간단합니다.
행복한 트랜스코딩!