RDB와 NoSQL로 살펴보는 데이터베이스 확장성 전략

확장성이라는 키워드에 대해 RDB와 NoSQL이 각각 어떠한 접근법으로 해당 이슈를 해결하였는지 알아보고자 한다.


1. 확장성의 두 가지 패러다임: 수직적 vs 수평적 접근

현대 비즈니스에서 데이터베이스 시스템은 끊임없이 증가하는 데이터 양과 사용자의 요청을 처리해야 한다. 이러한 확장 요구에 대응하기 위해 두가지 근본적으로 다른 접근법이 발전해왔다. 수직적 확장(Scale-up)수평적 확장(Scale-out)이다.

수직적 확장은 기존 서버의 하드웨어 성능을 향상시키는 전략이다. CPU 성능 강화, 메모리 용량 확대, 고성능 스토리지 도입 등을 통해 단일 서버의 처리 능력을 높이는 방식이다. 반면, 수평적 확장은 여러 대의 서버를 네트워크로 연결하여 부하를 분산시키는 방식으로, 데이터와 작업을 여러 노드에 분산하여 처리한다.

필자는 이 두 접근법이 단순한 기술적 선택을 넘어 데이터베이스 설계 철학의 차이를 반영한다고 본다. 관계형 데이터베이스(RDB)는 전통적으로 수직적 확장에 최적화되었으며, NoSQL 데이터베이스는 태생적으로 수평적 확장을 염두에 두고 설계되었다.

인프라 확장에 관한 기본 개념은 필자의 이전 글 Scale-up & Scale-out에서 더 자세히 다루었으니 참고하기 바란다. 해당 글에서는 서버 인프라 관점에서의 수직적, 수평적 확장 방식의 기본 개념과 장단점을 비교하고 있다.

2. RDB의 전통적 확장성 접근법: 성능과 일관성 우선

관계형 데이터베이스는 1970년대 Edgar F. Codd의 관계 모델을 기반으로 발전했으며, 정규화된 테이블 구조와 ACID(원자성, 일관성, 고립성, 지속성) 트랜잭션을 핵심 가치로 삼는다. 이러한 특성은 데이터 일관성과 무결성을 보장하지만, 분산 환경에서는 상당한 제약으로 작용한다.

2.1. RDB의 수직적 확장 전략

Oracle, MySQL, PostgreSQL과 같은 전통적인 RDB 시스템은 주로 수직적 확장에 의존한다. 더 강력한 CPU, 더 많은 메모리, 더 빠른 스토리지를 추가함으로써 성능을 향상시킨다. 이 접근법의 장점은 구현이 비교적 간단하고 애플리케이션 코드 변경이 최소화된다는 것이다.

그러나 수직적 확장은 명확한 한계가 있다.

  1. 물리적 한계: 단일 서버에 추가할 수 있는 하드웨어 자원에는 물리적 한계가 있다.
  2. 비용 증가 곡선: 고사양 하드웨어로 갈수록 비용이 기하급수적으로 증가한다.
  3. 단일 장애점: 서버 장애 시 전체 시스템이 영향을 받는다.
  4. 다운타임: 하드웨어 업그레이드 시 시스템 중단이 불가피한 경우가 많다.

2.2. RDB의 수평적 확장 시도

RDB 시스템도 수평적 확장을 시도해왔지만, 근본적인 설계 철학으로 인해 복잡성이 증가한다.

  1. 파티셔닝(Partitioning): 테이블을 여러 파티션으로 나누어 관리하는 방식이다. 수평 파티셔닝(샤딩)과 수직 파티셔닝이 있으며, 대부분의 상용 RDB에서 지원한다.
  2. 페더레이션(Federation): 기능별로 데이터베이스를 분리하는 방식이다. 예를 들어, 사용자 데이터, 상품 데이터, 주문 데이터를 별도의 데이터베이스로 관리한다.
  3. 읽기 전용 복제본(Read Replicas): 마스터 데이터베이스의 복제본을 생성하여 읽기 쿼리를 분산시키는 방식이다.
예시: Oracle Exadata와 같은 고성능 데이터베이스 어플라이언스는 수직적 확장의 정점을 보여준다. 
수백 개의 CPU 코어, 테라바이트 단위의 메모리, 최적화된 스토리지 서브시스템을 결합하여 
단일 시스템에서 최대한의 성능을 발휘하도록 설계되었다.

그러나 이러한 접근법들은 분산 트랜잭션, 조인 쿼리 처리, 스키마 변경 등에서 상당한 복잡성을 야기한다. 특히 ACID 속성을 유지하면서 분산 환경에서 일관성을 보장하는 것은 어려운 과제이다.

3. NoSQL의 혁신적 확장성 접근법: 분산과 유연성 우선

NoSQL 데이터베이스는 2000년대 후반부터 웹 규모의 애플리케이션 요구를 충족시키기 위해 발전했다. 관계형 모델의 제약에서 벗어나 다양한 데이터 모델(문서, 키-값, 컬럼 기반, 그래프 등)을 채택하고, BASE(기본 가용성, 소프트 상태, 최종 일관성) 원칙을 따른다.

3.1. NoSQL의 수평적 확장 설계 철학

NoSQL 데이터베이스는 RDB와는 다르게, 처음부터 분산 환경을 고려하여 설계되었다.

  1. 분산 아키텍처: 데이터를 여러 노드1에 분산하여 저장하고 처리하는 구조가 기본이다.
  2. 느슨한 일관성 모델: 강한 일관성 대신 최종 일관성(Eventual Consistency)을 채택하여 분산 환경에서의 성능과 가용성을 최적화한다.
  3. 스키마리스 또는 유연한 스키마: 엄격한 스키마 정의 없이 데이터를 저장할 수 있어 변화에 빠르게 대응할 수 있다.
  4. 수평적 확장 내장 기능: 샤딩, 레플리케이션과 같은 분산 데이터 관리 기능이 기본적으로 내장되어 있다.
예시: MongoDB의 샤딩 아키텍처는 Config Server, Shard Server, Query Router로 구성되어
자동으로 데이터를 분산하고 쿼리를 라우팅한다. 새로운 샤드를 추가하는 것만으로
전체 시스템의 용량과 처리량을 선형적으로 확장할 수 있다.

NoSQL의 분산 설계는 다음과 같은 장점을 제공한다.

  1. 선형적 확장성: 노드 추가에 따라 거의 선형적으로 성능이 향상된다.
  2. 고가용성: 여러 노드에 데이터가 복제되어 있어 일부 노드 장애에도 서비스가 유지된다.
  3. 지리적 분산: 전 세계에 데이터를 분산하여 지연 시간을 최소화할 수 있다.
  4. 비용 효율성: 일반 상용 서버를 사용하여 대규모 클러스터를 구성할 수 있다.

그러나 이러한 이점은 강한 일관성의 포기, 복잡한 쿼리 지원 제한, 트랜잭션 지원 제한 등의 트레이드오프를 수반하게 된다.

4. 샤딩(Sharding): RDB와 NoSQL의 접근 차이

샤딩대규모 데이터셋을 여러 서버에 분산하는 기술로, 수평적 확장의 핵심 요소이다. 그러나 RDB와 NoSQL은 샤딩에 대해 근본적으로 다른 접근법을 취한다.

4.1. RDB에서의 샤딩: 복잡한 후속 추가 기능

관계형 데이터베이스에서 샤딩은 일반적으로 후속 추가된 기능으로, 복잡한 구현이 필요하다.

  1. 애플리케이션 레벨 샤딩: 많은 경우 애플리케이션 코드에서 샤딩 로직을 구현해야 한다. 이는 개발 복잡성을 증가시키고 애플리케이션에 샤딩 로직이 침투하게 한다.
  2. 샤딩 미들웨어: MySQL의 ProxySQL, PostgreSQL의 Citus와 같은 미들웨어를 사용하여 샤딩을 구현한다. 이는 추가적인 계층과 복잡성을 도입한다.
  3. 분산 트랜잭션의 복잡성: 여러 샤드2에 걸친 트랜잭션은 2단계 커밋과 같은 복잡한 프로토콜이 필요하며, 성능 저하를 유발할 수 있다.
  4. 조인 연산의 제약: 다른 샤드에 있는 테이블 간의 조인은 매우 비효율적이거나 불가능할 수 있다.
MySQL 샤딩 예시 (ProxySQL 사용):

-- 샤딩 규칙 설정
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (1, 1, '^SELECT .* WHERE user_id=([0-9]+)', 
        'user_id % 4 + 1', 1);

-- 애플리케이션에서 샤드 키에 따른 라우팅 처리가 필요하다

4.2. NoSQL에서의 샤딩: 기본 내장 기능

반면 NoSQL 데이터베이스는 처음부터 샤딩을 고려하여 설계되었다.

  1. 자동 샤딩: 대부분의 NoSQL 시스템은 자동 샤딩 기능을 제공하며, 클러스터에 노드를 추가하면 데이터가 자동으로 재분배된다.
  2. 샤드 키와 데이터 모델의 통합: 데이터 모델 자체가 효율적인 샤딩을 고려하여 설계된다. 예를 들어, MongoDB의 문서 모델은 관련 데이터를 함께 저장하여 샤드 간 조인의 필요성을 줄인다.
  3. 분산 쿼리 처리: 쿼리 엔진이 분산 환경에 최적화되어 있어 여러 샤드에 걸친 쿼리를 자동으로 처리한다.
  4. 균형 조정 메커니즘: 자동 리밸런싱 기능을 통해 데이터가 샤드 간에 고르게 분포되도록 관리한다.
MongoDB 샤딩 예시:

// 샤딩 활성화 및 샤드 키 설정
sh.enableSharding("myDatabase")
sh.shardCollection("myDatabase.users", { "user_id": "hashed" })

// 추가 구성 없이 클러스터에 새 샤드 추가 가능
sh.addShard("newShardServer:27017")

5. 레플리케이션(Replication): 일관성과 가용성의 균형

레플리케이션동일한 데이터를 여러 노드에 복제하여 가용성을 높이고 읽기 성능을 향상시키는 기술이다. RDB와 NoSQL 모두 레플리케이션을 지원하지만, 그 구현과 철학에는 차이가 있다.

5.1. RDB의 레플리케이션: 마스터-슬레이브 모델과 강한 일관성

관계형 데이터베이스는 전통적으로 마스터-슬레이브(혹은 프라이머리-레플리카라고도 한다) 모델을 채택한다.

  1. 중앙 집중식 구조: 하나의 마스터 노드가 모든 쓰기 작업을 처리하고, 여러 슬레이브 노드가 읽기 작업을 담당한다.
  2. 동기식 vs 비동기식 복제: 동기식 복제는 트랜잭션이 모든 또는 지정된 수의 레플리카에 적용된 후에만 완료되어 강한 일관성을 보장하지만 지연 시간이 증가한다. 비동기식 복제는 성능을 우선시하지만 일시적인 데이터 불일치가 발생할 수 있다.
  3. 자동 장애 조치(Failover): 마스터 노드 장애 시 슬레이브를 마스터로 승격시키는 메커니즘이 필요하며, 이는 복잡한 설정과 모니터링을 요구한다.
MySQL 레플리케이션 설정 예시:

-- 마스터 설정
server-id = 1
log_bin = mysql-bin
binlog_format = ROW

-- 슬레이브 설정
server-id = 2
relay-log = slave-relay-bin

-- 슬레이브에서 레플리케이션 시작
CHANGE MASTER TO
  MASTER_HOST='master_host',
  MASTER_USER='replication_user',
  MASTER_PASSWORD='password',
  MASTER_LOG_FILE='mysql-bin.000001',
  MASTER_LOG_POS=4;
START SLAVE;

5.2. NoSQL의 레플리케이션: 분산 합의와 결과적 일관성

NoSQL 시스템은 더 분산된 레플리케이션 모델을 채택한다.

  1. P2P 레플리케이션: 많은 NoSQL 데이터베이스는 모든 노드가 동등한 피어-투-피어 레플리케이션 모델을 사용한다. 예를 들어, Cassandra의 모든 노드는 읽기와 쓰기 모두 처리할 수 있다.
  2. 쿼럼 기반 일관성: 쓰기 작업 시 지정된 수(W)의 노드에 데이터가 기록되고, 읽기 작업 시 지정된 수(R)의 노드에서 데이터를 읽어오는 방식이다. W + R > N(전체 레플리카 수)일 경우 강한 일관성을 보장할 수 있다.
  3. 벡터 클럭과 충돌 해결: 분산 환경에서 발생하는 데이터 충돌을 감지하고 해결하기 위한 메커니즘을 내장하고 있다.
  4. 최종 일관성(Eventual Consistency): 일시적인 불일치를 허용하지만, 최종적으로는 모든 레플리카가 동일한 상태로 수렴한다.
Cassandra 레플리케이션 설정 예시:

CREATE KEYSPACE my_keyspace
WITH replication = {
  'class': 'NetworkTopologyStrategy',
  'datacenter1': 3,
  'datacenter2': 2
};

-- 읽기 일관성 레벨 설정
CONSISTENCY QUORUM;

6. 클러스터링(Clustering): 단일 시스템 vs 분산 시스템

클러스터링여러 서버가 하나의 시스템처럼 동작하게 하는 기술이다. RDB와 NoSQL은 클러스터링에 대해 근본적으로 다른 접근법을 취한다.

6.1. RDB의 클러스터링: 공유 스토리지와 중앙 조정

관계형 데이터베이스 클러스터링은 주로 고가용성과 장애 복구에 초점을 맞추며, 종종 공유 스토리지 아키텍처를 채택한다.

  1. 공유 디스크 아키텍처: Oracle RAC(Real Application Clusters)와 같은 시스템은 모든 노드가 공유 스토리지에 접근하는 방식을 사용한다. 이는 데이터 일관성을 유지하기 쉽지만, 스토리지 병목 현상이 발생할 수 있다.
  2. 액티브-패시브 구성: 하나의 노드만 활성 상태로 모든 작업을 처리하고, 다른 노드는 대기 상태에 있다가 활성 노드 장애 시 작업을 인계받는 방식이다.
  3. 중앙집중식 잠금 관리: 데이터 일관성을 유지하기 위해 중앙에서 관리되는 잠금 메커니즘을 사용한다.
  4. 분산 트랜잭션 관리자: 2단계 커밋과 같은 프로토콜을 통해 분산 환경에서의 트랜잭션 일관성을 보장한다.
Oracle RAC 구성 예시:

-- 클러스터 확인
SELECT inst_id, instance_name, host_name, status 
FROM gv$instance;

-- 서비스 등록
EXECUTE DBMS_SERVICE.CREATE_SERVICE(
  service_name => 'sales_service',
  network_name => 'sales.example.com',
  goal => DBMS_SERVICE.GOAL_SERVICE_TIME,
  clb_goal => DBMS_SERVICE.CLB_GOAL_SHORT);

6.2. NoSQL의 클러스터링: 비공유 아키텍처와 분산 조정

NoSQL 데이터베이스는 일반적으로 공유 없는(shared-nothing) 아키텍처를 채택한다:

  1. 비공유 아키텍처: 각 노드는 자체 CPU, 메모리, 디스크를 가지며, 노드 간 통신은 네트워크를 통해 이루어진다. 이는 선형적인 확장성을 제공하지만, 분산 조정이 복잡해진다.
  2. 분산 합의 알고리즘: Paxos, Raft와 같은 알고리즘을 사용하여 분산 환경에서의 합의를 이루고 클러스터 상태를 관리한다.
  3. 무중단 운영: 노드 추가, 제거, 장애 등의 상황에서도 서비스 중단 없이 계속 운영할 수 있도록 설계되었다.
  4. 자동 데이터 재분배: 클러스터 토폴로지 변경 시 데이터가 자동으로 재분배된다.
Elasticsearch 클러스터 구성 예시:

# elasticsearch.yml
cluster.name: my-cluster
node.name: node-1
discovery.seed_hosts: ["node-1", "node-2", "node-3"]
cluster.initial_master_nodes: ["node-1"]

# 클러스터 상태 확인
GET _cluster/health

7. 쿼리 처리와 확장성: 두 패러다임의 접근 차이

데이터베이스의 주요 기능 중 하나는 효율적인 쿼리 처리이다. RDB와 NoSQL은 확장성 측면에서 쿼리 처리에 근본적인 차이가 있다.

7.1. RDB의 복잡한 쿼리와 조인 작업

관계형 데이터베이스는 복잡한 SQL 쿼리와 조인 작업에 최적화되어 있다.

  1. 관계 모델과 조인: 정규화된 테이블 간의 조인 연산을 통해 복잡한 질의를 처리한다. 이는 단일 시스템에서는 효율적이지만, 분산 환경에서는 상당한 병목을 유발할 수 있다.
  2. 쿼리 옵티마이저: 복잡한 쿼리 계획을 생성하고 최적화하여 효율적인 실행 경로를 찾는다. 그러나 분산 환경에서는 전역적인 최적화가 어렵다.
  3. 트랜잭션과 잠금: ACID 트랜잭션을 지원하기 위해 잠금 기반의 동시성 제어를 사용하며, 이는 확장성에 제약을 가져올 수 있다.
복잡한 SQL 쿼리 예시:

SELECT c.customer_name, 
       SUM(o.order_amount) as total_amount,
       COUNT(DISTINCT p.product_id) as product_count
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE o.order_date BETWEEN '2023-01-01' AND '2023-12-31'
GROUP BY c.customer_name
HAVING SUM(o.order_amount) > 1000
ORDER BY total_amount DESC;

분산 환경에서 이러한 복잡한 쿼리를 효율적으로 처리하는 것은 어려운 과제이다. 특히 여러 샤드에 걸친 조인 작업은 네트워크 오버헤드와 분산 트랜잭션의 복잡성으로 인해 성능이 크게 저하될 수 있다.

7.2. NoSQL의 단순 쿼리와 비정규화 전략

NoSQL 데이터베이스는 단순한 쿼리 패턴과 비정규화된 데이터 모델을 채택한다.

  1. 목적 지향적 쿼리: 특정 액세스 패턴에 최적화된 단순한 쿼리를 사용한다. 예를 들어, 키-값 조회, 문서 ID 기반 검색, 제한된 범위의 스캔 등을 주로 사용한다.
  2. 비정규화와 중복: 조인 작업의 필요성을 줄이기 위해 데이터 중복을 허용하고, 액세스 패턴에 맞게 데이터를 비정규화한다. 이는 쓰기 작업 시 추가적인 복잡성을 유발할 수 있지만, 읽기 성능을 크게 향상시킨다.
  3. 분산 쿼리 최적화: 로컬 실행이 가능한 쿼리를 선호하며, 필요한 경우에만 제한된 범위에서 분산 처리를 수행한다.
MongoDB 비정규화 예시 (주문과 고객 정보 포함):

db.orders.insertOne({
  order_id: "12345",
  order_date: ISODate("2023-10-15"),
  order_amount: 1500,
  customer: {
    customer_id: "C1001",
    name: "John Doe",
    email: "john@example.com"
  },
  items: [
    { product_id: "P100", name: "Laptop", price: 1200, quantity: 1 },
    { product_id: "P101", name: "Mouse", price: 50, quantity: 1 },
    { product_id: "P102", name: "Keyboard", price: 80, quantity: 1 }
  ]
})

// 간단한 조회 쿼리 - 조인 필요 없음
db.orders.find({"customer.name": "John Doe"})

8. 하이브리드 접근법: 두 세계의 장점 결합

현대 시스템에서는 RDB와 NoSQL의 장점을 결합한 하이브리드 접근법이 점점 더 인기를 얻고 있다.

8.1. 폴리글랏 퍼시스턴스(Polyglot Persistence)

다양한 데이터 유형과 액세스 패턴에 따라 적절한 데이터베이스를 선택하는 전략이다.

  1. 트랜잭션 데이터: 금융 거래, 주문 처리와 같은 ACID 트랜잭션이 중요한 데이터는 RDB에 저장한다.
  2. 대용량 데이터: 로그, 센서 데이터, 클릭스트림과 같은 대용량 데이터는 확장성이 뛰어난 NoSQL 시스템에 저장한다.
  3. 그래프 데이터: 소셜 관계, 네트워크 토폴로지와 같은 복잡한 연결 데이터는 그래프 데이터베이스에 저장한다.
  4. 검색 데이터: 전문 검색이 필요한 데이터는 Elasticsearch와 같은 검색 엔진에 저장한다.

8.2. CQRS(Command Query Responsibility Segregation) 패턴

명령(쓰기)과 쿼리(읽기) 작업을 분리하는 아키텍처 패턴이다.

  1. 명령 처리: 데이터 변경 작업은 강한 일관성이 필요하므로 RDB에서 처리한다.
  2. 쿼리 처리: 읽기 작업은 확장성과 성능이 중요하므로 비정규화된 NoSQL 저장소나 특화된 읽기 모델에서 처리한다.
  3. 이벤트 소싱: 데이터 변경을 이벤트 로그로 저장하고, 이를 기반으로 다양한 읽기 모델을 구성한다.
CQRS 아키텍처 예시:

// 명령 처리 (RDB)
@Transactional
public void createOrder(OrderCommand command) {
    // 트랜잭션 일관성이 중요한 주문 생성 로직
    Order order = new Order(command.getCustomerId(), command.getItems());
    orderRepository.save(order);
    
    // 이벤트 발행
    eventPublisher.publish(new OrderCreatedEvent(order));
}

// 쿼리 처리 (NoSQL)
public OrderSummary getOrderSummary(String orderId) {
    // 비정규화된 읽기 모델에서 조회
    return orderSummaryRepository.findById(orderId);
}

8.3. 마이크로서비스 아키텍처의 데이터 전략

마이크로서비스에서는 각 서비스가 자체 데이터 저장소를 가지는 것이 일반적이다.

  1. 데이터베이스 per 서비스: 각 마이크로서비스가 독립적인 데이터베이스를 사용하여 기술 선택의 자유도를 높이고 결합도를 낮춘다.
  2. API 컴포지션: 여러 서비스의 데이터를 조합해야 하는 경우, 데이터베이스 수준의 조인 대신 API 호출을 통해 데이터를 조합한다.
  3. 이벤트 기반 데이터 동기화: 서비스 간 데이터 일관성을 유지하기 위해 이벤트 기반 비동기 통신을 사용한다.

이러한 하이브리드 접근법은 RDB의 트랜잭션 일관성과 NoSQL의 확장성을 모두 활용할 수 있는 균형 잡힌 아키텍처를 제공한다.

9. 클라우드 환경에서의 RDB와 NoSQL 확장성

클라우드 컴퓨팅의 발전으로 데이터베이스 확장성 접근법에도 변화가 생겼다. 클라우드 환경에서 RDB와 NoSQL은 각각 다른 방식으로 확장된다.

9.1. 관리형 RDB 서비스의 확장 옵션

AWS RDS, Azure SQL Database, Google Cloud SQL과 같은 관리형 RDB 서비스는 다음과 같은 확장 옵션을 제공한다:

  1. 자동 수직 확장: 워크로드에 따라 인스턴스 유형을 자동으로 조정하는 기능을 제공한다.
  2. 읽기 복제본: 읽기 트래픽을 분산시키기 위한 읽기 전용 복제본을 쉽게 생성할 수 있다.
  3. 다중 AZ 배포: 고가용성을 위해 여러 가용 영역에 데이터베이스를 복제한다.
  4. 프록시 및 연결 풀링: 데이터베이스 연결을 효율적으로 관리하여 확장성을 향상시킨다.

그러나 클라우드 RDB도 여전히 샤딩과 같은 수평적 확장에는 제약이 있으며, 대부분 수직적 확장에 의존하고 있다.

9.2. 클라우드 네이티브 NoSQL 서비스

DynamoDB, Cosmos DB, Google Cloud Firestore와 같은 클라우드 네이티브 NoSQL 서비스는 처음부터 클라우드 환경을 고려하여 설계되었다.

  1. 자동 확장: 처리량과 스토리지 요구사항에 따라 자동으로 확장되며, 사용자는 명시적인 클러스터 관리가 필요 없다.
  2. 서버리스 모델: 프로비저닝된 용량이 아닌 실제 사용한 만큼만 비용을 지불하는 서버리스 모델을 제공한다.
  3. 글로벌 분산: 전 세계 리전에 데이터를 자동으로 복제하여 지연 시간을 최소화하고 가용성을 높인다.
  4. 탄력적 확장: 트래픽 패턴에 따라 읽기/쓰기 처리량을 즉시 조정할 수 있다.
AWS DynamoDB 자동 확장 설정 예시:

aws dynamodb update-table \
  --table-name Orders \
  --provisioned-throughput \
    ReadCapacityUnits=10,WriteCapacityUnits=5 \
  --billing-mode PROVISIONED \
  --auto-scaling-settings-update \
    IndexName=GSI1,MinimumCapacityUnits=5,MaximumCapacityUnits=100

10. 언제 어떤 접근법을 선택할 것인가?

데이터베이스 확장성 전략을 선택할 때는 여러 요소를 고려해야 한다. 다음은 필자가 생각하는 데이터베이스 의사 결정을 위한 예시이다.

10.1. RDB(수직적 확장) 선택이 적합한 경우

  1. 복잡한 트랜잭션이 필요한 경우: 여러 엔티티에 걸친 ACID 트랜잭션이 중요한 경우 (금융 거래, ERP 시스템 등)
  2. 복잡한 쿼리와 조인이 빈번한 경우: 다양한 테이블 간 복잡한 관계를 조회해야 하는 경우
  3. 데이터 무결성과 일관성이 최우선인 경우: 데이터 정확성이 가용성보다 중요한 경우
  4. 기존 시스템과의 호환성이 필요한 경우: 레거시 시스템과 통합해야 하는 경우
  5. 데이터 볼륨이 예측 가능하고 중간 규모인 경우: 데이터 증가율이 완만하고 관리 가능한 수준인 경우

10.2. NoSQL(수평적 확장) 선택이 적합한 경우

  1. 대규모 데이터와 트래픽이 예상되는 경우: 페타바이트 규모의 데이터나 초당 수만 건의 요청을 처리해야 하는 경우
  2. 글로벌 분산이 필요한 경우: 전 세계 사용자에게 낮은 지연 시간을 제공해야 하는 경우
  3. 가용성이 일관성보다 중요한 경우: 시스템이 항상 응답 가능해야 하며, 일시적인 불일치를 허용할 수 있는 경우
  4. 유연한 스키마가 필요한 경우: 데이터 구조가 자주 변경되거나 다양한 형태의 데이터를 저장해야 하는 경우
  5. 읽기 중심 워크로드가 많은 경우: 쓰기보다 읽기 작업이 훨씬 많은 경우

10.3. 하이브리드 접근법이 적합한 경우

  1. 다양한 데이터 유형과 액세스 패턴이 혼재된 경우: 트랜잭션 데이터, 분석 데이터, 로그 데이터 등 다양한 유형의 데이터를 처리해야 하는 경우
  2. 읽기와 쓰기 요구사항이 크게 다른 경우: CQRS 패턴을 적용하여 쓰기는 RDB, 읽기는 NoSQL로 분리할 수 있는 경우
  3. 점진적인 마이그레이션이 필요한 경우: 기존 RDB 시스템에서 NoSQL로 점진적으로 전환해야 하는 경우
  4. 마이크로서비스 아키텍처를 채택한 경우: 각 서비스의 특성에 맞는 데이터 저장소를 선택할 수 있는 경우

11. 결론

필자는 데이터베이스 확장성이 단순한 기술적 선택을 넘어 비즈니스 요구사항, 데이터 특성, 운영 환경 등 다양한 요소를 고려해야 하는 복잡한 결정이라고 생각한다. RDB의 수직적 확장과 NoSQL의 수평적 확장은 각각 고유한 장단점을 가지고 있으며, 현대 시스템에서는 이 두 접근법을 조합한 하이브리드 전략이 점점 더 보편화되고 있다고 한다. 필자 역시도 이전 프로젝트(BooCrum)를 개발할 당시, 화이트보드 오브젝트는 NoSQL(MongoDB)를 활용하고 그 외 메타데이터의 경우에는 RDB(MySQL)를 사용하도록 의사 결정한 경험이 있다.

샤딩, 레플리케이션, 클러스터링과 같은 핵심 기술들은 RDB와 NoSQL 시스템에서 다른 방식으로 구현되지만, 모두 확장성, 가용성, 성능의 발전이라는 공통의 목표를 향해 나아가고 있다. 클라우드 컴퓨팅의 발전은 이러한 기술들을 더욱 접근하기 쉽고 관리하기 쉽게 만들고 있다.

결국, 확장성 전략의 성공적인 구현은 기술적 깊이와 함께 비즈니스 요구사항에 대한 명확한 이해, 그리고 현재와 미래의 데이터 특성을 고려한 균형 잡힌 접근에 달려 있기 때문에 개발자의 전략적 사고가 필수적이라고 판단된다.


  1. 분산 데이터베이스 시스템을 구성하는 개별 서버 또는 인스턴스 ↩︎

  2. 데이터베이스를 여러 작은 부분으로 분할하여 저장한 각각의 데이터 조각 ↩︎