날렸습니다...

2021.07.16
3 minutes read
3414 views
thumbnail

데이터 날렸습니다.

몇 개 글이 없긴 했지만 그래도... 😇

미천한 제가 이렇게 하나 배워갑니다.

발생

신나게 새로운 포스트를 작성을 다 할 때쯤 임시저장 시 500 에러가 났었습니다.

문제가 무엇인지 찾기 위해서 로그를 보니 utf8mb4 타입을 지정해주지 않아 발생한 거였습니다.
(나중에 확인된 건 글 작성 시에 이모지를 사용했는데 그 부분을 인식하지 못한 문제였습니다.)

처음엔 컬럼 길이보다 텍스트 길이가 길어서 문제가 생겼는지 싶어서 컬럼 타입을 text에서 longtext로 변경작업을 했었습니다.(js entitiy를 통해)
개발 시에는 synchronize를 통해서 자동으로 db 업데이트 되도록 하여 진행을 하고 있었기 때문에 문제는 없었습니다.

문제는 다른 곳에 있었고 500에러는 typeorm connect 시 config 설정에 charset을 설정하는 부분이 있는데 그 곳에 utf8mb4를 넣어주니 오류 없이 실행되었습니다.

정상동작을 위해 backend 배포를 위해 push를 하고 pm2 restart하는 했습니다.(여기서 다 날아감요)

그러고 글 작성을 마치고 post를 하였고 글 잘 올라갔는지 확인한 뒤에 잠을 자려고했는데...

자기 전에 기존 글을 확인하기 위해서 누르는 순간 ... 빈 내용만 나오는 화면을 보고 이건 뭔가 잘 못 되었다 라고 직감했죠.

아(Ah)...
synchronize

원인

orm으로 typeorm을 사용하고 있습니다.
db는 mariadb를 사용하고 있구요.

orm은 자바스크립트로 작성된 entity를 db와 연동해줘서 제공된 함수나 클래스를 통해 쉽게 db 쿼리 작업을 진행할 수 있습니다.

그 중 synchronize(sync로 줄여 말하겠습니다)는 자바스크립토 작성된 entity를 통해 schema를 생성, 수정, 삭제 등을 해주는 작업을 말합니다.

문제는 sync 설정을 끄지 않고 production으로 진행하고 있었다는 것이었습니다.

개발 시에는 편의성을 위해 sync은 아주 좋은 옵션입니다.
실제 schema를 신경쓰지 않고 자바스크립트 entity만 신경쓰면 되니까 그렇습니다.

결국은 새롭게 컬럼 타입이 변경되어 alter column이 되는 순간 기존 column은 사라지고 새로운 컬럼 타입을 가진 column이 생성되어 해당 컬럼에 있던 데이터가 싹 날아가는 일이 벌어졌습니다.

markdown을 저장하는 컬럼이 변경되었기 때문에 본문을 저장한 데이터는 날아갔지만 글은 남아있었고 리스트는 보이지만 실제 내용은 보이지 않는 상태로 되었던 것이었습니다.

해결방식(sad ending)

구글링을 통해 mysql, mariadb 복구 글을 몇개 찾다 보니 flashback이라는게 보였습니다.

mariadb 10.2.x 버전 부터 제공되는 기능으로 binlog를 통해서 스키마, 데이터 등을 복구할 수 있는 기능입니다.

아!
binlog를 찾아봐야지!
잉? 없네?

문제는 db 설정을 잘 해놨어야 binlog가 저장된다는 것이었습니다.

// (custom).cnf

[mysqld]
# Log Config
binlog_format                   = 2
expire_logs_days                = 10
long_query_time                 = 10
max_binlog_size                 = 512M
sync_binlog                     = 1
log-bin                         = mysql-bin
log-error                       = mysql.err
datadir                         = /var/lib/mysql/log

위와 같이 binlog를 위한 log config 설정을 해주고 db를 실행해야 binlog가 생성됩니다.

저는 아~주 plain config만 설정하였기 때문에 있을리가 없었죠.

우선 bin log가 존재를 해야 가능하다. bin log조차 없다면 깔끔하게 포기~~!!
- 어느 블로그

binlog를 통해 백업하는 방법은 mysqlbinlog를 통해 sql을 생성해서 동작시키면 되는 방법입니다.

$ mysqlbinlog /var/log/mysql/mybin-log.000001 > restore.sql

그래서 뭐...깔끔하게 포기하고 일단 나중에 상황을 대비해서 binlog 설정과 다른 config 설정들을 마치고 docker 설정도 몇가지 더 하고 배포했습니다.

typeorm migration

synchronize 기능은 개발 시엔 편하지만 운영 시엔 매우 주의깊에 쓰거나 사용하지 말아야 합니다. 왜냐하면 저 처럼 될 수 있으니깐요 ^^..

스키마를 수정할 시에는 데이터 또한 수정되거나 삭제 되어버릴 수 있기 때문에 매우 조심스럽게 해야합니다.

typeorm에는 migration 기능을 제공합니다.(cli를 이용합니다.)
(up, down 기능을 제공해주어서 rollback 할 수도 있습니다.)

1. ormconfig 설정

  cli: {
      ...
      migrationsDir: join(__dirname, '/migrations'),
  },
js

cli migrationDir 경로를 지정해주고 마이그레이션 코드를 생성해줍니다.

2. migration:create

typeorm migration:create -n [whateverYouWantName]
bash

script로 만들어 쓰도록 합시다

"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js --config ./src/ormconfig.ts",
"migration": "yarn typeorm migration:run",
"migration:create": "yarn typeorm migration:create -n",
"migration:revert": "yarn typeorm migration:revert"
json

migration:generate 기능도 있지만 table을 drop 후 create하는 방식을 사용하기 때문에 create방식으로 쓰도록 하자.

-Migration generation drops and creates columns instead of altering resulting in data loss

그 후 migration 코드를 작성하면 끝.

3. implement migration code

migration:create를 하면 다음과 같이 생성됩니다.

import {MigrationInterface, QueryRunner} from "typeorm";

export class UserTableCreate1615828367540 implements MigrationInterface {

    // migration do
    public async up(queryRunner: QueryRunner): Promise<void> {
    }

    // migration rollback
    public async down(queryRunner: QueryRunner): Promise<void> {
    }

}

js

내부에 실제 동작할 DDL을 작성해줍니다.

import { MigrationInterface, QueryRunner } from "typeorm";

export class NewMigrationTIMESTAMP implements MigrationInterface {

    async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`);
    }

    async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "name" RENAME TO "title"`);
    }

}
js

4. run, revert

# up 메서드가 실행됩니다.
typeorm migration:run

# down 메서드가 실행됩니다.
typeorm migration:revert
bash

마치며

개인적으로 운영하는 db라서 망정이지 실제로 서비스되는 db에 이런 문제가 생겼으면...

그래도 이렇게 또 하나 배워 간다고 생각하고 긍정적으로 생각해보려고 합니다.
(그게 잘 안돼...)

reference