부드러운 데이터베이스 변경을 위한 거위 이주

안녕, 친구야!

오늘은 데이터베이스 마이그레이션에 대해 이야기해보고, 왜 그렇게 중요한지 살펴보겠습니다. 오늘날, 데이터베이스에 대한 모든 변경은 신중하게 하고 특정 프로세스에 따라 수행되어야 하는 것은 놀라운 일이 아닙니다. 이러한 단계들은 이상적으로는 CI/CD 파이프라인에 통합되어 모든 것이 자동으로 실행되도록 해야 합니다.

다음은 우리의 안건입니다:

  1. 무슨 문제가 있나요?
  2. 어떻게 해결할까요?
  3. 간단한 예시
  4. 더 복잡한 예시
  5. 권장사항
  6. 결과
  7. 결론

무슨 문제가 있나요?

만약 당신의 팀이 데이터베이스 마이그레이션을 다뤄본 적이 없고, 왜 필요한지 정확히 모르겠다면, 그것을 해결해보죠. 기본적인 내용을 이미 알고 있다면 건너뛰어도 괜찮습니다.

주요 도전 과제

데이터베이스에 “계획된” 그리고 “원활한” 변경을 가할 때, 서비스 가용성을 유지하고 SLA 요구 사항을 충족해야 합니다 (사용자들이 다운타임이나 랙으로 고통받지 않도록). 예를 들어 500만 명의 사용자가 있는 테이블의 열 유형을 변경하려고 한다고 상상해보세요. 만약 이를 “단호하게” 수행한다면 (예: 준비 없이 단순히 ALTER TABLE을 실행한다면), 테이블이 상당 시간 동안 잠길 수 있고 사용자들은 서비스를 이용할 수 없을 것입니다.

그러한 머리 아픔을 피하기 위해 두 가지 규칙을 따르세요:

  1. 테이블을 잠그지 않는 방식으로 마이그레이션을 적용하세요 (또는 적어도 잠금을 최소화하세요).
  2. 열 유형을 변경해야하는 경우, 올바른 유형의 새 열을 먼저 만든 다음 이후에 이전 열을 삭제하는 것이 종종 더 쉽습니다.

다른 문제: 버전 관리와 롤백

때로는 마이그레이션을 롤백해야 할 때가 있습니다.

이를 수동으로 하는 것은 — 제품 데이터베이스로 들어가 데이터를 만지작거리는 것은 — 위험할 뿐만 아니라 직접 액세스 권한이 없다면 불가능할 수도 있습니다. 여기서 전용 마이그레이션 도구가 유용합니다. 변경 사항을 깔끔하게 적용하고 필요한 경우 되돌릴 수 있게 해줍니다.

어떻게 해결할까요? 올바른 도구 사용

각 언어 및 생태계에는 고유의 마이그레이션 도구가 있습니다:

  • Java의 경우, LiquibaseFlyway가 일반적입니다.
  • Go의 경우, 인기 있는 선택은 goose입니다 (여기서 살펴볼 것입니다).
  • 등이 있습니다.

Goose: 무엇이며 왜 유용한가

Goose는 마이그레이션을 자동으로 관리하는 경량 Go 유틸리티입니다. 다음을 제공합니다:

  • 간결함. 최소한의 종속성 및 마이그레이션을 위한 투명한 파일 구조.
  • 다양성. 다양한 DB 드라이버 지원 (PostgreSQL, MySQL, SQLite 등).
  • 유연성. SQL 또는 Go 코드로 마이그레이션 작성.

Goose 설치

Shell

 

작동 방식: 이주 구조

기본적으로 Goose는 db/migrations에서 이주 파일을 찾습니다. 각 이주는 이 형식을 따릅니다:

Shell

 

  • NNN은 이주 번호입니다(예: 001, 002 등).
  • 그 후에는 init_schema와 같은 설명적인 이름을 사용할 수 있습니다.
  • 확장자는 .sql 또는 .go가 될 수 있습니다.

SQL 이주의 예

파일: 001_init_schema.sql:

SQL

 

첫 번째 예제

열 유형 변경하기 (문자열 → 정수)

age 유형이 VARCHAR(255)users 테이블이 있다고 가정해보겠습니다. 이제 이를 INTEGER로 변경하려고 합니다. 다음은 마이그레이션이 어떻게 보일지에 대한 예제입니다 (파일 005_change_column_type.sql):

SQL

 

여기서 무슨 일이 일어나고 있는지:

  1. Up 마이그레이션

    • age 열을 INTEGER로 변경합니다. USING (age::INTEGER) 절은 PostgreSQL에 기존 데이터를 새 유형으로 변환하는 방법을 알려줍니다.
    • age에 숫자가 아닌 데이터가 있는 경우 이 마이그레이션이 실패합니다. 그 경우에는 더 복잡한 전략이 필요합니다(아래 참조).
  2. 다운 마이그레이션

    • 롤백하면 ageVARCHAR(255)로 되돌립니다.
    • 우리는 다시 USING (age::TEXT)를 사용하여 INTEGER에서 텍스트로 변환합니다.

두 번째 및 복잡한 경우: 다단계 마이그레이션

age 열에 어수선한 데이터(숫자뿐만 아니라)가 포함될 수 있는 경우, 여러 단계로 수행하는 것이 더 안전합니다:

  1. 타입이 INTEGER인 새로운 열(age_int)을 추가합니다.
  2. 유효한 데이터를 새로운 열로 복사하고, 무효 항목을 처리하거나 제거합니다.
  3. 오래된 열을 삭제합니다.
SQL

 

적절한 롤백을 허용하기 위해 Down 섹션은 역순으로 동작을 반영합니다.

자동화가 핵심입니다

시간을 절약하기 위해 마이그레이션 명령을 Makefile(또는 다른 빌드 시스템)에 추가하는 것이 정말 편리합니다. 아래는 PostgreSQL에 대한 주요 Goose 명령이 포함된 예제 Makefile입니다.

가정해 보겠습니다:

  • 데이터베이스의 DSN은 postgres://user:password@localhost:5432/dbname?sslmode=disable입니다.
  • 마이그레이션 파일은 db/migrations에 있습니다.
Shell

 

사용 방법은?

1. 새 마이그레이션 생성(SQL 파일). 이 파일은 db/migrations/002_add_orders_table.sql을 생성합니다.

Shell

 

2. 모든 마이그레이션 적용. Goose는 데이터베이스에 schema_migrations 테이블을 생성하고(존재하지 않는 경우) 새로운 마이그레이션을 오름차순으로 적용합니다.

Shell

 

3. 마지막 마이그레이션 롤백. 마지막 것을 다운시킵니다.

Shell

 

4. 모든 마이그레이션 롤백(운영 환경에서는 주의 필요). 전체 초기화.

Shell

 

5. 마이그레이션 상태 확인.

Shell

 

출력 예시:

Shell

 

요약

마이그레이션 도구와 Makefile을 사용하여 우리는:

  1. 생산 데이터베이스에 대한 직접 접근을 제한하고 마이그레이션을 통해서만 변경할 수 있습니다.
  2. 데이터베이스 버전을 쉽게 추적하고 문제가 발생할 경우 롤백할 수 있습니다.
  3. 데이터베이스 변경의 일관된 단일 기록을 유지합니다.
  4. 마이크로서비스 환경에서 실행 중인 운영 환경을 중단하지 않는 “부드러운” 마이그레이션을 수행합니다.
  5. 추가 검증을 얻습니다 — 모든 변경 사항은 PR 및 코드 검토 과정을 거칩니다(이러한 설정이 활성화된 경우).

또 다른 장점은 이러한 모든 명령을 CI/CD 파이프라인에 쉽게 통합할 수 있다는 것입니다. 그리고 기억하세요 — 모든 것보다 보안이 가장 중요합니다.

예를 들어:

YAML

 

결론 및 팁

주요 아이디어는 매우 간단합니다:

  • 마이그레이션을 작고 자주 유지하세요. 필요할 경우 쉽게 검토, 테스트 및 복구할 수 있습니다.
  • 모든 환경에서 동일한 도구를 사용하세요 따라서 개발, 스테이지 및 프로드가 동기화됩니다.
  • 마이그레이션을 CI/CD에 통합하세요 이렇게 하면 수동으로 실행하는 특정 인물에 의존하지 않게 됩니다.

이러한 방식으로 데이터베이스 구조를 변경하는 신뢰할 수 있고 통제된 프로세스를 갖게 될 것입니다 — 이로써 프로덕션을 망가뜨리지 않고 빠르게 대응할 수 있게 됩니다.

마이그레이션 작업을 잘 수행하시길 바랍니다!

읽어 주셔서 감사합니다!

Source:
https://dzone.com/articles/goose-as-crucial-tool-for-your-service