앞의 글에서 언급한 바와 같이, DB 백업의 필요성을 느꼈다. [이전글 바로가기 👉 클릭]
당장 떠오르는 방법은 스냅샷이었다.
AWS RDS를 사용하는 만큼, 클라우드에서 제공하는 스냅샷이 가장 간단하고 확실한 방법이라고 생각했다. 개인이 아닌 기업의 입장이라면 확실히 효율적인 방법이 아니었을까? 하지만 나의 경우 개인 프로젝트인 만큼 과금의 우려가 있었다. 또한 게시글이 불규칙하게 올라가는 만큼, 노드 크론잡을 활용하여 백업 스케줄링을 유연하게 가져갈 수 있겠다라는 생각이 들어 cron job + mysqldump의 조합으로 DB 백업을 시도하기로 결정하였다.
현재 mysqldump로 SQL백업 파일을 만드는 것까지 진행한 내용을 기록해보고자 한다.
꽤나 많은 사람들이 개인 프로젝트에 mysqldump를 활용하고 있었다.
dump가 특정 시점의 내용을 복사 혹은 기록한다는 점에서 내겐 git branch를 새로 생성하는 개념과 비슷하게 다가왔다.
The mysqldump client utility performs logical backups, producing a set of SQL statements that can be executed to reproduce the original database object definitions and table data.
출처: MySQL 공식 사이트
위의 설명과 같이 mysqldump는 DB 혹은 특정 테이블의 백업을 SQL문으로 생성한다.
이번 이슈의 경우, 게시글 내용이 통째로 사라지는 문제였기에, article 테이블을 백업하는 스크립트를 우선적으로 작성하였다.
전에 노드의 worker threads와 관련하여 게시글을 작성한 적이 있다.
mysqldump의 경우, SQL파일의 생성해내는 I/O 작업인 만큼, 스레드를 활용하는것이 효율적인가 하는 의문이 들었다. 역시나 구글링을 해보니 다수의 사람들이 자식 프로세스를 활용하여 백업 스크립트를 작성하고 있었다. 확실히 굳이 같은 메모리를 공유할 필요가 없을뿐더러 시스템에 접근하는만큼 child process 모듈을 활용하여 스크립트를 작성하게 되었다.
spawn & exec구글링 결과, 많은 사람들이 child process 모듈의 exec 메소드를 활용하여 백업 스크립트를 활용하고 있었다. 그러던 와중 스택오버플로우에서 아래와 같은 글을 발견했다.
"You can use exec method as described by siavolt it is simple but not the "best way" as requested by the question. The best and efficient way in case if you care about your system memory and CPU and performance or if you have a big database is to use spawn along with the stream, ..."
노드에서의 mysqldump를 질문하자 첫번째 답변자는 exec 메소드를, 두번째 답변자는 spawn 메소드를 제안했다. 둘의 차이를 인지하고 선택하고 싶어 찾아본 내용은 아래와 같다.
운영체제의 쉘(shell)을 사용하는 만큼 >, |과 같은 기능을 그대로 작성할 수 있다. mysqldump의 경우, exec(' mysqldump -u root -p[root_password] [database_name] > dumpfilename.sql');와 같이 바로 쉘에서 작성하듯 스크립트를 적을 수 있다.
명령이 실행된 후 결과물은 버퍼에 한번에 저장되고, 완료된 후에 콜백에 전달된다. stdout, stderr 표준 출력 버퍼의 크기가 1MB로 제한되어 있어서 크기가 넘치면 프로그램이 중단될 수도 있는 리스크가 있다.
많은 사람들이 사용하는 만큼 가볍게 사용하기 용이해보였고, 짧은 명령어로 결과가 빠르게 필요할때 활용하면 유용할 듯 싶었다.
spawn은 의미 그대로 새로운 프로세스를 생성하여 이를 stream 형식으로 처리한다. 대규모 데이터나 실시간 처리에 적합해보였다.
stream인만큼 데이터 크기의 제한이 크지 않아보였다. 물론 줄글로 작성된 article 테이블 백업 SQL이 얼마나 크겠냐만, 앞으로도 계속해서 작성해나갈뿐더러 해당 테이블 외에 어드민 시스템 까지 백업한다고 고려해봤을때 안정적으로 가져가면 좋겠다는 생각이 컸다. 물론 괜히 서비스 운영중에 버퍼 초과로 다운이 되지 않았으면 좋겠다는 생각이 든 것도 사실이다. (또한 프론트 메이트와 새로운 프로젝트로 실시간 모니터링 서비스를 논의하는 만큼 spawn을 미리 써보고 싶기도 했고...ㅎ..)
import { spawn } from "child_process";
import { config } from "dotenv";
import { createWriteStream } from "fs";
import path from "path";
config();
const TODAY = new Date().toISOString().slice(0, 10);
const DB_HOST = process.env.DB_HOST!;
const DB_USERNAME = process.env.DB_USERNAME;
const DB_PASSWORD = process.env.DB_PASSWORD;
const DB_NAME = process.env.DB_DB;
const ARTICLE_TABLE = "article";
const BACKUP_PATH = path.join(
process.cwd(),
"../",
`${DB_NAME}_${ARTICLE_TABLE}_backup_${TODAY}.sql`,
);
const writeStream = createWriteStream(BACKUP_PATH);
const dumpCommand = spawn("mysqldump", [
"-h",
DB_HOST,
"-u",
`${DB_USERNAME}`,
`-p${DB_PASSWORD}`,
`${DB_NAME}`,
ARTICLE_TABLE,
]);
dumpCommand.stdout
.pipe(writeStream)
.on("finish", () => console.log("Dump completed."))
.on("error", (err) => console.log("Dump Error:", err));
나름 스스로 근거를 갖고 개발을 해나가려 하는데 아직 부족한 부분이 많다. 공부할게 산더미고만!