InfoGrab DocsInfoGrab Docs

업데이트 간 하위 호환성

요약

GitLab 배포는 여러 구성 요소로 나눌 수 있습니다. 어떤 의미에서 이러한 시나리오는 모두 일시적인 상태입니다. 이전 시그니처로 job이 큐에 추가되고 새 월간 릴리즈에서 실행되는 것이 괜찮은가요? 새 시그니처로 job이 큐에 추가되고 이전 월간 릴리즈에서 실행되는 것이 괜찮은가요?

GitLab 배포는 여러 구성 요소로 나눌 수 있습니다. GitLab 업데이트는 원자적으로 이루어지지 않습니다. 따라서 많은 구성 요소가 하위 호환성을 유지해야 합니다.

자주 발생하는 문제#

어떤 의미에서 이러한 시나리오는 모두 일시적인 상태입니다. 그러나 실제 운영 환경에서는 종종 수 시간 동안 지속될 수 있습니다. 따라서 영구적인 상태와 동일한 주의를 기울여 다루어야 합니다.

Sidekiq 워커 수정 시#

예를 들어 인수를 변경할 때:

  • 이전 시그니처로 job이 큐에 추가되고 새 월간 릴리즈에서 실행되는 것이 괜찮은가요?

  • 새 시그니처로 job이 큐에 추가되고 이전 월간 릴리즈에서 실행되는 것이 괜찮은가요?

새 Sidekiq 워커 추가 시#

Sidekiq 노드가 아직 업데이트되지 않아 이 job들이 수 시간 동안 실행되지 않는 것이 괜찮은가요?

JavaScript/Vue 수정 시#

Rails 코드 변경사항(Rails 컨트롤러, REST API, GraphQL API)이 이전 월간 릴리즈에서 머지되어 릴리즈된 경우, JavaScript는 문제없이 해당 Rails 코드에 요청을 보낼 수 있습니다.

Rails 코드 변경사항(Rails 컨트롤러, REST API, GraphQL API)이 아직 릴리즈되지 않은 경우, JavaScript는 해당 Rails 코드에 요청을 보낼 수 있지만 기본적으로 비활성화된 기능 플래그 뒤에 있거나 우아하게 실패할 수 있어야 합니다. 예를 들어 18.3에서 GraphQL 쿼리를 추가하는 경우, 기능 플래그 없이 프론트엔드에서 해당 쿼리를 사용하려면 18.4까지 기다려야 합니다.

기존 쿼리에 GraphQL 필드를 추가할 때는 @gl_introduced 지시어를 사용하여 우아하게 실패할 수 있습니다. REST API에 필드를 추가할 때는 응답에 새 필드가 없으면 이전 필드로 폴백하여 우아하게 실패할 수 있습니다.

사전 배포 마이그레이션 추가 시#

사전 배포 마이그레이션이 실행되었지만 웹, Sidekiq, API 노드가 이전 릴리즈를 실행 중인 것이 괜찮은가요?

사후 배포 마이그레이션 추가 시#

모든 GitLab 노드가 업데이트되었지만 사후 배포 마이그레이션이 며칠 후에야 실행되는 것이 괜찮은가요?

백그라운드 마이그레이션 추가 시#

모든 노드가 업데이트되고, 며칠 후 사후 배포 마이그레이션이 실행된 다음, 백그라운드 마이그레이션이 완료되는 데 일주일이 걸리는 것이 괜찮은가요?

Rails와 같은 의존성 업그레이드 시#

일부 노드는 새 Rails 버전을 사용하고, 일부 노드는 이전 Rails 버전을 사용하는 것이 괜찮은가요?

업데이트 과정 살펴보기#

업데이트 중 하위 호환성 문제는 종종 매우 미묘합니다. 따라서 다음 내용을 숙지하는 것이 좋습니다:

이러한 문제가 어떻게 발생하는지 설명하기 위해 다음 예시를 살펴보세요:

  • 🚢 새 버전

  • 🙂 이전 버전

이 예시에서는 한 달간의 릴리즈로 업데이트한다고 가정합니다. 하지만 코드는 얼마나 오래 하위 호환성을 유지해야 하나요?를 참조하세요.

업데이트 단계 PostgreSQL DB 웹 노드 API 노드 Sidekiq 노드 호환성 우려 사항
초기 상태 🙂 🙂 🙂 🙂
사전 배포 마이그레이션 실행 🚢 (사후 배포 마이그레이션 제외) 🙂 🙂 🙂 🙂의 Rails 코드가 🚢에 DB 호출을 수행함
웹 노드 업데이트 🚢 (사후 배포 마이그레이션 제외) 🚢 🙂 🙂 🚢의 JavaScript가 🙂에 API 호출을 수행함. 🚢의 Rails 코드가 🙂의 Sidekiq 노드에서 실행되는 job을 큐에 추가함
API 및 Sidekiq 노드 업데이트 🚢 (사후 배포 마이그레이션 제외) 🚢 🚢 🚢 🚢의 Rails 코드가 사후 배포 마이그레이션이나 백그라운드 마이그레이션 없이 DB 호출을 수행함
사후 배포 마이그레이션 실행 🚢 🚢 🚢 🚢 🚢의 Rails 코드가 백그라운드 마이그레이션 없이 DB 호출을 수행함
백그라운드 마이그레이션 완료 🚢 🚢 🚢 🚢

이 예시는 완전하지 않습니다. GitLab은 다양한 방식으로 배포될 수 있습니다. 각 업데이트 단계도 원자적으로 이루어지지 않습니다. 예를 들어, 롤링 배포의 경우 그룹 내 노드가 일시적으로 서로 다른 버전을 사용하게 됩니다. 업데이트 단계 사이에 많은 시간이 소요된다고 가정해야 합니다. 이는 GitLab.com에서 종종 사실입니다.

GitLab Next#

GitLab.com에는 프로덕션에 배포될 다음 버전을 실행하는 카나리 Stage가 있습니다. 이는 GitLab의 여러 버전을 오랜 시간 동안 실행한다는 것을 의미합니다.

다음 버전을 테스트하기 위해 소량의 트래픽을 카나리로 라우팅합니다. 사용자는 gitlab_canary 쿠키를 true로 설정하여 카나리로 라우팅되는 GitLab Next에 참여할 수도 있습니다. 사용자 메뉴 토글(오른쪽 상단 아바타)과 g x 키보드 단축키 모두 .gitlab.com 도메인에서 이 쿠키를 설정하므로 하위 도메인 전체에서 공유됩니다. next.gitlab.com에 방문하면 참여가 되지만, 해당 사이트에서 별도로 관리됩니다. 또한 gitlab-org 또는 gitlab-com으로 시작하는 경로를 카나리로 라우팅하며, 이는 카나리의 버전이 프로덕션에 배포될 때까지 수 시간 동안 지속될 수 있는 다양한 다중 버전 호환성 문제를 자주 노출시킵니다.

API 요청이 동일한 경로 접두사로 시작하지 않기 때문에 문제가 발생합니다. 따라서 최신 카나리 프론트엔드 코드에서 수행된 이러한 API 요청이 이전 메인 노드로 전달됩니다.

GraphQL 요청에서 이 상황이 어떻게 발생할 수 있는지 예시를 보겠습니다:

sequenceDiagram Client browser->>Canary node: GET /gitlab-org/gitlab/-/issues/1 Canary node-->>Client browser: HTML page with canary JS Client browser->>Main node: POST /api/graphql with query including newFieldAddedInCanary Main node-->>Client browser: Returns error due to unrecognized field

사용자는 카나리 쿠키를 설정하여 두 요청 모두 카나리 노드로 전달되도록 이 문제를 해결할 수 있습니다. 그러나 이 해결 방법에 의존할 수 없으므로 코드가 하위 호환성을 유지해야 합니다.

코드는 얼마나 오래 하위 호환성을 유지해야 하나요?#

무중단 업데이트 지침을 따르는 사용자의 경우, 한 달간의 릴리즈가 답입니다. 예를 들어:

  • 13.11 => 13.12

  • 13.12 => 14.0

  • 14.0 => 14.1

GitLab.com의 경우, 하루에 여러 번 소규모 버전 업데이트가 이루어질 수 있으므로, GitLab.com은 변경 사항이 하위 호환성을 유지해야 하는 범위를 제한하지 않습니다.

많은 사용자가 일부 월간 릴리즈를 건너뜁니다. 예를 들어:

  • 13.0 => 13.12

이러한 사용자는 업데이트 중에 일부 다운타임을 수용합니다. 안타깝게도 이 경우를 완전히 무시할 수는 없습니다. 예를 들어, 13.12는 13.0의 Sidekiq job을 실행할 수 있으며, 이는 메이저 릴리즈까지 job에서 인수를 제거하지 않는 이유를 설명합니다. 핵심 질문은 다음과 같습니다: 업데이트가 완료된 후 배포가 정상 상태에 도달할 것인가?

GitLab을 어떤 구성 요소로 나눌 수 있나요?#

1000 RPS 또는 50,000명 사용자 레퍼런스 아키텍처는 GitLab을 48개 이상의 노드에서 실행합니다. GitLab.com은 그보다 규모가 크며, 인프라 일부는 쿠버네티스에서 실행되고, 업데이트를 먼저 받는 "카나리" Stage도 있습니다.

하지만 문제는 단순히 노드가 많다는 것이 아닙니다. 더 큰 문제는 배포가 서로 다른 컨텍스트로 나뉠 수 있다는 것입니다. 그리고 GitLab.com만 이렇게 하는 것이 아닙니다. 가능한 분류는 다음과 같습니다:

  • "카나리 웹 앱 노드": 일부 사용자로부터 비-API 요청을 처리

  • "Git 앱 노드": Git 요청 처리

  • "웹 앱 노드": 웹 요청 처리

  • "API 앱 노드": API 요청 처리

  • "Sidekiq 앱 노드": Sidekiq job 처리

  • "PostgreSQL 데이터베이스": 내부 PostgreSQL 호출 처리

  • "Redis 데이터베이스": 내부 Redis 호출 처리

  • "Gitaly 노드": 내부 Gitaly 호출 처리

업데이트 중에는 서로 다른 컨텍스트에서 두 가지 버전의 GitLab이 실행됩니다. 예를 들어, 웹 노드가 이전 Sidekiq 노드에서 실행되는 job을 큐에 추가할 수 있습니다.

업데이트 단계의 순서가 중요한가요?#

네! 무중단 업데이트에 대한 구체적인 지침이 있습니다. 이는 일부 호환성 순열을 무시할 수 있게 해줍니다. 그래서 Rails 코드가 이전 PostgreSQL 데이터베이스 스키마에 DB 호출을 수행하는 것에 대해 걱정하지 않아도 됩니다.

잠재적인 하위 호환성 문제를 발견했다면 어떻게 해야 하나요?#

조율#

Rails 또는 Puma의 메이저 또는 마이너 버전 업데이트의 경우:

  • MR을 철저히 테스트하기 위해 Quality 팀에 참여를 요청하세요.

  • 머지하기 전에 MR에서 @gitlab-org/release/managers에 알리세요.

기능 플래그#

기능 플래그는 하위 호환성 문제를 처리하기 위한 전략이 아닌 도구입니다.

예를 들어, 프론트엔드와 API 변경 사항이 모두 기본적으로 비활성화된 경우 두 가지 모두 포함한 새 기능을 안전하게 추가할 수 있습니다. 여러 머지 리퀘스트를 사용하여 임의의 순서로 머지할 수 있습니다. 모든 변경 사항이 GitLab.com에 배포된 후, ChatOps에서 기능을 활성화하고 GitLab.com에서 검증할 수 있습니다.

그러나 기본적으로 기능을 활성화하는 것이 반드시 안전한 것은 아닙니다. 기능 플래그가 제거되거나 기본값이 활성화로 전환된 것이 코드가 머지된 동일한 릴리즈에서 이루어진 경우, 무중단 업데이트를 수행하는 고객은 이전 릴리즈의 API에 대해 새 프론트엔드 코드를 실행하게 됩니다.

모든 변경 사항을 한 번에 활성화하는 것이 안전한지 확실하지 않다면, 한 가지 옵션은 현재 릴리즈에서 API를 활성화하고 다음 릴리즈에서 프론트엔드 변경 사항을 활성화하는 것입니다. 이것은 확장 및 축소 패턴의 예입니다.

또는 프론트엔드를 이전 릴리즈의 API에 대해 우아하게 저하되도록 수정하여 릴리즈 지연을 피할 수도 있습니다.

우아한 저하#

예를 들어, 프론트엔드와 API 변경 사항으로 새 기능을 추가할 때, 새 기능이 이전 API 응답에 대해 우아하게 저하되도록 프론트엔드를 작성할 수 있습니다. 이렇게 하면 변경 사항을 3번의 릴리즈에 걸쳐 분산해야 하는 필요성을 피하는 데 도움이 될 수 있습니다.

확장 및 축소 패턴#

온프레미스 인스턴스의 무중단 업데이트를 보장하는 한 가지 방법은 확장 및 축소 패턴을 따르는 것입니다.

이는 모든 호환성이 깨지는 변경 사항을 세 단계로 분리한다는 것을 의미합니다: 확장, 마이그레이션, 축소.

  • 확장: 소프트웨어의 하위 호환성을 유지하면서 호환성이 깨지는 변경 사항이 도입됩니다.

  • 마이그레이션: 모든 소비자가 새로운 구현을 사용하도록 업데이트됩니다.

  • 축소: 하위 호환성이 제거됩니다.

무중단 업데이트를 허용하기 위해 이 세 단계는 반드시 서로 다른 마일스톤의 일부여야 합니다.

기능의 지원 수준에 따라 축소 단계는 다음 메이저 릴리즈까지 지연될 수 있습니다.

확장 및 축소 예시#

라우트 변경, Sidekiq 워커 파라미터 변경, 데이터베이스 마이그레이션은 모두 호환성이 깨지는 변경 사항의 완벽한 예입니다. 이것들을 안전하게 처리하는 방법을 살펴보겠습니다.

라우트 변경#

라우팅을 변경할 때는 새 버전에서 생성된 라우트가 이전 버전에서 서비스될 수 있고 그 반대도 가능한지 확인해야 합니다. 보시다시피, 이를 하지 않으면 장애로 이어질 수 있습니다. 이 유형의 변경 사항은 두 구현 간의 즉각적인 전환처럼 보일 수 있습니다. 그러나 특히 카나리 Stage의 경우, 두 버전의 코드가 프로덕션에 공존하는 기간이 연장됩니다.

  • 확장: 이전 라우트와 동일한 컨트롤러를 가리키는 새 라우트가 추가됩니다. 하지만 애플리케이션에서 새 라우트에 대한 링크를 생성하는 것은 없습니다.

  • 마이그레이션: 플릿의 모든 머신이 새 라우트를 이해할 수 있게 되면 새 라우팅으로 링크를 생성할 수 있습니다.

  • 축소: 이전 라우트를 안전하게 제거할 수 있습니다. (이전 라우트가 리포지터리 파일 링크처럼 광범위하게 공유될 가능성이 있는 경우, 리다이렉트를 추가하고 이전 라우트를 더 오래 유지하는 것이 좋습니다.)

Sidekiq 워커 파라미터 변경#

이 주제는 업데이트 간 Sidekiq 호환성에서 자세히 설명합니다.

Sidekiq 워커 클래스에 새 파라미터를 추가해야 할 때, 다음 단계로 분리할 수 있습니다:

  • 확장: 워커 클래스가 기본값이 있는 새 파라미터를 추가합니다.

  • 마이그레이션: 워커의 모든 호출에 새 파라미터를 추가합니다.

  • 축소: 기본값을 제거합니다.

처음에는 확장과 마이그레이션을 단일 마일스톤으로 묶는 것이 안전해 보일 수 있지만, Sidekiq 이전에 Puma가 재시작되면 장애가 발생합니다. Puma가 이전 Sidekiq이 처리할 수 없는 추가 파라미터와 함께 job을 큐에 추가합니다.

데이터베이스 마이그레이션#

다음 그래프는 배포의 단순화된 시각적 표현으로, 확장 및 축소가 마이그레이션 전략에서 어떻게 구현되는지 이해하는 데 도움이 됩니다.

특별한 고려사항이 있습니다. 사후 배포 마이그레이션 프레임워크를 사용하면 세 단계 모두를 하나의 마일스톤으로 묶을 수 있습니다.

gantt title Deployment dateFormat HH:mm

section Deploy box Run migrations :done, migr, after schemaA, 2m Run post-deployment migrations :postmigr, after mcvn , 2m

section Database Schema A :done, schemaA, 00:00 , 1h Schema B :crit, schemaB, after migr, 58m Schema C. : schemaC, after postmigr, 1h

section Machine A Version N :done, mavn, 00:00 , 75m Version N+1 : after mavn, 105m

section Machine B Version N :done, mbvn, 00:00 , 105m Version N+1 : mbdone, after mbvn, 75m

section Machine C Version N :done, mcvn, 00:00 , 2h Version N+1 : mbcdone, after mcvn, 1h

이 스키마를 데이터베이스 관점에서 보면, 두 배포가 단일 GitLab 배포로 연결됩니다:

  • Schema A에서 Schema B

  • Schema B에서 Schema C

그리고 이러한 배포는 애플리케이션 변경과 완벽하게 일치합니다.

  • 처음에는 Schema AVersion N이 있습니다.

  • 그런 다음 Schema BVersion NVersion N+1이 함께 있는 긴 전환 기간이 있습니다.

  • Schema BVersion N+1만 있을 때 스키마가 다시 변경됩니다.

  • 마지막으로 Schema CVersion N+1이 있습니다.

이러한 모든 세부 사항을 염두에 두고, 쿼리를 교체해야 하고 이 쿼리에 지원 인덱스가 있는 경우를 상상해 보겠습니다.

  • 확장: 이것은 Schema A에서 Schema B로의 배포입니다. 새 인덱스를 추가하지만 애플리케이션은 아직 이를 무시합니다.

  • 마이그레이션: 이것은 Version N에서 Version N+1로의 애플리케이션 배포입니다. 새 코드가 배포되고 이 시점에서 새 쿼리만 실행됩니다.

  • 축소: Schema B에서 Schema C로 (사후 배포 마이그레이션). 더 이상 이전 인덱스를 사용하는 것이 없으므로 안전하게 제거할 수 있습니다.

이것은 하나의 예시일 뿐입니다. 특히 백그라운드 마이그레이션이 필요한 더 복잡한 마이그레이션은 하나 이상의 마일스톤이 필요할 수 있습니다. 자세한 내용은 마이그레이션 스타일 가이드를 참조하세요.

이전 인시던트 예시#

이슈 및 MR의 일부 링크가 끊어짐#

MR 라우트를 이동했을 때, 새 서버의 사용자는 새 URL로 리다이렉트되었습니다. 이 사용자들이 Markdown(또는 다른 곳)에 새 URL을 공유하면, 이전 서버의 사용자에게는 끊어진 링크가 되었습니다.

자세한 내용은 관련 이슈를 참조하세요.

이슈 또는 머지 리퀘스트 설명 및 댓글의 오래된 캐시#

Markdown 캐시 버전을 업데이트했을 때, 사용자가 다른 Markdown 캐시 버전에서 생성된 설명이나 댓글을 편집할 때 버그를 발견했습니다. 저장 후 캐시된 HTML이 제대로 생성되지 않았습니다. 대부분의 경우 이런 일이 발생하지 않았을 것입니다. 사용자가 편집을 선택하기 전에 Markdown을 봤을 것이고 그러면 Markdown 캐시가 갱신되었을 것이기 때문입니다. 그러나 혼합 버전을 실행하기 때문에 이 상황이 더 발생하기 쉬워집니다. 다른 버전의 다른 사용자가 같은 페이지를 보고 뒤에서 캐시를 다른 버전으로 갱신할 수 있습니다.

자세한 내용은 관련 이슈를 참조하세요.

프로젝트 서비스 템플릿이 잘못 복사됨#

서비스가 템플릿인지 여부를 나타내는 칼럼을 변경했습니다. 서비스를 생성할 때 템플릿에서 속성을 복사하고 이 칼럼을 false로 설정합니다. 이전 서버는 여전히 이전 칼럼을 업데이트하고 있었지만 이전 칼럼에서 새 칼럼을 업데이트하는 DB 트리거가 있었으므로 괜찮았습니다. 하지만 새 서버의 경우 새 칼럼만 업데이트하고 있었고 그 동일한 트리거가 이제 역으로 작동하여 잘못된 값으로 되돌리고 있었습니다.

자세한 내용은 관련 이슈를 참조하세요.

일부 사용자에게 사이드바가 로드되지 않음#

하나의 GraphQL 필드의 데이터 유형을 변경했습니다. 사용자가 새 서버에서 이슈 페이지를 열고 GraphQL AJAX 요청이 이전 서버로 전달될 때, 유형 불일치가 발생하여 사이드바가 로드되지 않는 JavaScript 오류가 발생했습니다.

자세한 내용은 관련 이슈를 참조하세요.

CI 아티팩트 업로드 실패#

칼럼에 NOT NULL 제약 조건을 추가하고 기존 행에 적용되지 않도록 NOT VALID 제약 조건으로 표시했습니다. 그러나 이렇게 했음에도 이전 서버가 여전히 null 값으로 새 행을 삽입하고 있었기 때문에 여전히 문제가 되었습니다.

자세한 내용은 관련 이슈를 참조하세요.

카나리 배포와 프로덕션 배포 사이에서 릴리즈 기능의 다운타임#

이 문제를 해결하기 위해 기본값을 지정하지 않고 NOT NULL 제약 조건이 있는 기존 테이블에 새 칼럼을 추가했습니다. 즉, 애플리케이션이 칼럼에 값을 설정해야 합니다.

이전 버전의 애플리케이션은 해당 엔티티/개념이 이전에 존재하지 않았기 때문에 NOT NULL 제약 조건을 설정하지 않았습니다.

문제는 카나리 배포가 완료된 직후에 시작됩니다. 그 시점에서 데이터베이스 마이그레이션(칼럼 추가)이 성공적으로 실행되고 카나리 인스턴스가 새 애플리케이션 코드를 사용하기 시작하여 QA가 성공했습니다. 안타깝게도 프로덕션 인스턴스는 여전히 이전 코드를 사용하고 있었으므로 새 릴리즈 항목 삽입에 실패하기 시작했습니다.

자세한 내용은 Releases API 관련 이슈를 참조하세요.

노드 유형별 다양한 배포 시간으로 인한 빌드 실패#

프로덕션 이슈에서 parallel 키워드를 사용하고 CI_NODE_TOTAL이 정수인 것에 의존하는 CI 빌드가 실패했습니다. 이는 사용자가 커밋을 푸시한 후 다음과 같은 이유로 발생했습니다:

  • 새 코드: Sidekiq이 새 파이프라인과 새 빌드를 생성했습니다. build.options[:parallel]Hash입니다.

  • 이전 코드: Runner가 이전 버전을 실행 중인 API 노드에서 job을 요청했습니다.

  • 결과적으로 새 코드가 API 서버에서 실행되지 않았습니다. 이전 API 서버가 CI_NODE_TOTAL CI/CD 변수를 반환하려 했지만 정수 값(예: 9) 대신 직렬화된 Hash 값({:number=>9, :total=>9})을 전송했기 때문에 Runner의 요청이 실패했습니다.

배포 파이프라인을 보면 모든 노드가 병렬로 업데이트된 것을 알 수 있습니다:

[

](/19.1/development/img/deployment_pipeline_v13_3.png)

그러나 업데이트가 비슷한 시간에 시작되었음에도 완료 시간은 크게 달랐습니다:

노드 유형 소요 시간 (분)
API 54
Sidekiq 21
K8S 8

parallel 키워드를 사용하고 CI_NODE_TOTALCI_NODE_INDEX에 의존하는 빌드는 Sidekiq이 업데이트된 후부터 실패했습니다. 쿠버네티스(K8S)도 Sidekiq Pod를 실행하기 때문에 기간은 최대 46분 또는 최소 33분이었을 수 있습니다. 어느 쪽이든, 배포가 완료된 후에 활성화하는 기능 플래그가 있었다면 이를 방지할 수 있었을 것입니다.

업데이트 간 하위 호환성

GitLab v19.1
원문 보기
요약

GitLab 배포는 여러 구성 요소로 나눌 수 있습니다. 어떤 의미에서 이러한 시나리오는 모두 일시적인 상태입니다. 이전 시그니처로 job이 큐에 추가되고 새 월간 릴리즈에서 실행되는 것이 괜찮은가요? 새 시그니처로 job이 큐에 추가되고 이전 월간 릴리즈에서 실행되는 것이 괜찮은가요?

GitLab 배포는 여러 구성 요소로 나눌 수 있습니다. GitLab 업데이트는 원자적으로 이루어지지 않습니다. 따라서 많은 구성 요소가 하위 호환성을 유지해야 합니다.

자주 발생하는 문제#

어떤 의미에서 이러한 시나리오는 모두 일시적인 상태입니다. 그러나 실제 운영 환경에서는 종종 수 시간 동안 지속될 수 있습니다. 따라서 영구적인 상태와 동일한 주의를 기울여 다루어야 합니다.

Sidekiq 워커 수정 시#

예를 들어 인수를 변경할 때:

  • 이전 시그니처로 job이 큐에 추가되고 새 월간 릴리즈에서 실행되는 것이 괜찮은가요?

  • 새 시그니처로 job이 큐에 추가되고 이전 월간 릴리즈에서 실행되는 것이 괜찮은가요?

새 Sidekiq 워커 추가 시#

Sidekiq 노드가 아직 업데이트되지 않아 이 job들이 수 시간 동안 실행되지 않는 것이 괜찮은가요?

JavaScript/Vue 수정 시#

Rails 코드 변경사항(Rails 컨트롤러, REST API, GraphQL API)이 이전 월간 릴리즈에서 머지되어 릴리즈된 경우, JavaScript는 문제없이 해당 Rails 코드에 요청을 보낼 수 있습니다.

Rails 코드 변경사항(Rails 컨트롤러, REST API, GraphQL API)이 아직 릴리즈되지 않은 경우, JavaScript는 해당 Rails 코드에 요청을 보낼 수 있지만 기본적으로 비활성화된 기능 플래그 뒤에 있거나 우아하게 실패할 수 있어야 합니다. 예를 들어 18.3에서 GraphQL 쿼리를 추가하는 경우, 기능 플래그 없이 프론트엔드에서 해당 쿼리를 사용하려면 18.4까지 기다려야 합니다.

기존 쿼리에 GraphQL 필드를 추가할 때는 @gl_introduced 지시어를 사용하여 우아하게 실패할 수 있습니다. REST API에 필드를 추가할 때는 응답에 새 필드가 없으면 이전 필드로 폴백하여 우아하게 실패할 수 있습니다.

사전 배포 마이그레이션 추가 시#

사전 배포 마이그레이션이 실행되었지만 웹, Sidekiq, API 노드가 이전 릴리즈를 실행 중인 것이 괜찮은가요?

사후 배포 마이그레이션 추가 시#

모든 GitLab 노드가 업데이트되었지만 사후 배포 마이그레이션이 며칠 후에야 실행되는 것이 괜찮은가요?

백그라운드 마이그레이션 추가 시#

모든 노드가 업데이트되고, 며칠 후 사후 배포 마이그레이션이 실행된 다음, 백그라운드 마이그레이션이 완료되는 데 일주일이 걸리는 것이 괜찮은가요?

Rails와 같은 의존성 업그레이드 시#

일부 노드는 새 Rails 버전을 사용하고, 일부 노드는 이전 Rails 버전을 사용하는 것이 괜찮은가요?

업데이트 과정 살펴보기#

업데이트 중 하위 호환성 문제는 종종 매우 미묘합니다. 따라서 다음 내용을 숙지하는 것이 좋습니다:

이러한 문제가 어떻게 발생하는지 설명하기 위해 다음 예시를 살펴보세요:

  • 🚢 새 버전

  • 🙂 이전 버전

이 예시에서는 한 달간의 릴리즈로 업데이트한다고 가정합니다. 하지만 코드는 얼마나 오래 하위 호환성을 유지해야 하나요?를 참조하세요.

업데이트 단계 PostgreSQL DB 웹 노드 API 노드 Sidekiq 노드 호환성 우려 사항
초기 상태 🙂 🙂 🙂 🙂
사전 배포 마이그레이션 실행 🚢 (사후 배포 마이그레이션 제외) 🙂 🙂 🙂 🙂의 Rails 코드가 🚢에 DB 호출을 수행함
웹 노드 업데이트 🚢 (사후 배포 마이그레이션 제외) 🚢 🙂 🙂 🚢의 JavaScript가 🙂에 API 호출을 수행함. 🚢의 Rails 코드가 🙂의 Sidekiq 노드에서 실행되는 job을 큐에 추가함
API 및 Sidekiq 노드 업데이트 🚢 (사후 배포 마이그레이션 제외) 🚢 🚢 🚢 🚢의 Rails 코드가 사후 배포 마이그레이션이나 백그라운드 마이그레이션 없이 DB 호출을 수행함
사후 배포 마이그레이션 실행 🚢 🚢 🚢 🚢 🚢의 Rails 코드가 백그라운드 마이그레이션 없이 DB 호출을 수행함
백그라운드 마이그레이션 완료 🚢 🚢 🚢 🚢

이 예시는 완전하지 않습니다. GitLab은 다양한 방식으로 배포될 수 있습니다. 각 업데이트 단계도 원자적으로 이루어지지 않습니다. 예를 들어, 롤링 배포의 경우 그룹 내 노드가 일시적으로 서로 다른 버전을 사용하게 됩니다. 업데이트 단계 사이에 많은 시간이 소요된다고 가정해야 합니다. 이는 GitLab.com에서 종종 사실입니다.

GitLab Next#

GitLab.com에는 프로덕션에 배포될 다음 버전을 실행하는 카나리 Stage가 있습니다. 이는 GitLab의 여러 버전을 오랜 시간 동안 실행한다는 것을 의미합니다.

다음 버전을 테스트하기 위해 소량의 트래픽을 카나리로 라우팅합니다. 사용자는 gitlab_canary 쿠키를 true로 설정하여 카나리로 라우팅되는 GitLab Next에 참여할 수도 있습니다. 사용자 메뉴 토글(오른쪽 상단 아바타)과 g x 키보드 단축키 모두 .gitlab.com 도메인에서 이 쿠키를 설정하므로 하위 도메인 전체에서 공유됩니다. next.gitlab.com에 방문하면 참여가 되지만, 해당 사이트에서 별도로 관리됩니다. 또한 gitlab-org 또는 gitlab-com으로 시작하는 경로를 카나리로 라우팅하며, 이는 카나리의 버전이 프로덕션에 배포될 때까지 수 시간 동안 지속될 수 있는 다양한 다중 버전 호환성 문제를 자주 노출시킵니다.

API 요청이 동일한 경로 접두사로 시작하지 않기 때문에 문제가 발생합니다. 따라서 최신 카나리 프론트엔드 코드에서 수행된 이러한 API 요청이 이전 메인 노드로 전달됩니다.

GraphQL 요청에서 이 상황이 어떻게 발생할 수 있는지 예시를 보겠습니다:

sequenceDiagram Client browser->>Canary node: GET /gitlab-org/gitlab/-/issues/1 Canary node-->>Client browser: HTML page with canary JS Client browser->>Main node: POST /api/graphql with query including newFieldAddedInCanary Main node-->>Client browser: Returns error due to unrecognized field

사용자는 카나리 쿠키를 설정하여 두 요청 모두 카나리 노드로 전달되도록 이 문제를 해결할 수 있습니다. 그러나 이 해결 방법에 의존할 수 없으므로 코드가 하위 호환성을 유지해야 합니다.

코드는 얼마나 오래 하위 호환성을 유지해야 하나요?#

무중단 업데이트 지침을 따르는 사용자의 경우, 한 달간의 릴리즈가 답입니다. 예를 들어:

  • 13.11 => 13.12

  • 13.12 => 14.0

  • 14.0 => 14.1

GitLab.com의 경우, 하루에 여러 번 소규모 버전 업데이트가 이루어질 수 있으므로, GitLab.com은 변경 사항이 하위 호환성을 유지해야 하는 범위를 제한하지 않습니다.

많은 사용자가 일부 월간 릴리즈를 건너뜁니다. 예를 들어:

  • 13.0 => 13.12

이러한 사용자는 업데이트 중에 일부 다운타임을 수용합니다. 안타깝게도 이 경우를 완전히 무시할 수는 없습니다. 예를 들어, 13.12는 13.0의 Sidekiq job을 실행할 수 있으며, 이는 메이저 릴리즈까지 job에서 인수를 제거하지 않는 이유를 설명합니다. 핵심 질문은 다음과 같습니다: 업데이트가 완료된 후 배포가 정상 상태에 도달할 것인가?

GitLab을 어떤 구성 요소로 나눌 수 있나요?#

1000 RPS 또는 50,000명 사용자 레퍼런스 아키텍처는 GitLab을 48개 이상의 노드에서 실행합니다. GitLab.com은 그보다 규모가 크며, 인프라 일부는 쿠버네티스에서 실행되고, 업데이트를 먼저 받는 "카나리" Stage도 있습니다.

하지만 문제는 단순히 노드가 많다는 것이 아닙니다. 더 큰 문제는 배포가 서로 다른 컨텍스트로 나뉠 수 있다는 것입니다. 그리고 GitLab.com만 이렇게 하는 것이 아닙니다. 가능한 분류는 다음과 같습니다:

  • "카나리 웹 앱 노드": 일부 사용자로부터 비-API 요청을 처리

  • "Git 앱 노드": Git 요청 처리

  • "웹 앱 노드": 웹 요청 처리

  • "API 앱 노드": API 요청 처리

  • "Sidekiq 앱 노드": Sidekiq job 처리

  • "PostgreSQL 데이터베이스": 내부 PostgreSQL 호출 처리

  • "Redis 데이터베이스": 내부 Redis 호출 처리

  • "Gitaly 노드": 내부 Gitaly 호출 처리

업데이트 중에는 서로 다른 컨텍스트에서 두 가지 버전의 GitLab이 실행됩니다. 예를 들어, 웹 노드가 이전 Sidekiq 노드에서 실행되는 job을 큐에 추가할 수 있습니다.

업데이트 단계의 순서가 중요한가요?#

네! 무중단 업데이트에 대한 구체적인 지침이 있습니다. 이는 일부 호환성 순열을 무시할 수 있게 해줍니다. 그래서 Rails 코드가 이전 PostgreSQL 데이터베이스 스키마에 DB 호출을 수행하는 것에 대해 걱정하지 않아도 됩니다.

잠재적인 하위 호환성 문제를 발견했다면 어떻게 해야 하나요?#

조율#

Rails 또는 Puma의 메이저 또는 마이너 버전 업데이트의 경우:

  • MR을 철저히 테스트하기 위해 Quality 팀에 참여를 요청하세요.

  • 머지하기 전에 MR에서 @gitlab-org/release/managers에 알리세요.

기능 플래그#

기능 플래그는 하위 호환성 문제를 처리하기 위한 전략이 아닌 도구입니다.

예를 들어, 프론트엔드와 API 변경 사항이 모두 기본적으로 비활성화된 경우 두 가지 모두 포함한 새 기능을 안전하게 추가할 수 있습니다. 여러 머지 리퀘스트를 사용하여 임의의 순서로 머지할 수 있습니다. 모든 변경 사항이 GitLab.com에 배포된 후, ChatOps에서 기능을 활성화하고 GitLab.com에서 검증할 수 있습니다.

그러나 기본적으로 기능을 활성화하는 것이 반드시 안전한 것은 아닙니다. 기능 플래그가 제거되거나 기본값이 활성화로 전환된 것이 코드가 머지된 동일한 릴리즈에서 이루어진 경우, 무중단 업데이트를 수행하는 고객은 이전 릴리즈의 API에 대해 새 프론트엔드 코드를 실행하게 됩니다.

모든 변경 사항을 한 번에 활성화하는 것이 안전한지 확실하지 않다면, 한 가지 옵션은 현재 릴리즈에서 API를 활성화하고 다음 릴리즈에서 프론트엔드 변경 사항을 활성화하는 것입니다. 이것은 확장 및 축소 패턴의 예입니다.

또는 프론트엔드를 이전 릴리즈의 API에 대해 우아하게 저하되도록 수정하여 릴리즈 지연을 피할 수도 있습니다.

우아한 저하#

예를 들어, 프론트엔드와 API 변경 사항으로 새 기능을 추가할 때, 새 기능이 이전 API 응답에 대해 우아하게 저하되도록 프론트엔드를 작성할 수 있습니다. 이렇게 하면 변경 사항을 3번의 릴리즈에 걸쳐 분산해야 하는 필요성을 피하는 데 도움이 될 수 있습니다.

확장 및 축소 패턴#

온프레미스 인스턴스의 무중단 업데이트를 보장하는 한 가지 방법은 확장 및 축소 패턴을 따르는 것입니다.

이는 모든 호환성이 깨지는 변경 사항을 세 단계로 분리한다는 것을 의미합니다: 확장, 마이그레이션, 축소.

  • 확장: 소프트웨어의 하위 호환성을 유지하면서 호환성이 깨지는 변경 사항이 도입됩니다.

  • 마이그레이션: 모든 소비자가 새로운 구현을 사용하도록 업데이트됩니다.

  • 축소: 하위 호환성이 제거됩니다.

무중단 업데이트를 허용하기 위해 이 세 단계는 반드시 서로 다른 마일스톤의 일부여야 합니다.

기능의 지원 수준에 따라 축소 단계는 다음 메이저 릴리즈까지 지연될 수 있습니다.

확장 및 축소 예시#

라우트 변경, Sidekiq 워커 파라미터 변경, 데이터베이스 마이그레이션은 모두 호환성이 깨지는 변경 사항의 완벽한 예입니다. 이것들을 안전하게 처리하는 방법을 살펴보겠습니다.

라우트 변경#

라우팅을 변경할 때는 새 버전에서 생성된 라우트가 이전 버전에서 서비스될 수 있고 그 반대도 가능한지 확인해야 합니다. 보시다시피, 이를 하지 않으면 장애로 이어질 수 있습니다. 이 유형의 변경 사항은 두 구현 간의 즉각적인 전환처럼 보일 수 있습니다. 그러나 특히 카나리 Stage의 경우, 두 버전의 코드가 프로덕션에 공존하는 기간이 연장됩니다.

  • 확장: 이전 라우트와 동일한 컨트롤러를 가리키는 새 라우트가 추가됩니다. 하지만 애플리케이션에서 새 라우트에 대한 링크를 생성하는 것은 없습니다.

  • 마이그레이션: 플릿의 모든 머신이 새 라우트를 이해할 수 있게 되면 새 라우팅으로 링크를 생성할 수 있습니다.

  • 축소: 이전 라우트를 안전하게 제거할 수 있습니다. (이전 라우트가 리포지터리 파일 링크처럼 광범위하게 공유될 가능성이 있는 경우, 리다이렉트를 추가하고 이전 라우트를 더 오래 유지하는 것이 좋습니다.)

Sidekiq 워커 파라미터 변경#

이 주제는 업데이트 간 Sidekiq 호환성에서 자세히 설명합니다.

Sidekiq 워커 클래스에 새 파라미터를 추가해야 할 때, 다음 단계로 분리할 수 있습니다:

  • 확장: 워커 클래스가 기본값이 있는 새 파라미터를 추가합니다.

  • 마이그레이션: 워커의 모든 호출에 새 파라미터를 추가합니다.

  • 축소: 기본값을 제거합니다.

처음에는 확장과 마이그레이션을 단일 마일스톤으로 묶는 것이 안전해 보일 수 있지만, Sidekiq 이전에 Puma가 재시작되면 장애가 발생합니다. Puma가 이전 Sidekiq이 처리할 수 없는 추가 파라미터와 함께 job을 큐에 추가합니다.

데이터베이스 마이그레이션#

다음 그래프는 배포의 단순화된 시각적 표현으로, 확장 및 축소가 마이그레이션 전략에서 어떻게 구현되는지 이해하는 데 도움이 됩니다.

특별한 고려사항이 있습니다. 사후 배포 마이그레이션 프레임워크를 사용하면 세 단계 모두를 하나의 마일스톤으로 묶을 수 있습니다.

gantt title Deployment dateFormat HH:mm

section Deploy box Run migrations :done, migr, after schemaA, 2m Run post-deployment migrations :postmigr, after mcvn , 2m

section Database Schema A :done, schemaA, 00:00 , 1h Schema B :crit, schemaB, after migr, 58m Schema C. : schemaC, after postmigr, 1h

section Machine A Version N :done, mavn, 00:00 , 75m Version N+1 : after mavn, 105m

section Machine B Version N :done, mbvn, 00:00 , 105m Version N+1 : mbdone, after mbvn, 75m

section Machine C Version N :done, mcvn, 00:00 , 2h Version N+1 : mbcdone, after mcvn, 1h

이 스키마를 데이터베이스 관점에서 보면, 두 배포가 단일 GitLab 배포로 연결됩니다:

  • Schema A에서 Schema B

  • Schema B에서 Schema C

그리고 이러한 배포는 애플리케이션 변경과 완벽하게 일치합니다.

  • 처음에는 Schema AVersion N이 있습니다.

  • 그런 다음 Schema BVersion NVersion N+1이 함께 있는 긴 전환 기간이 있습니다.

  • Schema BVersion N+1만 있을 때 스키마가 다시 변경됩니다.

  • 마지막으로 Schema CVersion N+1이 있습니다.

이러한 모든 세부 사항을 염두에 두고, 쿼리를 교체해야 하고 이 쿼리에 지원 인덱스가 있는 경우를 상상해 보겠습니다.

  • 확장: 이것은 Schema A에서 Schema B로의 배포입니다. 새 인덱스를 추가하지만 애플리케이션은 아직 이를 무시합니다.

  • 마이그레이션: 이것은 Version N에서 Version N+1로의 애플리케이션 배포입니다. 새 코드가 배포되고 이 시점에서 새 쿼리만 실행됩니다.

  • 축소: Schema B에서 Schema C로 (사후 배포 마이그레이션). 더 이상 이전 인덱스를 사용하는 것이 없으므로 안전하게 제거할 수 있습니다.

이것은 하나의 예시일 뿐입니다. 특히 백그라운드 마이그레이션이 필요한 더 복잡한 마이그레이션은 하나 이상의 마일스톤이 필요할 수 있습니다. 자세한 내용은 마이그레이션 스타일 가이드를 참조하세요.

이전 인시던트 예시#

이슈 및 MR의 일부 링크가 끊어짐#

MR 라우트를 이동했을 때, 새 서버의 사용자는 새 URL로 리다이렉트되었습니다. 이 사용자들이 Markdown(또는 다른 곳)에 새 URL을 공유하면, 이전 서버의 사용자에게는 끊어진 링크가 되었습니다.

자세한 내용은 관련 이슈를 참조하세요.

이슈 또는 머지 리퀘스트 설명 및 댓글의 오래된 캐시#

Markdown 캐시 버전을 업데이트했을 때, 사용자가 다른 Markdown 캐시 버전에서 생성된 설명이나 댓글을 편집할 때 버그를 발견했습니다. 저장 후 캐시된 HTML이 제대로 생성되지 않았습니다. 대부분의 경우 이런 일이 발생하지 않았을 것입니다. 사용자가 편집을 선택하기 전에 Markdown을 봤을 것이고 그러면 Markdown 캐시가 갱신되었을 것이기 때문입니다. 그러나 혼합 버전을 실행하기 때문에 이 상황이 더 발생하기 쉬워집니다. 다른 버전의 다른 사용자가 같은 페이지를 보고 뒤에서 캐시를 다른 버전으로 갱신할 수 있습니다.

자세한 내용은 관련 이슈를 참조하세요.

프로젝트 서비스 템플릿이 잘못 복사됨#

서비스가 템플릿인지 여부를 나타내는 칼럼을 변경했습니다. 서비스를 생성할 때 템플릿에서 속성을 복사하고 이 칼럼을 false로 설정합니다. 이전 서버는 여전히 이전 칼럼을 업데이트하고 있었지만 이전 칼럼에서 새 칼럼을 업데이트하는 DB 트리거가 있었으므로 괜찮았습니다. 하지만 새 서버의 경우 새 칼럼만 업데이트하고 있었고 그 동일한 트리거가 이제 역으로 작동하여 잘못된 값으로 되돌리고 있었습니다.

자세한 내용은 관련 이슈를 참조하세요.

일부 사용자에게 사이드바가 로드되지 않음#

하나의 GraphQL 필드의 데이터 유형을 변경했습니다. 사용자가 새 서버에서 이슈 페이지를 열고 GraphQL AJAX 요청이 이전 서버로 전달될 때, 유형 불일치가 발생하여 사이드바가 로드되지 않는 JavaScript 오류가 발생했습니다.

자세한 내용은 관련 이슈를 참조하세요.

CI 아티팩트 업로드 실패#

칼럼에 NOT NULL 제약 조건을 추가하고 기존 행에 적용되지 않도록 NOT VALID 제약 조건으로 표시했습니다. 그러나 이렇게 했음에도 이전 서버가 여전히 null 값으로 새 행을 삽입하고 있었기 때문에 여전히 문제가 되었습니다.

자세한 내용은 관련 이슈를 참조하세요.

카나리 배포와 프로덕션 배포 사이에서 릴리즈 기능의 다운타임#

이 문제를 해결하기 위해 기본값을 지정하지 않고 NOT NULL 제약 조건이 있는 기존 테이블에 새 칼럼을 추가했습니다. 즉, 애플리케이션이 칼럼에 값을 설정해야 합니다.

이전 버전의 애플리케이션은 해당 엔티티/개념이 이전에 존재하지 않았기 때문에 NOT NULL 제약 조건을 설정하지 않았습니다.

문제는 카나리 배포가 완료된 직후에 시작됩니다. 그 시점에서 데이터베이스 마이그레이션(칼럼 추가)이 성공적으로 실행되고 카나리 인스턴스가 새 애플리케이션 코드를 사용하기 시작하여 QA가 성공했습니다. 안타깝게도 프로덕션 인스턴스는 여전히 이전 코드를 사용하고 있었으므로 새 릴리즈 항목 삽입에 실패하기 시작했습니다.

자세한 내용은 Releases API 관련 이슈를 참조하세요.

노드 유형별 다양한 배포 시간으로 인한 빌드 실패#

프로덕션 이슈에서 parallel 키워드를 사용하고 CI_NODE_TOTAL이 정수인 것에 의존하는 CI 빌드가 실패했습니다. 이는 사용자가 커밋을 푸시한 후 다음과 같은 이유로 발생했습니다:

  • 새 코드: Sidekiq이 새 파이프라인과 새 빌드를 생성했습니다. build.options[:parallel]Hash입니다.

  • 이전 코드: Runner가 이전 버전을 실행 중인 API 노드에서 job을 요청했습니다.

  • 결과적으로 새 코드가 API 서버에서 실행되지 않았습니다. 이전 API 서버가 CI_NODE_TOTAL CI/CD 변수를 반환하려 했지만 정수 값(예: 9) 대신 직렬화된 Hash 값({:number=>9, :total=>9})을 전송했기 때문에 Runner의 요청이 실패했습니다.

배포 파이프라인을 보면 모든 노드가 병렬로 업데이트된 것을 알 수 있습니다:

[

](/19.1/development/img/deployment_pipeline_v13_3.png)

그러나 업데이트가 비슷한 시간에 시작되었음에도 완료 시간은 크게 달랐습니다:

노드 유형 소요 시간 (분)
API 54
Sidekiq 21
K8S 8

parallel 키워드를 사용하고 CI_NODE_TOTALCI_NODE_INDEX에 의존하는 빌드는 Sidekiq이 업데이트된 후부터 실패했습니다. 쿠버네티스(K8S)도 Sidekiq Pod를 실행하기 때문에 기간은 최대 46분 또는 최소 33분이었을 수 있습니다. 어느 쪽이든, 배포가 완료된 후에 활성화하는 기능 플래그가 있었다면 이를 방지할 수 있었을 것입니다.