ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Django Database Migration Conflict 해결하기
    Tech 2021. 8. 13. 17:13

    알고리마 Lead Software Engineer 김재용입니다. 최근 알고리마 개발팀이 경험한 Database Migration Conflict와 문제 해결 과정을 소개합니다!

     

    1.  발생: migration fail

     

    평화롭던 어느 날, 우리 staging branch에서 migration fail이 발생하고 있다는 사실을 인지했다. 이런 @#$%...! 한가로이 코드 리뷰를 하고 있을 때가 아니군! 곧바로 이 문제에 대한 파악에 돌입했다.

     

    마음을 가다듬고 차근차근 접근하기로 했다. 대체 언제, 어디서, 왜 때문에, 이런 문제가 발생하고 있었던 거지?

     

    다행히도 Circle CI에서 제공하는 필터 기능을 통해 문제를 일으킨 녀석을 금방 찾아낼 수 있었다.

    시작 지점은 어떤 PR 하나(#2214 PR). 이 PR이 stage에 merge 되고 부터 꾸준히 migration fail이 생겼던 것.

    조금 더 많은 정보가 필요했다. git 이력을 살피며 migration 변경이 일어나고도 fail하지 않은 마지막 PR을 찾아냈다. (PR #2199)

     

    2. 재현

     

    당장 문제를 해결하고자 migration fail로 배포가 멈춰버린 staging 환경과 동일한 상황을 만들기로 했다.

     

    우선 로컬 개발환경에서 DB 초기화를 진행했다. 이어 PR #2199가 merge된 지점으로 HEAD를 옮겨 migrate를 진행했다. 자, 마지막으로 PR #2214가 merge된 지점으로 HEAD를 옮겨서 migrate.

     

    Running migrations:
      Applying grpc_server.0026_auto_20210616_0508... OK
      Applying grpc_server.0027_pipelinetrainingtask_is_progress_callback_available...Traceback (most recent call last):
      File "/home/circleci/repo/server/venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
        return self.cursor.execute(sql, params)
    psycopg2.errors.DuplicateColumn: column "is_progress_callback_available" of relation "grpc_server_pipelinetrainingtask" already exists

    짠!

     

    staging 환경에서 나타나고 있던 migration conflict가 로컬에서 재현되었다. 문제를 해결하는 것은 어렵지만, 문제를 만드는 것은 쉽다.

     

    좋아, 그럼 이제 옳게 되돌릴 일만 남았다.

     

    3. 되짚기

     

    로컬 환경으로 옮겨둔 migration conflict 앞에서 천천히 생각에 빠져들었다.

     

    아니, 대체 왜 이런 문제가 발생하는거지?! 🤔

     

    문제를 일으킨 #2214 PR의 merge 방식을 하나 둘 시간 흐름순으로 나열했다. 그러다보니 아하, 문제 해결을 도와줄 조각들이 모이기 시작했다.

     

    결론부터 말하는 편이 낫겠다. 우리는 #2214 PR을 rebase → merge하는 과정에서 makemigrations를 잘못 적용했다.

     

    문제를 일으킨 merge방식을 시간 순서대로 풀어두면 이렇다.

     

    # 1. #2199 makemigrations
    
    # 2. 
     0026_pipelinetrainingtask_is_progress_callback_available #migration 생성
    
    # 3. merged into stage
    
    Running migrations:
      Applying grpc_server.0026_pipelinetrainingtask_is_progress_callback_available... OK
    CircleCI received exit code 0
    
     => 0026_pipelinetrainingtask_is_progress_callback_available # migration staging DB에 적용
    
    # 4. #2214 makemigration
    
    # 5. 
    0026_auto_20210616_0508 #migration 생성
    
    # 6. rebased on stage
    
    # 7. conflict를 일으킨 migration 파일을 삭제하고 다시 makemigrations
    
    # 8. 
    0026_auto_20210616_0508, 0027_pipelinetrainingtask_is_progress_callback_available #migration 생성
    
    # 9. merged into stage
    
    Running migrations:
      Applying grpc_server.0026_auto_20210616_0508... OK
      Applying grpc_server.0027_pipelinetrainingtask_is_progress_callback_available...Traceback (most recent call last):
      File "/home/circleci/repo/server/venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
        return self.cursor.execute(sql, params)
    psycopg2.errors.DuplicateColumn: column "is_progress_callback_available" of relation "grpc_server_pipelinetrainingtask" already exists​
          
          # 1) 
          0026_auto_20210616_0508 # migration staging DB 에 적용
    
          # 2) 
          0026_pipelinetrainingtask_is_progress_callback_available #에 적용했던 내용이         
          0027_pipelinetrainingtask_is_progress_callback_available #에서 반복 적용.

     

    여기까지다.

     

    문제를 되짚어 staging을 재구성한 결과 나온 것은, 성공적으로 반영된 migration 두 가지.

    1. PR #2199 의 0026_pipelinetrainingtask_is_progress_callback_available,
    2. PR #2214 의 0026_auto_20210616_0508.

     

    4. 해결

     

    문제 상황은 깔끔하게 정리했고, 남은 건 해결 뿐이다. 그렇다면 해결책은 무엇일까?

     

    우리는 #2199 와 #2214 에서 각각 생성한 0026_pipelinetrainingtask_is_progress_callback_available,

    0026_auto_20210616_0508 두 유효한 migration을 merge 해주기로 했다.

     

    참고)) 유효한 migration을 모두 두고 migrate를 실행할 때, django에서는 다음과 같은 merge 코드를 권한다:

    $ python manage.py migrate
    CommandError: Conflicting migrations detected (0026_pipelinetrainingtask_is_progress_callback_available, 0026_auto_20210616_0508 in grpc_server).
    To fix them run 'python manage.py makemigrations --merge'

     

    makemigrations --merge 명령어를 실행해 0027_merge_20210626_1144 migration을 만들었고, 음, 아무런 문제 없었다.

     

    이로써 성공적으로 migration conflict 해결!

    여러 feature branch에서 같은 app의 model을 두고 이리저리 만지다 보면 migration conflict가 언제나 우리들을 찾아올 수 있다. 그럴 때면 당황하지 말자. 각 feature branch에서 생성한 유효한 migration들을 makemigrations --merge 명령어를 통해 쉽게 merge해 해결할 수 있다.

     

    물론 Django 공식 문서에서도 이와 같은 문제가 생겼다고 해서 걱정하지 말 것을(?) 당부하고 있다! 😄 conflict를 겁내지 말고 적극적으로 migration을 진행하자.

     

    5. 돌이켜보기

     

    문제는 언제 어디서나 터진다. 이번 경우처럼 로컬 환경에서도, 코드 리뷰에서도 놓치고 지나치기 쉬운 문제들이 있다. 그럴 때는 미리 구축한 자동 배포, 테스트 시스템이 이를 발견하고 지적해줄 수 있다.

     

    우리 알고리마 역시 그 덕분에 실제 서버 환경에 영향을 미치지 않은 채 문제를 빠르게 해결할 수 있었다. 이번 migration conflict는 특히 이런 시스템의 중요성을 잘 보여줄 수 있는 예시라 생각한다. 다시 이런 상황이 벌어져도 당황하지 않기 위해 우리가 사용했던 방식을 정리해봤다.

     

    자동 시스템으로 자리 잡은 배포와 테스트, 적극적이고 꼼꼼한 코드 리뷰 문화를 만들어보는 것은 어떨까. 이는 곧 여러분이 로컬 환경에서 발견하지 못했던 문제들을 방지하고, 발견하고, 대처하도록 도와줄 것이다.

     

    문제가 터졌을 때는 패닉에 빠지기보다는 찬찬히 문제를 격리하고, 순차적으로 되짚고, 재현해보자. 해결책이 곧 떠오를 것이다. 어떤 문제 상황이든 반드시 해결 방법은 있다.

     


    <알고리마 채용> 알고리마에서는 '재용'님과 함께 성장하며 AI 에듀테크 시장을 이끌어갈 개발자를 기다립니다.

    (이미지를 클릭해 보세요!)

     

    댓글

Designed by Tistory.