8분

분산 SQL: Spanner, CockroachDB, YugabyteDB를 선택할 때

분산 SQL의 비용을 정당화하는 경우, Spanner·CockroachDB·YugabyteDB의 차이, 안전한 멀티 리전 워크로드 계획 방법을 알아보세요.

분산 SQL: Spanner, CockroachDB, YugabyteDB를 선택할 때

분산 SQL이란 무엇인가

분산 SQL은 데이터를 여러 장비에 나누어 저장하고 트랜잭션을 처리하면서도, 애플리케이션에는 하나의 논리적 SQL 데이터베이스로 보이게 하는 관계형 데이터베이스 아키텍처입니다. 테이블, 조인, 인덱스, 제약 조건, ACID 트랜잭션을 유지하면서 자동 파티셔닝, 복제, 장애 복구를 더합니다.

일반적으로 다음 특성을 함께 갖추면 이 범주에 속합니다.

  • 관계형 스키마와 SQL 쿼리 인터페이스
  • 데이터베이스 노드 전체로의 수평 확장
  • 파티션 간 트랜잭션 일관성
  • 자동 복제와 장애 조치
  • 하나의 논리적 데이터베이스로 조정되어 동작

이 정의가 중요한 이유는 PostgreSQL이나 MySQL에 읽기 리플리카를 추가했다고 해서 분산 SQL이 되지는 않기 때문입니다. 프라이머리와 리플리카 구성에서는 쓰기가 여전히 하나의 주 서버를 거칩니다. 애플리케이션이 관리하는 샤딩은 쓰기를 분산하지만, 레코드가 어디에 있을지와 샤드 간 작업을 어떻게 처리할지를 애플리케이션이 정해야 합니다. 분산 SQL은 이 책임의 상당 부분을 데이터베이스로 옮깁니다.

일반적인 RDBMS와 NoSQL 사이의 위치

분산 SQL은 일반적인 RDBMS의 관계형 프로그래밍 모델과 분산 데이터 저장소의 스케일아웃 설계를 결합합니다. 기존 PostgreSQL과 MySQL 배포는 프라이머리 인스턴스 하나가 쓰기 부하를 처리할 수 있고, 리전 장애가 나도 다른 곳에서 계속 쓸 필요가 없다면 잘 작동합니다. 읽기 리플리카, 캐시, 커넥션 풀, 더 나은 인덱스로 이 모델을 오랫동안 확장할 수 있습니다.

많은 NoSQL 데이터베이스는 조인, 트랜잭션, 일관성 보장 일부를 제한해 분산을 쉽게 만들었습니다. 대규모 이벤트 스트림, 일시적인 캐시, 여러 행 트랜잭션에 거의 참여하지 않는 레코드 같은 워크로드에는 이런 선택이 여전히 적절합니다. 관계형 클러스터는 데이터가 노드 사이에 나뉘어도 제약 조건과 트랜잭션이 유효하게 유지되리라는 애플리케이션의 기대를 충족하기 위해 더 많은 조정을 맡습니다.

실질적인 차이는 복잡성을 누가 책임지느냐에 있습니다. 수동 샤딩에서는 애플리케이션 팀이 라우팅을 구현하고, 데이터를 재분배하며, 스키마 변경을 조정하고, 여러 샤드에 걸친 작업을 처리합니다. 분산 SQL에서는 데이터베이스가 이런 장치를 제공하지만, 엔지니어는 여전히 네트워크 환경을 고려해 스키마와 쿼리를 설계해야 합니다.

해결하려는 문제

분산 SQL은 가용성, 지리적 배치 또는 쓰기 증가가 단일 프라이머리 아키텍처의 한계를 넘은 애플리케이션을 위해 설계되었습니다. 대표적인 예는 글로벌 SaaS 서비스, 초과 판매가 허용되지 않는 예약 시스템, 노드 장애에도 불변 조건이 지켜져야 하는 금융 원장입니다.

애플리케이션 수준 샤딩의 필요성을 없애고 하나의 쓰기 위치에 대한 의존을 줄일 수 있습니다. 사용자 가까이 또는 승인된 관할권 안에 데이터를 둘 수도 있습니다. 대신 리플리카, 네트워크 트래픽, 조정 작업이 늘고 단일 서버에는 없는 장애 양상이 생깁니다.

워크로드가 한 리전에 무리 없이 들어간다면 일반적인 관리형 관계형 데이터베이스가 여전히 더 나은 기본 선택입니다. 사용자 정의 샤딩, 리전 장애 조치, 지리적 데이터 통제가 별도의 대형 엔지니어링 시스템이 될 상황이라면 분산 SQL의 비용을 정당화할 수 있습니다.

분산 SQL의 내부 동작 방식

분산 SQL은 데이터를 복제된 파티션으로 나누고, 합의와 분산 트랜잭션 프로토콜로 변경을 조정합니다. 데이터베이스는 SQL 뒤에 이 장치를 대부분 숨기지만, 동작 방식은 지연 시간, 처리량, 스키마 설계, 장애 대응에 영향을 줍니다.

파티션이 레코드 위치를 결정한다

클러스터는 논리적 테이블을 노드 간에 독립적으로 이동할 수 있는 작은 단위로 나눕니다. Spanner는 보통 이 단위를 split, CockroachDB는 range, YugabyteDB는 tablet이라고 부릅니다. 각 단위는 테이블이나 인덱스 키 공간의 일부를 담당합니다.

파티션 경계는 범위, 해시 또는 명시적 지리 규칙을 따를 수 있습니다. 고객 식별자 순서로 된 범위는 관련 레코드를 쉽게 스캔하게 하지만, 단조 증가 식별자는 새 쓰기를 하나의 파티션으로 몰 수 있습니다. 해시 분산은 쓰기를 더 고르게 퍼뜨리지만, 순서 있는 스캔이나 테넌트 배치를 어렵게 할 수 있습니다. 많은 운영 스키마는 테넌트 식별자와 다른 값을 조합해, 모든 쓰기가 한 위치에 쏠리지 않으면서 관련 데이터에 접근할 수 있게 합니다.

보조 인덱스도 자체 분산 저장소가 필요합니다. 따라서 한 행을 쓸 때 기본 테이블과 다른 파티션의 여러 인덱스 항목을 함께 갱신할 수 있습니다. 단일 서버에서는 저렴했던 인덱스가 클러스터에서는 추가 합의 작업과 네트워크 트래픽을 만들 수 있습니다.

복제와 합의가 각 파티션을 보호한다

각 파티션에는 보통 여러 리플리카가 있고, 합의 그룹이 허용되는 변경 순서를 결정합니다. CockroachDB와 YugabyteDB는 Raft 기반 복제를 사용합니다. Spanner는 시간 인프라와 함께 Paxos 기반 복제를 사용합니다.

리더 또는 leaseholder가 리플리카 그룹의 쓰기를 조정합니다. 시스템은 쿼럼을 이루는 충분한 리플리카에 변경을 기록한 뒤 커밋으로 처리합니다. 노드가 사라져도 쿼럼이 남아 있으면 살아남은 구성원이 다른 조정자를 선출하거나 지정할 수 있습니다.

쿼럼은 수학적 조건이지 모든 장애가 무해하다는 약속이 아닙니다. 리플리카가 3개인 그룹은 보통 하나의 리플리카 장애를 견딜 수 있습니다. 두 구성원을 잃으면 남은 사본은 다른 다수가 다른 곳에서 진행하지 않았음을 증명할 수 없으므로 안전하게 쓰기를 받을 수 없습니다. 장애 도메인 전체의 배치가 리플리카 수만큼 중요합니다.

분산 트랜잭션은 여러 파티션을 조정한다

하나의 파티션만 건드리는 트랜잭션은 비교적 적은 조정으로 끝날 수 있습니다. 여러 파티션이 참여하면 모든 참여자가 쓰기를 적용하거나 모두 중단하도록 공통 커밋 결정이 필요합니다.

정확한 프로토콜은 제품마다 다르지만, 보통 관련 버전을 읽거나 잠그고, 동시 변경을 검증하며, intent 또는 임시 레코드를 복제하고, 커밋을 마무리하는 작업이 포함됩니다. 긴 트랜잭션은 충돌 가능 시간을 늘립니다. 큰 배치는 각 문장이 단순해 보여도 많은 합의 그룹을 건드려 지연 시간 급증을 만들 수 있습니다.

그래서 네트워크를 고려한 트랜잭션 설계가 중요합니다. 데이터베이스가 지원한다면 관련 행을 호환되는 파티션 접두사 아래에 묶으세요. 트랜잭션은 짧게 유지하고, 트랜잭션이 열린 상태에서 외부 서비스를 기다리지 말며, 영향 측정 없이 서로 무관한 수천 개의 레코드를 하나의 원자적 단위에 넣지 마세요.

시간과 순서에는 명시적 장치가 필요하다

분산 노드는 완벽하게 동기화된 벽시계를 공유하지 않으므로, 각 제품에는 트랜잭션 순서를 정하는 방법이 필요합니다. Spanner는 TrueTime의 불확실성 범위와 커밋 대기를 이용해 외부 일관성을 제공합니다. 다른 시스템은 물리 시계와 논리 구성 요소, 의존성 추적, 트랜잭션 프로토콜을 결합할 수 있습니다.

시계 조정은 직렬화 가능 실행, follower read, 스냅샷 같은 작업에 영향을 줍니다. 별도 애플리케이션 서버가 생성한 타임스탬프가 신뢰할 수 있는 전역 순서를 만든다고 가정하지 말고 데이터베이스 트랜잭션 타임스탬프를 사용해야 합니다.

로컬리티가 네트워크 경로를 제어한다

로컬리티 구성은 리플리카가 어디에 있고 어느 리전이 레코드 쓰기를 조정하는지 결정합니다. 호출자 가까이에 적절한 리플리카가 있으면 읽기는 빠를 수 있습니다. 강한 순서를 보장하는 쓰기는 여전히 쿼럼에 필요한 리플리카까지 도달해야 하므로, 지연 시간은 선택한 토폴로지를 반영합니다.

좋은 배치는 회사 조직도를 따르기보다 워크로드를 따릅니다. EU 테넌트의 대부분 쓰기가 유럽에서 발생한다면, 그 테넌트의 쓰기 조정자를 유럽에 두어 매 트랜잭션 시작 시 대륙 간 이동을 피할 수 있습니다. 모든 리전에서 갱신하는 카운터처럼 전 세계가 공유하는 레코드는 모든 작성자에게 로컬일 수 없고 경합 지점이 될 수 있습니다.

분산 SQL이 적합한 경우

분산 SQL은 지리적 복원력, 수평 쓰기 용량, 파티션 간 정확성이 지속적인 조정 비용을 감수할 만큼 중요할 때 적합합니다. 대기업이라고 자동으로 필요한 것은 아니며, 엄격한 리전 가용성을 비즈니스 약속으로 내건 작은 제품에는 필요할 수 있습니다.

평가를 정당화하는 조건

다음 조건 중 여러 개가 해당하면 진지한 평가가 적절합니다.

  • 서비스가 영역 또는 리전 장애에도 계속 동작해야 함
  • 쓰기 수요가 단일 프라이머리 데이터베이스의 실질적 한계에 가까움
  • 수동 샤딩에 상당한 애플리케이션 엔지니어링 시간이 들 것임
  • 노드나 위치를 넘어서도 트랜잭션이 정확해야 함
  • 레코드에 강제 가능한 지리적 배치가 필요함

이 조건은 수치로 뒷받침해야 합니다. 필요한 복구 시간 목표, 복구 시점 목표, 트랜잭션 지연 시간, 피크 쓰기 비율, 장애 도메인을 정의하세요. 막연한 글로벌 확장 요청만으로는 아키텍처를 선택할 수 없습니다.

리전 사용자가 있다는 사실만으로 결정되지는 않습니다. 콘텐츠 비중이 높은 애플리케이션은 데이터베이스 리전 하나를 유지하면서 웹 서버와 캐시를 사용자 가까이에 둘 수 있습니다. 약간 오래된 결과가 허용되면 읽기 리플리카로 리전별 탐색을 지원할 수 있습니다. 여러 위치의 사용자가 관련 데이터를 대상으로 저지연 쓰기를 해야 할 때 근거가 더 강해집니다.

더 단순한 데이터베이스가 유리한 조건

트래픽이 보통 수준이고 쓰기가 한 리전에서 발생하며 계획된 데이터베이스 승격으로 복구할 수 있다면 일반적인 관계형 서비스가 보통 더 좋습니다. 성숙한 도구, 폭넓은 확장 호환성, 익숙한 디버깅, 더 작은 인프라 비용을 제공합니다.

엄격한 지연 시간 요구도 단일 리전 프라이머리에 유리할 수 있습니다. 로컬의 내구성 있는 쓰기는 먼 리전을 넘는 쿼럼 쓰기보다 훨씬 빨리 끝날 수 있습니다. 분석 중심 시스템은 하나의 클러스터가 둘 다 잘하기를 기대하기보다 운영 트랜잭션과 긴 스캔을 분리하는 편이 보통 낫습니다.

팀 역량도 중요합니다. 관리형 서비스는 하드웨어, 패치, 제어 플레인 운영 부담을 줄이지만 스키마 경합, 트랜잭션 재시도, 쿼리 계획, 용량 관리, 애플리케이션 측 장애 대응을 없애지는 않습니다. 장애 동작을 테스트할 시간이 없는 팀이라면 분산 데이터베이스 도입은 위험을 늘릴 수 있습니다.

대안을 기준으로 한 결정 임계점

대안이 이미 복잡할 때 가장 강한 근거가 나옵니다. 엔지니어가 테넌트 라우팅, 샤드 맵, 샤드 간 트랜잭션 규칙, 리전 승격 절차, 별도 마이그레이션 도구를 만들려 한다면, 이 기능을 제공하는 데이터베이스를 면밀히 평가할 가치가 있습니다.

대안이 읽기 리플리카와 검증된 백업을 갖춘 관리형 PostgreSQL 인스턴스 하나라면, 마이그레이션에는 분명한 근거가 필요합니다. 먼저 기존 시스템을 벤치마크하세요. CPU 포화의 실제 원인은 수평 쓰기 부족이 아니라 비효율적 쿼리, 잘못된 커넥션 관리, 과도한 인덱스, 누락된 캐시일 수 있습니다.

일관성, 가용성, 지연 시간

분산 SQL은 보통 필요한 쿼럼에 도달하지 못하는 작업을 거부해 장애 중에도 트랜잭션 일관성을 지킵니다. 커밋된 상태를 보호하지만, 네트워크 파티션 중 일부 요청이 실패하거나 기다릴 수 있음을 뜻합니다.

CAP는 장애 동작을 설명한다

CAP 정리는 클러스터 일부 사이의 통신이 끊길 때 적용됩니다. 영향을 받은 데이터에 대해 시스템은 선형화 가능한 일관성과 격리된 모든 쪽의 성공 응답을 동시에 보장할 수 없습니다. 일관성을 우선하는 데이터베이스는 쿼럼이 있는 쪽은 계속 처리하고, 다른 곳에서는 안전하지 않은 쓰기를 거부합니다.

CAP는 정상 운영 지연 시간을 설명하지 않습니다. 모든 연결이 정상이어도 리플리카는 통신해야 합니다. 더 넓은 엔지니어링 판단에는 파티션 때의 동작과 정상 상태에서 애플리케이션이 받아들일 조정 비용이 포함됩니다.

애플리케이션은 사용할 수 없는 결과를 명시적으로 처리해야 합니다. 타임아웃, 재시도 가능한 트랜잭션 오류, 쓰기 리전의 일시적 손실은 정상적으로 일어날 수 있습니다. 두 격리된 리전에서 모두 성공을 반환하면, 유효한 자동 조정 답이 없을 수 있으므로 잔액이나 예약에는 더 나쁜 결과가 됩니다.

강한 읽기와 의도적으로 오래된 읽기는 다르다

강한 읽기는 요청한 순서 보장과 일관된 데이터베이스 상태를 관찰합니다. 일부 제품은 신선도를 낮춰 지연 시간과 쓰기 조정자의 작업을 줄이는 follower read 또는 제한된 오래된 읽기도 제공합니다.

선택은 읽는 필드에 맞춰야 합니다. 상품 설명은 약간 오래된 리플리카여도 되는 경우가 많습니다. 방금 변경한 비밀번호, 현재 계좌 잔액, 남은 재고에는 적절한 강한 읽기 또는 세션 일관 경로를 써야 합니다. 속도를 위해 모든 읽기를 오래된 읽기로 표시한 뒤 서비스 코드에서 정확성을 다시 구현해서는 안 됩니다.

실제 드라이버와 라우팅 계층으로 read-your-writes 동작을 테스트해야 합니다. 업데이트 뒤 다음 요청은 다른 애플리케이션 서버나 데이터베이스 엔드포인트에 도달할 수 있습니다. 사용자가 수락된 변경을 보게 하려면 세션 토큰, 트랜잭션 경계, 강한 읽기 설정이 필요할 수 있습니다.

격리 수준이 동시 작업 결과를 제어한다

트랜잭션 격리 수준은 동시 트랜잭션이 만들 수 있는 이상 현상을 결정합니다. 직렬화 가능 격리는 데이터베이스가 동시에 실행해도 완료된 트랜잭션이 한 번에 하나씩 실행된 것처럼 보이게 하는 것을 목표로 합니다.

직렬화 가능 실행에서는 동시 작업을 안전하게 순서화할 수 없을 때 한 참여자를 중단시킬 수 있습니다. 이 중단은 잘못된 결과를 막는 보호 장치이지 데이터베이스 손상이 아닙니다. 애플리케이션은 쓰기에 영향을 준 모든 읽기를 포함해 전체 트랜잭션을 제한적으로 재시도해야 합니다.

데이터베이스 밖에서는 재시도가 멱등적이어야 합니다. 트랜잭션이 확실히 커밋되기 전에 이메일을 보내거나 결제 제공업체를 호출하면 재시도 시 부작용이 반복될 수 있습니다. 데이터베이스 트랜잭션에 아웃박스 이벤트를 기록하고 커밋한 뒤, 별도 워커가 외부 작업을 전달하게 하세요.

거리가 쓰기 지연 시간의 하한을 만든다

리전 간 트랜잭션은 프로토콜에 필요한 메시지보다 빨리 끝날 수 없습니다. 쿼럼 구성원 간 왕복이 80밀리초라면 쿼리 실행, 인덱스 유지보수, 애플리케이션 작업, 대기열 처리 전에 실제 시간이 추가됩니다.

비용이 큰 패턴은 사용자 작업 하나에서 여러 순차 트랜잭션을 실행하는 경우입니다. 결제 과정이 주문 삽입, 재고 예약, 결제 상태 갱신, 감사 기록을 네 번의 차단 커밋으로 수행하면 네트워크 비용이 누적됩니다. 하나의 원자적 결과를 공유하는 데이터베이스 변경을 결합하면 불필요한 왕복을 없앨 수 있고, 외부 결제 호출은 열린 트랜잭션 밖에 둬야 합니다.

평균 대신 백분위 지연 시간을 측정하세요. 리더 이동, 경합, 스토리지 정지, 재시도는 꼬리 지연 시간에 나타납니다. 중앙값 목표를 맞춰도 일반적인 재균형 중 99백분위를 놓치면 눈에 띄는 사용자 실패가 생길 수 있습니다.

Spanner, CockroachDB, YugabyteDB 비교

Spanner, CockroachDB, YugabyteDB는 비슷한 분산 문제를 풀지만 배포 모델, 호환성, 트랜잭션 구현, 운영 가정이 다릅니다. 같은 SQL이라는 이름만 보고 고르기보다 애플리케이션 동작을 테스트해야 합니다.

영역Google SpannerCockroachDBYugabyteDB
주 SQL 인터페이스GoogleSQL 또는 PostgreSQL 방언PostgreSQL 와이어 프로토콜 기반 PostgreSQL 호환 SQLPostgreSQL 호환 SQL용 YSQL, Cassandra 스타일 접근용 YCQL
복제 기반TrueTime 기반 순서를 갖춘 Paxos 그룹range 전체의 Raft 복제tablet 전체의 Raft 복제
일반적 제공 방식관리형 Google Cloud 데이터베이스관리형 클라우드 서비스 또는 자체 관리 배포관리형 클라우드 서비스 또는 자체 관리 배포
이식성 우려방언과 플랫폼별 동작PostgreSQL 기능, 확장, 의미의 차이YSQL과 PostgreSQL 사이의 버전 및 기능 차이
자연스러운 평가 사례글로벌 트랜잭션 배치가 필요한 Google Cloud 시스템분산 운영과 PostgreSQL 중심 개발을 원하는 팀PostgreSQL 중심 접근 또는 SQL과 Cassandra 스타일 API 선택을 원하는 팀

Spanner는 관리형 Google Cloud 전략에 적합하다

Spanner는 관리형 Google Cloud 데이터베이스를 사용하고 그 방언, 토폴로지, 운영 모델에 맞춰 설계할 준비가 된 조직에 적합합니다. TrueTime은 외부 일관성 트랜잭션을 지원하므로, 문서화된 의미 안에서 커밋된 트랜잭션이 실제 시간 순서를 존중합니다.

PostgreSQL 방언은 SQL 문법 차이를 줄일 수 있지만, 완전한 PostgreSQL 동등성은 아닙니다. 확장, 관리 함수, 시스템 카탈로그, 데이터 타입, 드라이버, ORM 가정은 여전히 확인해야 합니다. 기존 애플리케이션을 이식 가능하다고 보기 전에 모든 데이터베이스 의존성을 목록화해야 합니다.

원하는 시스템이 이미 Google Cloud의 ID, 네트워킹, 관찰성, 리전 제어에 의존한다면 Spanner를 특히 주의 깊게 검토할 만합니다. 관리형 모델은 데이터베이스 노드 관리를 없애지만, 스키마 설계, 쿼리 튜닝, 할당량, 비용 관리, 애플리케이션 복구는 여전히 고객 책임입니다.

CockroachDB는 PostgreSQL 중심 분산 애플리케이션에 적합하다

CockroachDB는 PostgreSQL 스타일 애플리케이션 접근을 원하면서 range 전체에 트랜잭션 데이터를 분산하려는 팀에 적합합니다. 기본값이 직렬화 가능 격리이므로, 경합이나 순서 충돌로 거부된 트랜잭션을 애플리케이션이 올바르게 재시도해야 합니다.

호환성은 마이그레이션, 드라이버, ORM 계층에서 테스트해야 합니다. PostgreSQL 확장과 특수 동작이 없거나 다를 수 있습니다. 단일 노드 실행 계획에 의존하는 쿼리는 테이블과 인덱스가 range로 나뉜 뒤 다르게 동작할 수도 있습니다.

range 이동과 자동 재균형은 용량 변경을 단순하게 하지만, 잘못된 기본 키 선택은 여전히 hot range를 만들 수 있습니다. 멀티 리전 추상화는 테이블 로컬리티를 표현하게 돕지만, 개발자는 어떤 레코드가 리전별인지, 어떤 레코드가 전역인지, 쓰기를 어디서 조정할지 정해야 합니다.

YugabyteDB는 YSQL과 혼합 API 요구에 적합하다

YugabyteDB는 PostgreSQL 호환 관계형 인터페이스를 중요하게 여기며 별도의 Cassandra 호환 API가 도움이 될 수 있는 애플리케이션에 적합합니다. YSQL은 관계형 테이블과 분산 트랜잭션을 제공하는 반면, YCQL은 다른 데이터 모델을 따르므로 모든 YSQL 작업으로 가는 또 다른 경로로 보면 안 됩니다.

스토리지 계층은 tablet으로 데이터를 분산합니다. 테이블 설계, tablet 분할, 인덱스 배치, 트랜잭션 범위가 클러스터 전체에 작업이 퍼지는 방식에 영향을 줍니다. PostgreSQL 애플리케이션도 확장, 함수, 도구, 플래너 동작의 호환성 테스트가 필요합니다.

여러 배포 방식을 선택할 수 있어 배치를 통제해야 하는 인프라 정책에 잘 맞을 수 있습니다. 자체 관리 시 이런 통제는 운영 책임을 고객에게 넘깁니다. 업그레이드, 복구 절차, 용량, 관찰성, 인증서, 백업, 장애 테스트 모두 담당자가 필요합니다.

유용한 제품 테스트는 애플리케이션 증거를 사용한다

유용한 비교는 같은 대표 워크로드를 실행 가능한 각 제품에서 돌립니다. 스키마 생성, 마이그레이션, ORM이 생성한 SQL, 트랜잭션 재시도, 백업 복원, 장애 조치, 확장 이벤트, 최고 볼륨 쿼리를 테스트하세요.

피크 초당 트랜잭션만 비교하지 마세요. p50, p95, p99 지연 시간, 충돌과 재시도 비율, 리전 간 전송 바이트, 스토리지 증폭, 복원 시간, 모의 장애 중 운영자 작업량을 기록하세요. 정확성과 복구 목표를 허용 가능한 비용과 운영 부담으로 만족하는 선택지가 최선입니다.

리전별 사용자를 둔 글로벌 SaaS

초기에 장애 조치를 연습하세요
테스트 환경을 배포하고 실제와 비슷한 트래픽으로 장애 훈련을 실행하세요.

글로벌 SaaS 애플리케이션은 테넌트별 리전 데이터 배치와 트랜잭션 접근이 필요하면서 지리마다 별도 데이터베이스 스택을 운영하고 싶지 않을 때 분산 SQL의 이점을 얻습니다. 테넌시가 스키마에 명시적이고 대부분의 트랜잭션이 한 테넌트 안에 머물 때 가장 잘 동작합니다.

테넌트 로컬리티는 계약과 트래픽을 따라야 한다

테넌트 식별자로 배치를 유도하면 유럽 레코드는 승인된 유럽 위치에 남기고, 다른 고객의 레코드는 계약된 국가나 리전에 둘 수 있습니다. 하나의 논리적 스키마를 유지하면서 물리적 정책은 다르게 적용할 수 있습니다.

배치 규칙은 기본 테이블만 다뤄서는 안 됩니다. 인덱스 항목, 변경 스트림, 임시 데이터, 백업, 내보낸 레코드에도 규제 대상 정보가 들어갈 수 있습니다. 행은 고정했지만 전역 보조 인덱스를 다른 곳으로 보내는 정책은 의도한 경계를 위반할 수 있습니다.

테넌트 격리는 성능에도 영향을 줍니다. 큰 테넌트 하나가 공유 파티션을 압도하거나 노드를 독점할 수 있습니다. 그 테넌트 안에서 해싱이나 하위 파티셔닝이 필요할 수 있지만, 테넌트 범위 트랜잭션에는 계속 효율적으로 접근할 수 있어야 합니다.

리전별 읽기에는 명시적 신선도 정책이 필요하다

읽기 비중이 큰 대시보드는 약간 지연된 데이터가 허용된다면 가까운 리플리카를 사용할 수 있습니다. 계정 변경, 권한 결정, 트랜잭션 후 확인 화면에는 더 강한 동작이 필요합니다. 전역 설정 하나를 쓰기보다 신선도 요구에 따라 쿼리 경로를 분류하세요.

쓰기 배치는 테넌트별 일반적인 작성자를 따라야 합니다. 고객 직원이 주로 싱가포르에서 일하는데 다른 대륙에서 쓰기를 조정하면 피할 수 있는 지연 시간이 생깁니다. 테넌트 마이그레이션 절차는 쓰기를 잃거나 레지던시를 위반하거나 애플리케이션 캐시가 이전 위치를 계속 가리키지 않도록 배치를 갱신해야 합니다.

글로벌 애플리케이션 코드는 이동을 견뎌야 한다

리더는 이동하고, 노드는 재시작하며, 유지보수 중 라우팅이 바뀝니다. 드라이버에는 적절한 타임아웃, 재시도 정책, 연결 갱신, 트랜잭션 재시작 로직이 필요합니다. 과부하 클러스터가 반복 요청의 즉각적이고 동기화된 물결을 받지 않도록 재시도에는 지터와 한도를 둬야 합니다.

모니터링은 리전과 테넌트 등급별 사용자 지연 시간을 분리해야 합니다. 전역 평균은 멀리 있는 고객 그룹이 몇 번의 추가 네트워크 이동을 감수하는 문제를 숨길 수 있습니다. API span과 데이터베이스 문장을 연결하는 추적 식별자는 로컬리티 실수를 찾기 쉽게 합니다.

금융 워크플로와 원장

금융 워크플로는 데이터베이스 제약 조건과 트랜잭션이 장애와 동시 요청 전반에서 원장 불변 조건을 강제할 때 이점을 얻습니다. 분산만으로 회계가 정확해지지는 않으므로, 스키마에 위반할 수 없는 규칙을 담아야 합니다.

원장은 감사 가능한 항목 순서를 보존해야 한다

추가 중심 원장은 이력 없이 잔액 하나를 반복해서 바꾸기보다 각 이동을 항목으로 기록합니다. 모든 전기에는 안정적인 트랜잭션 식별자, 계정, 금액, 통화, 비즈니스 타임스탬프, 생성 메타데이터가 있어야 합니다. 차변과 대변이 전기 단위에서 일치하도록 커밋 전에 복식부기 규칙을 확인해야 합니다.

캐시된 잔액은 읽기를 빠르게 할 수 있지만 항목과 같은 트랜잭션에서 바뀌거나, 파생 데이터임이 분명해야 합니다. 조정 작업은 파생 합계와 원본 항목을 비교하고 차이를 보고해야 하며, 이력을 조용히 다시 쓰면 안 됩니다.

모든 계정에 전역 순서가 필요한 경우는 드뭅니다. 한 계정이나 이체 쌍에 영향을 주는 트랜잭션에는 일관된 순서가 필요하지만, 무관한 계정은 동시에 진행할 수 있습니다. 이 경계를 중심으로 설계하면 전역 시퀀스나 정산 행 하나보다 경합을 줄일 수 있습니다.

멱등성이 재시도를 안전하게 만든다

결제 API, 큐, 웹훅은 타임아웃 뒤 재시도하므로, 각 비즈니스 작업에는 안정적인 멱등성 키가 필요합니다. 판매자 또는 계정 같은 올바른 범위에서 고유성을 강제한 뒤, 하나의 데이터베이스 트랜잭션에서 결제 레코드와 원장 항목을 만드세요.

CREATE TABLE payment_attempts (
    account_id UUID NOT NULL,
    idempotency_key TEXT NOT NULL,
    provider_reference TEXT,
    status TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL,
    PRIMARY KEY (account_id, idempotency_key)
);

두 워커가 같은 작업을 제출하면 고유 제약 조건이 어느 삽입이 성공하는지 결정합니다. 실패한 워커는 기존 레코드를 읽고 이미 정해진 결과를 반환해야 합니다. 데이터베이스 트랜잭션이 재시도되었다는 이유만으로 두 번째 제공업체 청구를 만들면 안 됩니다.

외부 호출에는 트랜잭션 경계가 필요하다

대부분의 공개 API는 전문 조정 프로토콜에 참여하지 않으므로, 둘 다 참여하지 않는 한 데이터베이스는 관련 없는 결제 제공업체와 원자적으로 커밋할 수 없습니다. 네트워크 호출은 데이터베이스 트랜잭션 밖에 두고, pending, authorized, captured, failed, reversed 같은 명시적 상태로 워크플로를 모델링하세요.

트랜잭션 아웃박스는 커밋된 변경을 다운스트림 워커에 발행할 수 있습니다. 메시지는 한 번 이상 전달될 수 있으므로 소비자는 이벤트 식별자로 중복을 제거해야 합니다. 이 방식은 모든 서비스에 걸친 불가능한 단일 트랜잭션을 주장하지 않으면서 복구 가능한 처리를 제공합니다.

핫 계정에는 워크로드별 설계가 필요하다

급여 실행, 마켓플레이스 정산, 대형 판매자는 하나의 계정에 쓰기를 집중시킬 수 있습니다. 데이터베이스 노드를 추가해도 하나의 충돌하는 행을 나누지는 못합니다. 불변 항목 파티션, 기간별 누산기, 한 계정용 대기열 전기, 신중하게 정의한 하위 계정 계층을 고려할 수 있습니다.

실제 편중 분포를 테스트하세요. 균일한 합성 트래픽에서는 클러스터가 준비된 것처럼 보여도 운영에서는 한 판매자가 반복적인 직렬화 가능 충돌을 만들 수 있습니다. 정확성이 먼저이지만, 데이터 모델은 회계 규칙이 허용하는 곳에서 안전한 동시성을 드러내야 합니다.

재고, 예약, 좌석 배정

재고와 예약 시스템은 여러 사용자가 같은 희소 항목을 차지할 수 있을 때 권위 있는 할당 트랜잭션이 필요합니다. 빠른 재고 확인 읽기는 탐색을 개선하지만, 최종 수량을 받는 사람은 커밋 경로만 결정할 수 있습니다.

조건부 쓰기가 초과 판매를 막는다

조건부 업데이트는 충분한 재고가 남았을 때만 재고를 예약할 수 있습니다. 영향을 받은 행 수로 애플리케이션은 할당 성공 여부를 알 수 있습니다.

UPDATE inventory
SET available = available - 1
WHERE sku = $1
  AND available > 0;

이 문장은 예약 레코드와 같은 트랜잭션에 있어야 합니다. 먼저 재고를 읽고 나중에 차감하면 격리 수준과 조건 처리 방식이 결정을 보호하지 않는 한 경쟁 상태가 생깁니다. 데이터베이스 제약 조건은 또 하나의 안전장치로 음수 수량을 거부해야 합니다.

지정 좌석에서는 공연과 좌석 식별자에 대한 고유 제약 조건이 하나의 승자만 만들 수 있습니다. 호텔 재고는 흔히 객실별 숙박일 또는 재고 풀 날짜로 모델링해 겹치는 숙박이 같은 용량을 차지하지 못하게 합니다. 올바른 경합 단위는 비즈니스 규칙에서 나옵니다.

홀드는 할당과 결제를 분리한다

임시 홀드는 결제나 사용자 확인이 진행되는 동안 재고를 확보합니다. 만료 시간과 상태를 저장한 뒤 조건부 트랜잭션으로 확정 예약으로 바꾸세요. 확정과 만료가 경쟁할 수 있으므로 만료 워커는 아직 활성 상태인 홀드만 해제해야 합니다.

벽시계 지연만으로 해제가 보장되지는 않습니다. 워커는 멈출 수 있고, 큐가 지연될 수 있으며, 리전 장애가 날 수 있습니다. 판매 가능한 재고를 계산하는 쿼리는 만료 상태를 일관되게 고려해야 하며, 복구 작업은 놓친 홀드를 회수해야 합니다.

홀드 시간은 제품과 용량에 관한 결정입니다. 결제에는 10분 홀드가 적절할 수 있지만, 혼잡한 때에는 희소 재고의 의미 있는 비율을 묶어 둘 수 있습니다. 설정 전에 이탈률과 결제 완료 시간을 측정하세요.

극심한 경합은 선형적으로 확장되지 않는다

한 행을 두고 수천 명의 구매자가 경쟁하는 상황은 리플리카를 추가해 병렬화할 수 없습니다. 모든 성공적인 차감은 다른 차감과 순서가 정해져야 합니다. 출시 중에는 입장 제어, 큐, 재고 버킷, 사전 할당된 리전별 할당량으로 데이터베이스를 보호할 수 있습니다.

리전별 할당량은 조정을 줄이지만 의미를 바꿉니다. 유럽에 사용하지 않은 수량이 남았는데 다른 리전은 매진되면, 시스템은 할당량을 안전하게 옮기거나 일시적 불균형을 받아들이는 방법이 필요합니다. 비즈니스가 리전별 용량 조정 방식을 정의할 수 있을 때만 이 패턴을 사용하세요.

고가용성과 재해 복구

스택의 주도권을 유지하세요
소스 코드를 직접 보유해 프로토타입이 준비되면 자체 리포지토리에서 계속 작업하세요.

분산 SQL은 리플리카 배치, 여유 용량, 애플리케이션 동작이 정해진 서비스 목표와 맞을 때 선택된 인프라 장애 중에도 서비스를 유지할 수 있습니다. 복제만으로는 이 결과가 보장되지 않습니다.

SLO는 장애 도메인을 명시해야 한다

가동 시간 목표에는 워크로드와 장애 시나리오가 필요합니다. 서비스가 노드 하나, 가용 영역 하나, 전체 리전 하나를 견뎌야 하는지 정의하세요. 복구 후뿐 아니라 장애 중 허용되는 오류율과 지연 시간도 명시하세요.

한 건물에 놓인 리플리카 3개 클러스터는 독립된 영역에 있는 리플리카 3개와 위험 프로필이 다릅니다. 멀티 리전 토폴로지는 더 넓은 사건을 막지만, 쿼럼 경로가 길어지고 한 위치가 사라진 뒤 트래픽을 흡수할 충분한 잔여 용량이 필요합니다.

복구 시간 목표는 서비스가 얼마나 빨리 돌아와야 하는지 정의합니다. 복구 시점 목표는 커밋된 데이터를 얼마나 잃어도 되는지 정의합니다. 동기식 쿼럼 복제는 해당 장애에 대해 커밋 데이터 손실 0이라는 목표를 지원할 수 있지만, 필요한 리플리카와 애플리케이션 경로가 설계대로 동작할 때만 가능합니다.

장애 조치는 애플리케이션에 보이는 이벤트를 만든다

리더십 변경은 진행 중인 트랜잭션을 끊고, 연결을 닫고, 지연 시간을 늘릴 수 있습니다. 애플리케이션은 재시도 가능한 데이터베이스 결과와 영구적인 비즈니스 오류를 구분해야 합니다. 실패한 트랜잭션은 마지막 문장만 다시 실행하지 말고 단위 전체를 재시작해야 합니다.

커넥션 풀은 장애 뒤에도 죽은 엔드포인트를 유지할 수 있습니다. 헬스 체크, DNS 동작, 로드 밸런서, 인증서 검증, 드라이버 토폴로지 검색은 테스트 계획에 포함해야 합니다. 데이터베이스가 정상이어도 애플리케이션이 찾지 못할 수 있습니다.

장애 뒤 용량은 명시적으로 계산해야 합니다. 리전 3개가 평소 70% 가까이 사용 중이면 하나를 잃었을 때 그 작업 몫을 담을 공간이 부족합니다. 여유 용량 확보에는 비용이 들지만, 장애 조치 용량이 없는 토폴로지는 명시한 목표를 충족하지 못합니다.

게임 데이가 설계를 검증한다

장애 훈련에서는 노드 하나를 끄고, 영역을 격리하며, 리전 연결을 끊고, 애플리케이션 엔드포인트를 제거해야 합니다. 오류 지속 시간, 트랜잭션 재시도율, 지연 시간 백분위, 큐 증가, 운영자 대응을 측정하세요.

의미 있는 토폴로지, 드라이버, 스키마 변경 뒤에는 이 훈련을 실행하세요. 작년 트래픽으로 검증된 절차가 데이터량이 두 배가 되거나 한 테넌트가 지배적이 된 뒤에는 실패할 수 있습니다. 증거가 연례 수동 행사에 의존하지 않도록 훈련의 안전한 부분을 자동화하세요.

복제는 백업이 아니다

리플리카는 실수로 삭제한 데이터, 결함 있는 마이그레이션, 해로운 애플리케이션 쓰기도 충실히 복사합니다. 백업과 특정 시점 복구는 복제가 감지할 수 없는 논리적 손상을 보호합니다.

복구 훈련은 별도의 깨끗한 환경을 만들고, 체크섬이나 애플리케이션 불변 조건을 확인하며, 총 복구 시간을 측정해야 합니다. 암호화 키, 접근 정책, 스키마 버전, 종속 구성도 포함하세요. 목표 시간 안에 복원할 수 없는 백업은 적절한 복구 시스템이 아닙니다.

데이터 레지던시와 규정 준수 중심 아키텍처

분산 SQL은 테넌트나 레코드 그룹을 승인된 리전에 배치할 수 있지만, 규정 준수는 모든 복사본, 접근 경로, 운영 프로세스에 달려 있습니다. 데이터베이스 로컬리티는 더 넓은 프로그램 안의 한 통제 수단입니다.

레지던시 규칙에는 정확한 정의가 필요하다

데이터를 한 국가에 남겨야 한다는 요구는 저장, 처리, 지원 접근, 백업, 암호화 키 또는 그 전부를 뜻할 수 있습니다. 해석에 따라 토폴로지가 달라집니다. 법률 자문과 감사 담당자는 규정과 계약을 테스트 가능한 기술 통제로 바꿔야 합니다.

팀에는 규제 대상 필드와 파생 데이터의 목록이 필요합니다. 로그, 추적, 검색 인덱스, 분석 내보내기, 지원 첨부 파일, 메시지 큐에는 기본 테이블과 같은 개인정보가 있을 수 있습니다. 원시 페이로드를 전 세계로 내보내면서 데이터베이스만 제한하면 의도한 정책을 충족하지 못합니다.

데이터 최소화는 설계를 단순하게 할 수 있습니다. 글로벌 서비스에 계정 식별자와 집계 상태만 필요하다면 민감한 세부 정보는 승인된 리전에 두고, 다른 곳에는 허용된 최소 표현만 노출하세요.

배치 정책은 수명 주기 작업까지 포함해야 한다

정책에는 운영 리플리카, 임시 리플리카, 백업, 스냅샷, 변경 레코드, 복원 환경이 존재할 수 있는 위치를 명시해야 합니다. 재균형과 유지보수도 같은 경계를 따라야 합니다. 긴급 절차가 편의상 규제 데이터를 승인되지 않은 리전으로 복사해서는 안 됩니다.

접근 제어에는 지리적, 조직적 제한이 필요합니다. 서비스 ID에는 필요한 테이블과 작업만 부여해야 합니다. 사람이 운영 환경에 접근할 때는 로그를 남기고, 가능하면 시간 제한을 두며, 검토해야 합니다. 리전에 묶인 암호화 키는 통제를 더할 수 있지만, 키 가용성과 재해 복구에는 별도 설계가 필요합니다.

테넌트 이전에는 문서화된 워크플로가 필요합니다. 계약 변경, 고객 마이그레이션, 기업 구조 조정으로 레코드를 관할권 사이에서 옮겨야 할 수 있습니다. 이전 사본이 언제 사라지는지, 백업은 어떻게 만료되는지, 완료를 증명할 증거는 무엇인지 식별해야 합니다.

전역 보고에는 파생 데이터셋이 필요할 수 있다

전역 대시보드는 리전 전체의 원시 고객 데이터를 스캔하면 엄격한 배치 정책과 충돌할 수 있습니다. 리전별 처리는 승인된 집계를 로컬에서 계산한 뒤 민감하지 않은 결과를 중앙 보고 저장소에 발행할 수 있습니다.

집계 규칙은 제한된 레코드를 재구성할 수 없게 해야 합니다. 직접 식별자를 제거해도 작은 그룹, 자유 텍스트 필드, 세부 차원은 개인정보를 노출할 수 있습니다. 따라서 분석 거버넌스는 나중 보고 프로젝트가 아니라 아키텍처 검토에 포함되어야 합니다.

운영 워크로드와 분석 워크로드는 종종 별도 시스템이 적합합니다. 트랜잭션 데이터베이스는 현재 제품 상태를 보호하고, 리전 범위 파이프라인은 보고서용 거버넌스 데이터셋을 만듭니다. 이렇게 분리하면 긴 분석 스캔이 지연 시간에 민감한 트랜잭션에서 멀어집니다.

비용과 성능 계획

기본 앱을 배포하세요
채팅으로 API와 UI를 생성한 뒤, 보일러플레이트 대신 데이터베이스 선택에 집중하세요.

분산 SQL은 중복 용량을 유지하고 네트워크 전체에서 작업을 조정하므로 기본적인 단일 리전 데이터베이스보다 비용이 큽니다. 비싼 샤딩 작업을 대체하거나 운영 프리미엄보다 큰 손실을 막는다면 투자를 정당화할 수 있습니다.

컴퓨팅과 스토리지에는 복제 오버헤드가 포함된다

논리 데이터셋이 2TB이고 전체 리플리카가 3개라면, 보조 인덱스, 임시 압축 공간, 백업, 메타데이터 전에도 복제 데이터가 약 6TB부터 시작합니다. 실제 과금과 압축은 제품마다 다르므로 논리적 테이블 크기만이 아니라 측정한 물리 스토리지로 추정해야 합니다.

컴퓨팅은 일반 작업, 합의 처리, 재균형, 백업 작업, 장애 여유분을 감당해야 합니다. 파티션 하나가 핫할 때 노드는 서로 대체 가능한 처리량 단위가 아닙니다. 용량 추가는 워크로드가 그 용량 전체로 퍼질 수 있을 때만 도움이 됩니다.

인덱스는 쓰기 작업과 스토리지를 늘립니다. 모든 보조 인덱스를 쿼리 가치, 갱신 빈도, 지리적 배치 관점에서 검토하세요. 분산 클러스터에서 사용하지 않는 인덱스는 디스크를 낭비하고 영향받는 모든 쓰기를 더 비싸게 합니다.

네트워크 비용도 커질 수 있다

복제는 리플리카 위치 사이에 쓰기를 전송합니다. 리전 간 쿼리, 변경 피드, 백업, 애플리케이션 트래픽은 전송을 더합니다. 여러 리전의 활성 트래픽은 단일 리전 벤치마크로는 보이지 않는 비용을 만들 수 있습니다.

트랜잭션당 바이트, 복제 계수, 쓰기 비율, 인덱스 증폭, 전송 방향을 추정하세요. 그 뒤 대표 부하 실행 중 제공업체 과금 데이터로 테스트하세요. 요청 수만으로는 큰 페이로드와 백그라운드 이동을 놓칩니다.

로컬리티 실수는 비용과 지연 시간을 함께 올립니다. 한 리전에 배포된 서비스가 엔드포인트 선택이나 테넌트 배치 때문에 다른 리전의 조정자를 반복해서 조회할 수 있습니다. 분산 추적과 리전별 비용 내역으로 이 패턴을 드러낼 수 있습니다.

사용자 여정은 누적 지연 시간을 보여 준다

개별 문장보다 완전한 사용자 작업을 모델링하세요. 결제 과정이라면 순차 데이터베이스 커밋, 강한 읽기, 외부 API 호출, 큐 전달을 모두 세세요. 측정한 리전 왕복 시간과 쿼리 실행 백분위를 핵심 경로에 적용하세요.

한 여정에 순차 쿼럼 쓰기가 두 번 있고 각각 네트워크 조정으로 90밀리초가 더해진다고 해 봅시다. 애플리케이션 처리 전에도 약 180밀리초가 추가됩니다. 하나의 원자적 결정을 공유하는 변경을 결합하면 커밋 하나를 없앨 수 있고, 독립된 읽기를 병렬화하면 경로를 줄일 수 있습니다.

부하 테스트에는 현실적인 경합과 페이로드 크기를 포함해야 합니다. 무작위 식별자를 쓴 벤치마크는 완벽히 분산되지만 운영 쓰기는 소수의 인기 테넌트에 집중될 수 있습니다. 리더 변경과 재균형도 포함해 꼬리 지연 시간이 일반적인 클러스터 운영을 반영하게 하세요.

현실적인 대안과 총소유비용을 비교한다

의미 있는 비교 대상은 운영 비용이 전혀 없는 가상의 데이터베이스가 아닙니다. 관리형 PostgreSQL, 리플리카, 샤딩 서비스, 리전 복구, 애플리케이션 라우팅, 이를 유지할 엔지니어처럼 구체적인 대안과 비교하세요.

마이그레이션 작업, 교육, 관찰성, 장애 대응, 지원 계획, 이탈 비용도 포함하세요. 관리형 운영은 인프라 인력을 줄일 수 있지만, 자체 관리는 더 깊은 인력 투입을 대가로 통제 요구를 만족시킬 수 있습니다.

간단한 재무 모델로 연간 플랫폼 프리미엄을 예상 장애 손실, 지연된 엔지니어링 작업, 규정 준수 노출, 리전 지연 시간의 영향을 받는 수익과 비교할 수 있습니다. 불확실한 입력에는 범위를 사용하고 어떤 가정이 결정을 바꾸는지 확인하세요. 결과가 비현실적으로 큰 장애 추정에 의존한다면 더 단순한 시스템이 적절할 가능성이 큽니다.

스키마와 애플리케이션 설계 패턴

분산 SQL 스키마는 독립 작업은 분산하면서 관련 트랜잭션은 가깝게 둘 때 잘 작동합니다. 단일 노드 스키마를 그대로 옮기면 정확성은 유지해도 지연 시간이 나쁘거나 심한 경합이 생길 수 있습니다.

기본 키가 분산에 영향을 준다

단조 증가 기본 키는 새 행을 한 range의 끝으로 보낼 수 있습니다. 무작위 식별자는 삽입을 퍼뜨리지만, 완전히 무작위인 분산은 테넌트 스캔이나 리전 배치를 비싸게 할 수 있습니다. 복합 키는 테넌트 또는 버킷 식별자로 시작하고 그 그룹 안에서 정렬 가능한 값을 유지해 두 목표의 균형을 맞추는 경우가 많습니다.

접두사는 트랜잭션 경계에 따라 선택하세요. 거의 모든 작업이 테넌트 범위라면 테넌트 기준 그룹화가 분산 작업을 줄일 수 있습니다. 아주 큰 테넌트는 여러 파티션이 동시에 쓰기를 받도록 네임스페이스 안에 버킷이 필요할 수 있습니다.

테이블이 커진 뒤 기본 키를 바꾸려면 대규모 데이터 재작성 작업이 필요할 수 있습니다. 마이그레이션 전에 실제 편중으로 후보 레이아웃을 테스트하세요. 총 처리량만 보지 말고 파티션 열, 트랜잭션 fan-out, 인덱스 로컬리티, 스캔 동작을 살피세요.

경합은 용량보다 먼저 재설계해야 한다

전역 카운터, 단일 구성 행, 하나의 판매자 잔액은 서로 독립적인 요청도 직렬화할 수 있습니다. 노드를 늘려도 모든 트랜잭션이 같은 값을 갱신해야 한다는 논리적 요구는 없앨 수 없습니다.

일시적인 집계가 허용된다면 정확한 전역 카운터를 파티션된 카운터로 바꾸세요. 하나의 행을 자주 갱신하기보다 구성을 버전 관리하세요. 금전 상태는 정확성을 약화하지 말고, 불변 항목이나 독립된 하위 계정에서 동시성을 찾아 회계 불변 조건을 보존하세요.

긴 읽기-수정-쓰기 트랜잭션은 충돌을 더 악화시킵니다. 필요한 최소 집합만 읽고, 트랜잭션 안에서 사용자 상호작용을 피하며, 즉시 커밋하세요. 비즈니스 작업이 몇 분 걸린다면 여러 짧은 트랜잭션에 걸친 상태 기계로 표현하세요.

재시도 동작은 애플리케이션 계약에 속한다

드라이버는 개별 문장을 재시도하거나 재시도 가능한 오류를 애플리케이션 코드에 노출할 수 있습니다. 전체 트랜잭션 재실행을 어느 계층이 책임지는지 이해해야 합니다. 부분 재실행은 오래된 결정을 사용하거나 앞선 읽기를 빠뜨릴 수 있습니다.

재시도 루프에는 최대 시도 횟수, 무작위 백오프, 계측이 있어야 합니다. 충돌 유형, 영향받은 작업, 시도 횟수, 최종 결과를 기록하세요. 무제한 재시도는 경합을 숨은 지연 시간으로 바꾸고 클러스터에 과부하를 줄 수 있습니다.

비즈니스 요청에는 안정적인 식별자가 필요하므로 불확실한 클라이언트 응답을 안전하게 확인할 수 있습니다. 데이터베이스가 커밋했지만 응답이 유실됐다면, 클라이언트는 의미상 새로운 작업을 제출하는 대신 이미 정해진 작업을 조회해야 합니다.

스키마 변경에는 운영 규모 리허설이 필요하다

분산 스키마 변경은 메타데이터를 빠르게 갱신할 수 있지만 백필과 인덱스 생성은 백그라운드에서 계속됩니다. 이 작업은 스토리지, 네트워크, CPU를 사용하며 운영 쓰기와 상호작용할 수 있습니다.

확장 후 축소하는 마이그레이션을 사용하세요. 먼저 호환 가능한 필드나 테이블을 추가하고, 두 형식 모두에서 동작할 수 있는 코드를 배포하며, 제어된 배치로 백필한 뒤, 읽기를 전환하고, 검증 후 이전 형식을 제거합니다. 롤백 계획에는 새 버전이 쓴 데이터를 고려해야 합니다.

대규모 마이그레이션은 운영과 비슷한 데이터량과 리전 토폴로지에서 테스트하세요. 작은 스테이징 클러스터에서 빨리 끝난 변경도 운영에서는 몇 시간 걸리고 고객 트래픽과 경쟁할 수 있습니다. 시작 전 진행 상황, 중지 제어, 디스크 여유분, 재시도 동작을 모니터링하세요.

도입 체크리스트와 PoC

유용한 PoC는 대표 워크로드 하나를 명시적인 정확성, 지연 시간, 복원력, 비용 목표로 테스트합니다. 일반적인 벤치마크만으로 특정 스키마와 애플리케이션이 잘 동작할지 결정할 수 없습니다.

실제 제약을 가진 워크로드를 선택한다

희소 항목 예약, 원장 이체 기록, 필수 리전에 테넌트 프로비저닝 같은 워크플로를 고르세요. 운영에 가까운 스키마, 쿼리, 트랜잭션 경계, 페이로드 크기, 트래픽 편중을 재사용하세요.

테스트 전 성공 기준을 정의합니다.

  • 동시성과 재시도에서도 정확한 결과
  • 리전별 p50, p95, p99 지연 시간
  • 장애 여유분을 갖춘 지속적 피크 처리량
  • 노드와 리전 장애 중 복구 동작
  • 측정된 컴퓨팅, 스토리지, 네트워크 비용

안전 여유분은 임의의 배수가 아니라 예상 성장과 장애 용량에서 나와야 합니다. 리전 하나를 잃는 것이 범위에 포함된다면, 테스트 중 남은 위치가 전환된 부하를 처리할 수 있어야 합니다.

현실적인 애플리케이션 표면을 만든다

API와 작은 사용자 인터페이스는 데이터베이스 전용 도구가 놓칠 수 있는 트랜잭션 순서, 드라이버 동작, 사용자 체감 지연 시간을 드러냅니다. Koder.ai는 채팅을 통해 React 인터페이스, Go 백엔드, PostgreSQL 기준 구성을 만들 수 있습니다. 플래닝 모드는 생성 전 워크플로 정의를 도울 수 있고, 소스 코드 내보내기로 엔지니어가 후보 데이터베이스에 맞게 데이터 계층을 조정할 수 있습니다.

생성한 애플리케이션은 데이터베이스 호환성의 증명이 아니라 테스트용 뼈대로 사용하세요. 마이그레이션을 실행하고, 생성된 SQL을 검토하고, 공식 드라이버를 구성하며, 트랜잭션 재시도를 의도적으로 구현하세요. Koder.ai 스냅샷과 롤백은 애플리케이션 반복을 보호할 수 있지만 데이터베이스 백업이나 복구 훈련을 대체하지는 않습니다.

Koder.ai는 배포와 호스팅도 지원하므로 테스트 애플리케이션 인스턴스를 데이터베이스 리전 가까이에 둘 수 있습니다. 모든 벤치마크를 한 위치에서 실행하는 대신 전체 요청 경로를 측정할 수 있습니다. 환경이 운영 레코드에 필요한 통제를 갖추지 않았다면 테스트 데이터는 합성 데이터로 유지하세요.

정상 동작과 장애를 모두 실행한다

테스트는 안정 트래픽, 급증, 핫 파티션, 장기 실행 쿼리, 스키마 변경, 백업 작업, 노드 교체를 다뤄야 합니다. 그런 다음 승인된 테스트 환경 안에서 연결을 끊고 장애 도메인을 제거하세요.

트랜잭션 중단, 재시도 횟수, 사용 불가 응답, 리더 이동, 큐 깊이, 디스크 사용량, 리전 전송량을 수집하세요. 운영자가 해야 했던 작업도 기록하세요. 문서화되지 않은 수동 단계를 요구하는 자동 복구는 아직 운영 준비가 되지 않은 것입니다.

백업을 별도 환경에 복원하고 애플리케이션 불변 조건을 확인하세요. 재고라면 할당이 재고를 넘지 않는지 확인합니다. 원장이라면 잔액을 다시 계산하고 전기의 균형을 검증합니다. SaaS 테넌시라면 복원 뒤에도 배치와 접근 정책이 유지되었는지 확인합니다.

마이그레이션 전에 호환성을 검증한다

데이터베이스 확장, 저장 프로시저, 트리거, 데이터 타입, 격리 가정, ORM 기능, 보고 쿼리, 백업 도구, 관리 스크립트를 목록화하세요. 각 항목을 호환 가능, 대체 가능, 차단 요소로 분류합니다.

전체 크기 사본이나 생성 데이터셋에서 대표 마이그레이션을 실행하세요. 백필 기간, 변경 데이터 캡처 지연, 이중 운영 비용, 전환 시간을 측정합니다. 마이그레이션에 이중 쓰기가 있다면 불일치를 감지하는 방법과 각 단계에서 어느 시스템이 권위 있는지 정의하세요.

섀도 읽기는 운영 상태를 바꾸지 않고 결과를 비교할 수 있습니다. 예상된 변화를 손상으로 잘못 표시하지 않도록 시간 차이와 의도적으로 오래된 쿼리를 고려하세요. 트랜잭션 데이터에서 설명되지 않는 차이는 전환 전에 반드시 해결해야 합니다.

운영 준비 상태를 검토한다

운영 검토에서는 데이터베이스 운영, 애플리케이션 재시도, 보안, 레지던시 정책, 비용, 장애 대응의 담당자를 지정해야 합니다. 대시보드, 알림, 런북, 용량 임계값, 복원 증거, 롤백 결정 지점도 포함해야 합니다.

최종 결정은 PostgreSQL이나 MySQL을 계속 쓰는 것일 수도 있습니다. PoC는 분산 선택지가 현재 요구보다 비용이 크다는 사실을 보여 주더라도 신뢰할 수 있는 증거를 만들었다면 성공한 것입니다. 요구가 도입을 뒷받침한다면 점진적으로 마이그레이션하고 각 단계를 측정하며, 새 시스템이 실제 부하에서 검증될 때까지 테스트된 복귀 경로를 유지하세요.

자주 묻는 질문

쉽게 말해 “분산 SQL” 데이터베이스란 무엇인가요?

분산 SQL 데이터베이스는 테이블, 조인, 제약 조건, 트랜잭션 같은 관계형 SQL 인터페이스를 제공하면서 여러 장비, 흔히 여러 리전에 걸친 클러스터로 동작합니다. 애플리케이션에서는 하나의 논리적 데이터베이스처럼 보입니다.

실제로는 다음을 함께 제공하려는 방식입니다.

  • 익숙한 SQL/ACID 동작
  • 수평 확장, 즉 노드 추가
  • 수동 샤딩 없이 높은 가용성과 장애 허용
분산 SQL은 기존 PostgreSQL/MySQL 구성과 어떻게 다른가요?

단일 노드 또는 프라이머리/리플리카 RDBMS는 보통 단일 리전 OLTP에 더 단순하고, 비용이 낮으며, 빠릅니다.

다음이 대안이 될 때 분산 SQL의 장점이 커집니다.

  • 애플리케이션에서 직접 관리하는 샤딩
  • 복잡한 멀티 리전 장애 조치
  • 영역이나 리전을 넘는 강한 일관성 요구
  • 하나의 운영 모델로 해결해야 하는 데이터 레지던시 요구
분산 SQL 시스템은 왜 Raft나 Paxos 같은 합의 프로토콜을 사용하나요?

대부분의 시스템은 두 가지 핵심 원리에 의존합니다.

  • 복제: 각 데이터 샤드/파티션을 여러 노드에 저장합니다.
  • 합의: Raft나 Paxos 같은 방식으로 리플리카가 쓰기 순서에 동의하며, 커밋에는 대개 과반수의 확인이 필요합니다.

덕분에 노드 장애 중에도 강한 일관성을 유지할 수 있지만, 네트워크 조정 비용이 추가됩니다.

데이터는 노드와 리전 전체에 어떻게 분할되고 배치되나요?

테이블을 더 작은 단위로 나눕니다. 이를 흔히 파티션/샤드라고 하며, 제품에 따라 range, tablet, split 같은 이름을 씁니다. 각 파티션은 다음 특성을 가집니다.

  • 자체 리플리카 그룹이 있음
  • 특정 노드나 리전에 배치 가능
  • 클러스터 재균형 과정에서 이동 가능

일반적으로 정책으로 배치에 영향을 주어 핫 데이터와 주된 쓰기 주체를 가깝게 두고 네트워크 왕복을 줄입니다.

분산 SQL에서, 특히 여러 리전을 오가는 트랜잭션이 느릴 수 있는 이유는 무엇인가요?

분산 트랜잭션은 여러 노드, 때로는 여러 리전에 있는 여러 파티션을 건드릴 수 있습니다. 안전한 커밋에는 보통 다음이 필요합니다.

  • 참여자 전체의 잠금 또는 검증
  • 복제 확인, 즉 쿼럼
  • 조정된 커밋 결정

이 추가 네트워크 왕복이 쓰기 지연 시간이 길어지는 주된 이유이며, 합의가 여러 리전에 걸치면 특히 두드러집니다.

정말 분산 SQL이 필요하다는 가장 분명한 신호는 무엇인가요?

다음 중 두 가지 이상에 해당하면 분산 SQL을 검토해 보세요.

  • 여러 리전에 중요한 사용자가 있고 일관된 데이터를 제공해야 함
  • 영역/리전을 넘는 자동 장애 조치가 필요함, RTO/RPO가 엄격함
  • 쓰기 부하에서 수직 확장만으로는 부족함
  • 핵심 트랜잭션에서 강한 일관성이 필요함, 예: 돈, 재고, 예약
  • 규정 준수를 위해 데이터의 지역 배치가 필요함

워크로드가 리플리카와 캐시를 둔 하나의 리전에 잘 맞는다면, 일반적인 RDBMS가 보통 더 나은 기본 선택입니다.

강한 일관성은 무엇을 얻고, 무엇을 비용으로 치르나요?

강한 일관성은 트랜잭션이 커밋된 뒤 읽기가 이전 데이터를 보지 않게 한다는 뜻입니다.

제품 관점에서는 다음을 막는 데 도움이 됩니다.

  • 이중 지불 또는 잘못된 잔액
  • 마지막 상품의 초과 판매
  • 두 사용자가 같은 좌석을 예약하는 일

대신 네트워크 파티션 중에는 서로 다른 상태를 받아들이기보다 일부 작업을 차단하거나 실패시킬 수 있습니다.

분산 SQL에서 재시도와 멱등성을 안전하게 처리하려면 어떻게 해야 하나요?

데이터베이스 제약 조건과 트랜잭션에 의존하세요.

  • 요청/시도마다 idempotency_key 같은 값을 저장
  • (account_id, idempotency_key) 같은 고유 제약 조건 추가
  • 하나의 트랜잭션에서 비즈니스 레코드와 원장/아웃박스 행 작성

이렇게 하면 재시도가 중복 생성이 아니라 무작업이 됩니다. 결제, 프로비저닝, 백그라운드 작업 재처리에 특히 중요합니다.

Spanner, CockroachDB, YugabyteDB 중 무엇을 선택해야 하나요?

실무적으로는 이렇게 구분할 수 있습니다.

  • Spanner: 일반적으로 GCP에서 관리형으로 사용하며, 멀티 리전 설계 경험이 풍부합니다. SQL 방언 선택이 이식성에 영향을 줍니다.
  • CockroachDB: PostgreSQL과 유사한 경험과 와이어 프로토콜을 제공하며, 관리형 또는 자체 호스팅으로 쓸 수 있습니다. PostgreSQL과 100% 호환되지는 않습니다.
  • YugabyteDB: PostgreSQL 호환 SQL API인 YSQL과 선택 가능한 Cassandra 스타일 API인 YCQL을 제공합니다. 관리형 또는 자체 호스팅으로 쓸 수 있습니다.

선택 전에는 실제 ORM, 마이그레이션, 의존 중인 PostgreSQL 확장을 테스트하세요. 그대로 교체할 수 있다고 가정하면 안 됩니다.

분산 SQL 도입 전에 어떤 PoC 계획을 세우면 좋나요?

결제, 예약, 원장 기록처럼 중요한 워크플로 하나를 중심으로 PoC를 시작하세요. 다음을 검증합니다.

  • 정확성, 이중 예약이나 유실된 업데이트가 없는지
  • 주요 쿼리의 p50/p95 지연 시간, 리전 간 목표 포함
  • 장애 동작, 노드 손실, 영역 손실, 필요하면 리전 손실
  • 운영 기본 사항, 모니터링, 백업, 복구 훈련

비용이나 티어 범위를 정하는 데 도움이 필요하면 가격 정보를 확인하세요. 관련 구현 노트는 블로그에서 볼 수 있습니다.

Related posts