주요 컨텐츠로 이동
제품

코드처럼 데이터베이스 브랜칭하기: Glaspoort의 프로덕션 환경에 적용된 Lakebase용 CI/CD 패턴

모든 환경을 프로덕션에서 분기하고, PR별 임시 데이터베이스를 구동하며, 마이그레이션을 신뢰할 수 있는 단일 소스로 취급함으로써 Glaspoort가 애플리케이션 코드와 동일한 엄격함으로 데이터베이스 변경 사항을 배포하는 방법.

작성자: Hadi Farhat, Gideon Spierings, Ricardo de Vries , Raymon Veldman

  • Glaspoort가 개발 및 승인 환경을 서로 쌓아 올리는 대신 프로덕션에서 직접 분기하도록 Lakebase 브랜칭 설정을 설계하여, 오래된 환경을 새로 고칠 때 하위의 모든 것을 삭제하고 다시 빌드해야 하는 "상위로부터의 재설정 함정(reset-from-parent trap)"을 방지한 방법.
  • PR별 CI/CD 흐름: 모든 pull request(PR)가 프로덕션에서 복사된 고유한 일회용 신규 Lakebase 브랜치를 가져오는 방법, 실제 환경에 영향을 미치기 전에 라이브 앱 이미지에 대해 마이그레이션을 재실행하고 테스트하는 방법, 그리고 데이터베이스가 아닌 마이그레이션 자체를 신뢰할 수 있는 단일 소스(source of truth)로 취급하는 이유.
  • 두 가지 프로모션 모델 간의 절충안: CI가 통과하는 즉시 병합하는 방식과 PR이 승인을 통해 완전히 프로모션된 후에만 병합하는 방식의 비교. 이 글에서는 Glaspoort가 속도 우선 접근 방식을 선택한 이유와 안전성을 유지하기 위해 추가한 보호 조치(스택 재검증 및 위기 파이프라인)에 대해 설명합니다.

우리가 무시할 수 없었던 문제

Glaspoort는 네덜란드에서 광섬유 인프라를 구축하고 운영합니다. 모든 것은 광섬유 연결 수를 늘리는 것을 중심으로 진행되며, 오랫동안 데이터 팀은 그 목표를 지원하기 위해 BI 보고서를 작성하는 데 시간을 보냈습니다. 하지만 보고서가 완성될 때쯤에는 각 보고서의 배경이 되는 질문이 이미 구식이 되어 버리곤 했습니다. 그 결과 일회성 보고서가 무분별하게 늘어났고, 사용자들은 후속 질문을 할 곳이 없었습니다.

그래서 우리는 이러한 의존성을 끊어냈습니다. 다음 보고서를 제공하는 대신, 프로젝트 매니저가 자신의 프로젝트에 어떤 기회가 있는지 직접 확인할 수 있는 맞춤형 프론트엔드 애플리케이션을 구축했습니다. 새로운 점은 내부 구조에 있습니다. 우리는 Databricks 제품을 앱의 빌딩 블록으로 직접 사용합니다. 데이터와 대화하고 신속하게 분석을 실행할 수 있는 Genie. 인사이트와 셀프 서비스 분석을 위한 AI/BI 대시보드. 프로젝트에서 특이 사항이 발생하는 즉시 프로젝트 매니저에게 알림을 보내는 Agent Bricks 기반의 자동화된 워크플로우, 그리고 애플리케이션의 트랜잭션 데이터를 위한 Databricks OLTP 데이터베이스인 Lakebase를 활용합니다.

이러한 조합은 최근까지 서로 분리되어 있던 두 세계, 즉 분석 환경과 분석 데이터가 트랜잭션 데이터와 만나는 운영 프론트엔드를 하나로 묶어줍니다. 이제 데이터 팀은 일회성 보고서 대신 Genie 스페이스와 메타데이터에 시간을 투자합니다. 하지만 CI/CD, 데이터 품질 테스트, Infrastructure as Code, 데이터 거버넌스와 같은 견고한 기반이 없다면 이 모든 것이 빠르게 유지될 수 없습니다. 이 기반 중에서도 가장 신중한 설계가 필요했던 부분이 있으며, 이 글의 나머지 부분에서 다룰 내용이 바로 그것입니다. 바로 앱 배후에 있는 데이터베이스에 변경 사항을 반영(ship)하는 방법입니다.

해당 애플리케이션 뒤에는 Databricks Lakebase 데이터베이스가 있습니다. 이는 레이크하우스에 임시로 덧붙여진 것이 아니라 레이크하우스 바로 옆에서 실행되는 서버리스 Postgres OLTP입니다. 데이터 흐름은 설명하기 간단하지만, 직접 운영해 보니 보기보다 훨씬 더 흥미로웠습니다.

  • 레이크하우스에서 정제된(curated) 데이터가 Lakebase 프로덕션 브랜치로 동기화되어 읽기 전용 애플리케이션 스키마에 저장됩니다.
  • 애플리케이션은 자체 상태를 동일한 브랜치의 별도 스키마에 다시 기록하므로, 레이크하우스에서 읽은 데이터와 앱에서 기록한 데이터가 충돌 없이 공존합니다.
  • 그 위에서 우리는 개발(development), 인수(acceptance), 프로덕션(production)의 세 가지 논리적 환경을 운영합니다. 우리는 애플리케이션 코드의 변경 사항을 배포하는 것과 동일한 방식으로 풀 리퀘스트(PR), CI, 게이트 승인(gated promotion)을 통해 이 데이터베이스에 변경 사항을 반영합니다.

image3.png

마지막 포인트에 흥미로운 질문이 숨어 있습니다. OLTP 데이터베이스도 애플리케이션 코드만큼 엄격하게 다루어야 한다고 결정하는 순간, 한 가지 어려운 질문에 답해야 합니다.

서로의 작업을 방해하지 않으면서, 그리고 테스트를 의미 있게 만드는 최신 데이터를 잃지 않으면서, 어떻게 모든 PR이 프로덕션과 동일한 데이터베이스를 대상으로 테스트하도록 할 수 있을까요?

여기서 발생하는 실패 유형은 팀 내에서 데이터베이스를 공유해 본 사람이라면 누구나 익숙할 것입니다. 개별적으로는 통과하지만 함께 병합될 때 깨지는 PR들, 실제 프로덕션의 모습과 서서히 멀어진 개발 및 인수 환경, 그리고 깨끗한 데이터를 다시 얻기 위해 환경을 허물고 모든 연결 문자열을 다시 연결하고 모든 권한(grant)을 수동으로 다시 적용해야 하는, 아무도 원치 않는 데이터 리프레시의 날 등이 있습니다.

이 글은 우리가 이러한 문제들을 어떻게 대부분 피할 수 있었는지, 그리고 우리가 여전히 활발히 논의 중인 한 가지 결정에 대한 이야기입니다.

60초 만에 이해하는 Lakebase 브랜칭

아직 Lakebase 브랜치를 사용해 보지 않으셨다면, 이 글을 이해하기 위해 필요한 유일한 개념 모델은 다음과 같습니다.

Lakebase 브랜치는 상위 브랜치에서 파생된 copy-on-write 방식의 Postgres 브랜치입니다. 브랜치를 생성해도 데이터가 복사되지 않으며, 비용 효율적이고 즉각적으로 상태를 포크합니다. 브랜치는 데이터를 쓸 때만 상위 브랜치와 달라집니다. 각 브랜치는 자체 엔드포인트와 데이터를 가지며 완전히 격리됩니다. 단 몇 초 만에 생성하고 그만큼 빠르게 삭제할 수 있습니다.

Git과 비슷하게 들린다면 정확히 보신 것입니다. 아래의 모든 내용을 구성하는 비유는 간단합니다. Git의 피처(feature) 브랜치는 Lakebase의 데이터베이스 브랜치에 매핑됩니다. PR은 자체 코드 브랜치와 자체 데이터베이스 브랜치를 가지며, 테스트는 두 브랜치 모두에서 실행되고, 작업이 완료되면 프로덕션으로 승격(promote)됩니다.

다음 섹션으로 넘어가기 전에 기억해야 할 제약 조건이 하나 있습니다. 이는 Git 브랜칭과의 중요한 차이점입니다. 상위 브랜치로부터 브랜치를 리셋하려면 먼저 해당 브랜치의 하위 브랜치들을 삭제해야 합니다. 상위 브랜치는 자신에게 의존하는 하위 브랜치들이 있는 상태에서 리셋될 수 없습니다.

상위 브랜치 리셋의 함정

대부분의 팀이 화이트보드에 환경을 그리는 방식을 반영하기 때문에 가장 먼저 고려하는 설계는 다음과 같습니다.

  • 프로덕션에서 파생된 단일 장기 실행(long-lived) 개발 브랜치.
  • 개발 브랜치에서 파생된 인수 브랜치.
  • 개발 브랜치에서 파생된 피처 브랜치.

루트에 프로덕션이 있고 그 아래에 개발, 그리고 그 아래에 인수 및 피처 브랜치가 매달려 있는 깔끔한 계층 구조입니다. 하지만 이는 예측 가능한 두 가지 실패 유형을 유발합니다. 브랜치 간의 차이(drift)가 발생하며, 이 차이를 해결하는 비용이 너무 커서 팀들이 해결을 포기하게 된다는 점입니다.

첫째, 개발 및 인수 브랜치가 프로덕션과 달라집니다. 프로덕션은 계속해서 동기화된 최신 데이터와 실제 애플리케이션 쓰기 작업을 수신하지만, 장기 실행되는 개발 및 인수 브랜치는 그렇지 않습니다. 한두 번의 스프린트만 지나도 실제 배포 대상 데이터베이스와는 전혀 다른 데이터베이스를 대상으로 테스트하게 됩니다.

가장 확실한 해결책은 프로덕션에서 개발 브랜치를 리셋(refresh)하는 것이지만, 여기서 이전 섹션의 제약 조건이 큰 부담으로 작용합니다. 상위 브랜치에서 개발 브랜치를 리셋하려면 먼저 개발 브랜치의 하위 브랜치를 삭제해야 합니다. 이 토폴로지에서는 인수 브랜치와 개발 브랜치에 매달려 있는 모든 피처 브랜치를 의미합니다. 따라서 일상적인 "최신 데이터 가져오기" 작업이 연쇄적인 작업으로 이어집니다. 인수 및 모든 피처 브랜치 삭제, 개발 브랜치 리셋, 환경 재구축, 이전 브랜치를 가리키던 모든 연결 문자열 재설정, 그리고 방금 삭제한 브랜치에 권한이 종속되어 있으므로 모든 Postgres 권한(grant) 재적용 등이 포함됩니다.

image1.png

이러한 단계 중 어느 것도 그 자체로는 어렵지 않습니다. 하지만 이 작업들이 주기적으로 반복되면 전체 팀이 리셋 작업을 은근슬쩍 피하게 만드는 확실한 원인이 되며, 결국 모두가 오래되고 차이가 발생한 데이터를 대상으로 테스트하는 상태로 돌아가게 됩니다. 이것이 바로 원래의 문제였습니다. 단순한 토폴로지는 단순히 힘든 오후를 선사하는 데 그치지 않고, 환경을 깨끗하게 유지하려는 노력을 저해합니다.

Glaspoort의 설계: 항상 프로덕션에서 브랜치 생성

해결책은 토폴로지에 대한 생각을 한 줄 바꾸는 것이며, 이는 엄청난 효과를 가져옵니다. 장기 실행되는 모든 환경 브랜치를 다른 환경이 아닌 프로덕션의 하위 브랜치로 만드는 것입니다.

개발 및 인수 브랜치는 모두 프로덕션에서 직접 가져온 브랜치입니다. 이들은 프로덕션 아래에 수직으로 쌓이는 것이 아니라 나란히 위치합니다. 서로가 서로의 상위 브랜치가 아니므로, 하나를 리셋하더라도 다른 하나를 삭제할 필요가 전혀 없습니다.

이 단 한 가지 변화로 함정에서 벗어날 수 있습니다. 개발 또는 인수 브랜치에 차이가 발생하면, 대략 한 스프린트에 한 번 또는 최신 데이터가 필요할 때마다 프로덕션에서 리셋합니다(현재는 UI 작업으로 가능). 개발 또는 인수 브랜치 아래에 아무것도 매달려 있지 않기 때문에 먼저 삭제해야 할 하위 브랜치도 없고, 팀 전체에 걸쳐 연결 문자열을 다시 연결할 필요도 없으며, 권한 재적용 마라톤을 할 필요도 없습니다. 리셋 비용이 저렴하기 때문에 실제로 리셋을 수행하게 되고, 결과적으로 환경이 깨끗하게 유지됩니다. 리셋 중에 실수로 변경 사항을 놓치더라도 다음 마이그레이션 재실행 시 간단히 다시 적용됩니다.

image2.gif

애니메이션: PR별 라이프사이클. 게시된 블로그에서 재생되며, 이 문서에서는 스틸 프레임으로 표시됩니다.

PR별 흐름

일상적인 개발자 루프는 이 안정적인 토폴로지 위에 임시(ephemeral) 브랜치를 계층화합니다.

  1. 개발자가 임시 PR을 엽니다(TTL은 1시간으로 설정).
  2. CI가 프로덕션에서 새로운 임시 pr-xxxx 브랜치를 생성합니다. 우리는 항상 프로덕션에서 브랜치를 생성하며 데이터베이스를 다시 병합하지 않습니다. 이러한 PR별 브랜치는 일회용입니다. PR이 닫히는 즉시 아카이브하므로, 프로젝트당 아카이브되지 않은 브랜치 제한인 10개 미만으로 Lakebase를 유지할 수 있습니다.
  3. CI가 해당 새 브랜치에 대해 마이그레이션을 재실행합니다. git diff 검사를 통해 마이그레이션 리허설이 실제로 필요한지 결정하므로, 마이그레이션을 변경하지 않는 PR은 재실행을 건너뜁니다.
  4. 우리는 마이그레이션만 단독으로 테스트하지 않습니다. CI가 새 앱 이미지를 스테이징 슬롯에 배포하고, 이를 새로 마이그레이션된 브랜치로 지정한 다음, 해당 쌍에 대해 전체 테스트 제품군을 실행합니다.
  5. 모든 PR은 실제 환경에 영향을 미치기 전에 마이그레이션과 새 애플리케이션 이미지를 함께 검증합니다.
  6. PR이 머지되면 CI가 이 작업을 다시 수행합니다. PR이 처음 통과한(green) 이후 상황이 바뀌었을 수 있으므로, 프로덕션에서 다시 브랜치를 생성하고, 마이그레이션을 다시 실행(replay)하며, 테스트를 다시 진행합니다. 마이그레이션이 성공하면 대상 브랜치에 그대로 적용됩니다. 첫 번째 머지의 경우, 해당 대상은 development입니다.
  7. acceptance 및 프로덕션으로의 승격(promotion)은 수동으로 승인된 게이트를 통해 이루어지며, 각 게이트는 대기 중인 다음 브랜치를 대상으로 마이그레이션을 다시 실행합니다.

git 중심의 환경에서 일하던 팀을 위해 한 가지 짚고 넘어갈 세부 사항이 있습니다. 저희는 git에 master 브랜치만 유지합니다. 소스 제어에 수명이 긴 develop 또는 release 브랜치는 존재하지 않습니다. dev에서 acceptance를 거쳐 프로덕션으로 가는 승격 경로는 전적으로 Azure DevOps의 수동 승인 게이트를 통해 표현되며, 이 게이트가 CI를 시작하고 변경 사항을 한 브랜치에서 다음 브랜치로 이동시킵니다. 환경 토폴로지는 Lakebase와 파이프라인에 존재하므로 git은 단순하게 유지됩니다.

마이그레이션 재실행이 단일 진실 공급원(source of truth)인 이유

이 모든 과정을 안전하게 만드는 비결은 데이터베이스를 서로 다시 머지하지 않는다는 점입니다. 저희는 피처(feature) 브랜치에서 development로 데이터를 복사하여 변경 사항을 승격하지 않습니다. 대신 대상 브랜치에 대해 마이그레이션을 재실행하여 승격합니다. 스테이징 슬롯에 대한 스모크 테스트를 실행하기 위해 vitest를 사용합니다.

이 모델에서 브랜치는 의도적으로 수명이 짧고 일회성입니다. 스키마가 어떻게 구성되어야 하는지에 대한 지속적이고 권위 있는 정의는 변경 사항이 누적되어 일탈했을 수 있는 장기 실행 데이터베이스가 아닙니다. 바로 순서가 지정된 마이그레이션 세트입니다. 그렇기 때문에 무언가를 놓쳤을 때도 리셋이 안전합니다. 새로 고침된 브랜치에 변경 사항이 누락된 경우, 다음 마이그레이션 재실행 시 다시 적용되기 때문입니다. 마이그레이션은 매우 핵심적인 역할을 하므로, 실서비스 데이터베이스는 마이그레이션 덕분에 소중하게 보존되기보다는 언제나 마이그레이션을 통해 재현 가능합니다.

이것이 진정한 돌파구입니다. 프로덕션에서 브랜치를 생성하면 데이터가 최신 상태로 유지됩니다. 마이그레이션을 단일 진실 공급원(source of truth)으로 취급하면 스키마가 올바르게 유지됩니다. 이 두 가지가 결합되면 그 어떤 환경도 버리고 새로 빌드하기에 너무 특별하지 않게 됩니다.

분기점: 두 가지 승격 모델

이 부분은 하나의 정답만 있는 모범 사례가 아니라 실제적인 분기점이기 때문에 다른 팀에 가장 유용할 것이라 생각합니다. 위의 토폴로지를 갖추고 나면, 언제 PR 머지를 허용할지 결정해야 합니다. 저희는 두 가지 모델을 평가했으며, 이들은 서로 반대 방향으로 트레이드오프 관계에 있습니다.

옵션 1: CI 통과 후 머지 (PR 스태킹)옵션 2: dev 및 acceptance로 CD 완료 후 머지 (PR별 승격)
작동 방식해당 pr-xxxx 브랜치의 CI가 통과(green)하는 즉시 PR을 머지할 수 있습니다.PR이 development 및 acceptance를 거쳐 승격될 때까지는 머지할 수 없습니다.
장점팀원들이 서로의 변경 사항을 기반으로 즉시 작업을 이어 나갈 수 있습니다.각 PR이 자체적인 속도로 진행됩니다. 핫픽스가 최우선으로 처리되며 다른 작업 뒤에 대기하지 않습니다.
단점머지된 여러 PR이 dev 및 acceptance로 승격될 때 함께 쌓이게(stack) 됩니다. 병렬 파이프라인 없이는 핫픽스가 대기열을 우회할 수 없습니다.팀원들이 변경 사항을 기반으로 작업을 시작하려면 acceptance가 통과될 때까지 기다려야 합니다.

요약하자면, 옵션 1은 긴밀하게 협업하는 팀 내에서 개발자 속도를 최적화합니다. 옵션 2는 독립성과 깔끔한 핫픽스 경로를 최적화합니다. 팀이 겹치는 변경 사항을 작업하고 CI를 신뢰한다면 스태킹을 통해 가장 빠르게 진행할 수 있습니다. 진행 중인 다른 작업 뒤에서 기다릴 수 없는 수정 사항을 자주 배포해야 한다면 PR별 승격 방식이 그 가치를 발휘합니다.

저희는 옵션 1을 선택했습니다. 저희는 겹치는 변경 사항을 작업하는 소규모 팀입니다. 누군가 내 작업을 기반으로 빌드하기 전에 acceptance가 통과되기를 기다리는 것은 매일 지불해야 하는 비용인 반면, 스태킹 리스크는 가끔씩만 발생합니다. 그리고 리스크가 발생하더라도 통제 가능합니다. 모든 승격 게이트는 실제 브랜치를 건드리기 전에 프로덕션의 새로운 포크에 대해 쌓인 마이그레이션을 다시 실행하므로, 스택이 하나의 단위로 검증됩니다. 정말 긴급한 상황을 위해 동일한 사전 검사를 거쳐 프로덕션으로 바로 이동하는 별도의 비상 파이프라인을 유지하고 있습니다. 기본적으로는 속도를 지향하되 비상 탈출구를 대기시켜 두는 방식입니다.

사이드바: 진행 과정에서 발생한 문제들

작업 세션에서 몇 가지 주제가 반복해서 언급되었습니다. 이야기의 핵심은 아니지만, 이 패턴이 실제 프로덕션 환경에서 살아남을 수 있을지를 결정하는 세부 사항들입니다.

데이터베이스 인증 및 토큰 로테이션. 앱은 수명이 긴 데이터베이스 보안 비밀(secret)을 보유하지 않습니다. Azure Key Vault의 정적 비밀번호를 사용하여 `databricks postgres generate-database-credential`을 통해 수명이 짧은 데이터베이스 자격 증명(TTL이 60분인 토큰)을 발급합니다. 토큰은 한 시간 후에 만료되므로 클라이언트가 선제적으로 새로 고쳐야 합니다. 저희는 자격 증명을 캐싱하고 만료 전에 로테이션하는 커넥션 풀의 비동기 비밀번호 함수를 사용하여 이를 처리합니다.

권한 부여(Grants) 및 액세스 프로비저닝. 저희는 마이그레이션을 통해 테이블 DDL을 관리하지 않으며, 객체 수준 권한 부여(object-level grants)는 현재 코드가 아닌 수동으로 적용하고 있습니다. 이는 개선 목록에서 가장 명확한 항목입니다. 하지만 프로덕션에서 이러한 권한을 적용하고 프로덕션에서 다시 포크하여 환경 브랜치를 리셋하기 때문에, 하위 브랜치는 새로 고칠 때마다 이 권한을 자동으로 상속받습니다. 수동 권한 부여 단계는 각 하위 브랜치에서 반복되는 것이 아니라 프로덕션에서 한 번만 발생하므로, 현재로서는 감수할 만한 수준으로 드물게 발생합니다.

데이터베이스 액세스: 단일 애플리케이션 사용자. 저희는 최종 사용자별로 Postgres 역할을 프로비저닝하지 않습니다. 앱은 단일 애플리케이션 사용자를 통해 연결되며, 개별 사용자에 대한 권한 부여는 데이터베이스가 아닌 애플리케이션 레이어에서 이루어집니다. 이를 통해 프로비저닝을 단순하게 유지할 수 있지만, 사용자별 액세스 제어가 Postgres가 아닌 그 상위 레이어에서 강제된다는 트레이드오프가 있습니다.

Glaspoort가 얻은 성과

이전에는 전체 팀이 달라붙어 몇 달 동안 작업해야 했던 유스케이스를 이제는 소규모 팀이 며칠 만에 라이브로 배포할 수 있게 되었습니다. 반복 주기가 10배 빨라졌으며, 하나의 팀이 조직과 함께 성장하는 플랫폼에서 지속적으로 새로운 가치를 제공하고 있습니다.—Raymon Veldman, 비즈니스 컨트롤 및 IT 매니저

가장 눈에 띄는 변화는 불안감은 줄어들고 속도는 빨라졌다는 점입니다. 이제 모든 PR은 몇 분 만에 자체적인 프로덕션 형태의 데이터베이스를 갖게 되므로, 개발자들은 공유된 dev 및 acceptance 환경을 조율할 필요가 없어졌고 "누가 acceptance를 망가뜨렸지?"와 같은 대화는 완전히 사라졌습니다. 환경 브랜치는 항상 프로덕션의 하위 브랜치이므로, 부모 브랜치로부터 리셋하는 날을 다시는 겪지 않아도 됩니다. 하위 브랜치를 삭제하거나, 연결 문자열을 다시 연결하거나, 권한을 다시 적용하느라 오후 시간을 허비하는 일이 없어졌습니다. 마이그레이션은 모든 승격 단계에서 재실행되며, 실제 환경을 건드리기 전에 새로 마이그레이션된 브랜치에 맞춰 스테이징 슬롯에 새 앱 이미지를 배포하고 전체 테스트 도구를 실행하므로, 프로덕션에 도달하는 것은 이미 두 번 검증된 상태입니다. 그리고 긴급한 상황이 발생하더라도 핫픽스는 파이프라인을 우회하는 대신 다른 모든 작업과 마찬가지로 동일하게 안전한 경로를 거쳐 더 빠르게 이동합니다.

애플리케이션 데이터베이스로 Lakebase를 선택하고 동일한 CI/CD 문제로 고민하고 계신가요? 프로덕션에서 직접 분기된 수명이 긴 환경, 임시(ephemeral) PR별 브랜치, 그리고 단일 진실 공급원(source of truth)으로서의 마이그레이션부터 시작해 보세요. Databricks Lakebase에 대해 자세히 알아보기

(이 글은 AI의 도움을 받아 번역되었습니다. 원문이 궁금하시다면 여기를 클릭해 주세요)

최신 게시물을 이메일로 받아보세요

블로그를 구독하고 최신 게시물을 이메일로 받아보세요.