데이터베이스 샤딩과 파티셔닝
1. 개요
데이터베이스 규모가 커질수록 성능과 확장성 문제가 발생합니다. 이러한 문제를 해결하기 위한 핵심 전략으로 샤딩(Sharding)과 파티셔닝(Partitioning)이 있습니다. 두 기법 모두 대용량 데이터를 관리하는 방법이지만, 구현 방식과 목적에 차이가 있습니다.
2. 파티셔닝(Partitioning)이란?
파티셔닝은 대용량 테이블이나 인덱스를 관리하기 쉬운 작은 단위로 분할하는 기법입니다. 모든 파티션은 동일한 데이터베이스 서버에 존재하며, 논리적으로는 하나의 테이블처럼 작동합니다.
2.1 파티셔닝의 주요 유형
2.1.1 수평 파티셔닝 (Horizontal Partitioning)
행(row) 기준으로 데이터를 분할합니다. 각 파티션은 동일한 스키마를 가지지만 서로 다른 행들을 포함합니다.
1
2
3
4
5
6
7
8
9
10
11
-- 날짜 기준 수평 파티셔닝 예시 (MySQL)
CREATE TABLE sales (
id INT,
sale_date DATE,
amount DECIMAL(10,2)
) PARTITION BY RANGE (YEAR(sale_date)) (
PARTITION p0 VALUES LESS THAN (2020),
PARTITION p1 VALUES LESS THAN (2021),
PARTITION p2 VALUES LESS THAN (2022),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
2.1.2 수직 파티셔닝 (Vertical Partitioning)
열(column) 기준으로 데이터를 분할합니다. 자주 사용하는 열과 그렇지 않은 열을 분리하여 성능을 최적화합니다.
2.2 파티셔닝 전략
2.2.1 범위 파티셔닝 (Range Partitioning)
특정 범위의 값을 기준으로 데이터를 분할합니다. 날짜, 숫자 등의 연속적인 값에 적합합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
-- PostgreSQL에서의 범위 파티셔닝 예시
CREATE TABLE measurements (
city_id int not null,
logdate date not null,
peaktemp int,
unitsales int
) PARTITION BY RANGE (logdate);
CREATE TABLE measurements_y2020 PARTITION OF measurements
FOR VALUES FROM ('2020-01-01') TO ('2021-01-01');
CREATE TABLE measurements_y2021 PARTITION OF measurements
FOR VALUES FROM ('2021-01-01') TO ('2022-01-01');
2.2.2 리스트 파티셔닝 (List Partitioning)
명확한 값 목록을 기준으로 데이터를 분할합니다. 지역, 카테고리 등 이산적인 값에 적합합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- Oracle에서의 리스트 파티셔닝 예시
CREATE TABLE sales (
prod_id NUMBER(6),
cust_id NUMBER,
time_id DATE,
channel_id CHAR(1),
promo_id NUMBER(6),
quantity_sold NUMBER(3),
amount_sold NUMBER(10,2)
)
PARTITION BY LIST (channel_id) (
PARTITION sales_web VALUES ('W'),
PARTITION sales_direct VALUES ('D'),
PARTITION sales_store VALUES ('S'),
PARTITION sales_other VALUES (DEFAULT)
);
2.2.3 해시 파티셔닝 (Hash Partitioning)
해시 함수를 사용하여 데이터를 균등하게 분산시킵니다. 데이터 분포가 고르지 않을 때 유용합니다.
2.3 파티셔닝의 장점
- 쿼리 성능 향상: 필요한 파티션만 스캔하여 I/O 감소
- 관리 용이성: 개별 파티션 단위로 백업, 복구, 유지보수 가능
- 가용성 향상: 일부 파티션에 문제가 생겨도 다른 파티션은 정상 작동
2.4 파티셔닝의 단점
- 조인 성능 저하: 파티션 간 조인 작업이 복잡해질 수 있음
- 오버헤드: 파티션 관리에 추가적인 시스템 리소스 필요
- 제약사항: DBMS에 따라 파티션 키에 대한 제약이 있을 수 있음
3. 샤딩(Sharding)이란?
샤딩은 데이터베이스를 여러 물리적 서버(샤드)로 분산하는 기법입니다. 각 샤드는 독립적인 데이터베이스 인스턴스로, 전체 데이터의 일부를 저장합니다.
3.1 샤딩 전략
3.1.1 키 기반 샤딩 (Key-Based Sharding)
특정 키(사용자 ID, 지역 코드 등)를 기준으로 데이터를 분산합니다.
1
2
3
4
5
6
# 파이썬에서 키 기반 샤딩 구현 예시
def get_shard(user_id, total_shards):
return user_id % total_shards
# 사용자 ID 1234는 샤드 2에 저장
shard_number = get_shard(1234, 4) # 결과: 2
3.1.2 범위 기반 샤딩 (Range-Based Sharding)
데이터 값의 범위에 따라 샤드를 결정합니다. 예를 들어, A-M으로 시작하는 사용자는 샤드1, N-Z는 샤드2에 저장하는 방식입니다.
3.1.3 지역 기반 샤딩 (Geography-Based Sharding)
사용자의 지리적 위치에 따라 가장 가까운 데이터 센터의 샤드에 데이터를 저장합니다.
3.2 샤딩 아키텍처
3.2.1 클라이언트 기반 샤딩
애플리케이션이 직접 어떤 샤드에 접근할지 결정합니다.
1
2
3
4
5
6
7
8
9
// 자바에서 클라이언트 기반 샤딩 예시
public class ShardingRouter {
private List<DataSource> shards;
public DataSource getShardForUser(long userId) {
int shardIndex = (int) (userId % shards.size());
return shards.get(shardIndex);
}
}
3.2.2 프록시 기반 샤딩
중간 계층(프록시)이 쿼리를 분석하여 적절한 샤드로 라우팅합니다.
3.3 샤딩의 장점
- 수평적 확장성: 서버를 추가하여 용량과 처리량을 선형적으로 확장 가능
- 지역적 최적화: 사용자와 가까운 위치에 데이터 배치 가능
- 고가용성: 일부 샤드에 장애가 발생해도 전체 시스템은 계속 작동
3.4 샤딩의 단점
- 복잡성 증가: 설계, 구현, 유지보수가 복잡해짐
- 조인 연산 제한: 샤드 간 조인이 어려움
- 트랜잭션 관리: 분산 트랜잭션 처리가 복잡해짐
- 데이터 불균형: 특정 샤드에 데이터가 집중될 수 있음 (핫스팟 문제)
4. 파티셔닝 vs 샤딩
특성 | 파티셔닝 | 샤딩 |
---|---|---|
데이터 위치 | 단일 서버 내 | 여러 서버에 분산 |
구현 복잡도 | 상대적으로 낮음 | 높음 |
확장성 | 제한적 (수직적 확장) | 높음 (수평적 확장) |
쿼리 투명성 | 높음 (애플리케이션 변경 최소화) | 낮음 (라우팅 로직 필요) |
비용 | 상대적으로 낮음 | 높음 (여러 서버 필요) |
5. 구현 시 고려사항
5.1 샤딩 키 선택
- 균등한 분포: 데이터가 고르게 분산되도록 키 선택
- 쿼리 패턴: 자주 사용되는 쿼리에 최적화된 키 선택
- 확장성: 미래 확장을 고려한 키 설계
5.2 리밸런싱 전략
샤드 간 데이터 불균형이 발생할 경우 리밸런싱이 필요합니다.
1
2
3
4
5
6
7
8
9
10
11
# 리밸런싱 의사 코드
def rebalance_shards(shards):
total_data = sum(shard.data_size for shard in shards)
target_size = total_data / len(shards)
for source_shard in shards:
if source_shard.data_size > target_size * 1.2: # 20% 이상 초과
for target_shard in shards:
if target_shard.data_size < target_size * 0.8: # 20% 이상 미달
# 데이터 이동 로직
move_data(source_shard, target_shard)
5.3 장애 복구 방안
- 복제: 각 샤드의 복제본 유지
- 백업 전략: 정기적인 백업 및 복구 테스트
- 장애 감지: 자동화된 모니터링 및 알림 시스템
6. 실무에서의 데이터베이스 샤딩과 파티셔닝 활용 사례
6.1 대규모 웹 서비스에서의 활용
6.1.1 소셜 미디어 플랫폼
소셜 미디어 플랫폼은 사용자 데이터를 효율적으로 관리하기 위해 샤딩을 적극 활용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 사용자 ID 기반 샤딩 예시 코드
public class UserDataService {
private static final int SHARD_COUNT = 256;
public Connection getConnectionForUser(long userId) {
int shardId = (int)(userId % SHARD_COUNT);
return ShardConnectionPool.getConnection("user_db_" + shardId);
}
public User getUserById(long userId) {
try (Connection conn = getConnectionForUser(userId)) {
// 해당 샤드에서 사용자 정보 조회
return userDao.findById(conn, userId);
}
}
}
![소셜 미디어 샤딩 아키텍처]
6.1.2 전자상거래 플랫폼
전자상거래 플랫폼은 주문 데이터를 시간 기준으로 파티셔닝하여 관리합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 주문 테이블 월별 파티셔닝 예시 (MySQL)
CREATE TABLE orders (
order_id BIGINT NOT NULL,
customer_id INT NOT NULL,
order_date DATETIME NOT NULL,
total_amount DECIMAL(10,2),
status VARCHAR(20),
PRIMARY KEY (order_id, order_date)
) PARTITION BY RANGE (MONTH(order_date)) (
PARTITION p1 VALUES LESS THAN (2), -- 1월
PARTITION p2 VALUES LESS THAN (3), -- 2월
PARTITION p3 VALUES LESS THAN (4), -- 3월
-- ... 나머지 월
PARTITION p12 VALUES LESS THAN (13), -- 12월
PARTITION p_future VALUES LESS THAN MAXVALUE
);
6.2 금융 서비스에서의 활용
6.2.1 거래 데이터 관리
금융 기관은 거래 데이터를 시간과 고객 ID를 기준으로 복합 파티셔닝합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- PostgreSQL에서 거래 데이터 복합 파티셔닝 예시
CREATE TABLE transactions (
tx_id UUID PRIMARY KEY,
account_id BIGINT NOT NULL,
tx_date TIMESTAMP NOT NULL,
amount DECIMAL(15,2) NOT NULL,
tx_type VARCHAR(10) NOT NULL
) PARTITION BY RANGE (tx_date);
-- 월별 파티션 생성
CREATE TABLE transactions_2023_01 PARTITION OF transactions
FOR VALUES FROM ('2023-01-01') TO ('2023-02-01')
PARTITION BY HASH (account_id);
-- 각 월별 파티션을 계좌 ID로 하위 파티셔닝
CREATE TABLE transactions_2023_01_p0 PARTITION OF transactions_2023_01
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE transactions_2023_01_p1 PARTITION OF transactions_2023_01
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
-- ... 나머지 하위 파티션
6.2.2 실시간 사기 탐지
금융 사기 탐지 시스템은 지역 기반 샤딩을 활용하여 지연 시간을 최소화합니다.
1
2
3
4
5
6
7
8
9
# 지역 기반 샤딩을 활용한 사기 탐지 시스템 의사 코드
def get_fraud_detection_shard(transaction_location):
# 거래 위치에 가장 가까운 데이터 센터 선택
nearest_datacenter = geo_router.find_nearest_datacenter(transaction_location)
return fraud_detection_shards[nearest_datacenter]
def check_transaction(transaction):
shard = get_fraud_detection_shard(transaction.location)
return shard.analyze_transaction(transaction)
![금융 사기 탐지 시스템 아키텍처]
6.3 게임 산업에서의 활용
6.3.1 플레이어 데이터 관리
온라인 게임은 플레이어 데이터를 지역별로 샤딩하여 관리합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// C#에서 지역 기반 게임 데이터 샤딩 예시
public class GameDataService
{
private Dictionary<Region, IGameDataShard> _regionShards;
public PlayerData GetPlayerData(string playerId, Region playerRegion)
{
IGameDataShard shard = _regionShards[playerRegion];
return shard.GetPlayerData(playerId);
}
public void UpdatePlayerData(PlayerData data)
{
IGameDataShard shard = _regionShards[data.Region];
shard.UpdatePlayerData(data);
}
}
6.3.2 게임 이벤트 로그 관리
게임 이벤트 로그는 시간 기반 파티셔닝으로 관리하여 분석 효율성을 높입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 게임 이벤트 로그 시간 기반 파티셔닝 (Oracle)
CREATE TABLE game_events (
event_id NUMBER,
player_id VARCHAR2(50),
event_time TIMESTAMP,
event_type VARCHAR2(30),
event_data CLOB
)
PARTITION BY RANGE (event_time) (
PARTITION events_hour_1 VALUES LESS THAN (TIMESTAMP '2023-03-15 01:00:00'),
PARTITION events_hour_2 VALUES LESS THAN (TIMESTAMP '2023-03-15 02:00:00'),
-- ... 시간별 파티션
PARTITION events_future VALUES LESS THAN (MAXVALUE)
);
6.4 로그 및 분석 데이터 관리
6.4.1 로그 데이터 파티셔닝
대규모 로그 데이터는 일자별 파티셔닝으로 관리하여 보관 정책을 쉽게 적용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 로그 데이터 일자별 파티셔닝 (MySQL)
CREATE TABLE application_logs (
log_id BIGINT AUTO_INCREMENT,
log_time DATETIME NOT NULL,
log_level VARCHAR(10),
service VARCHAR(50),
message TEXT,
PRIMARY KEY (log_id, log_time)
) PARTITION BY RANGE (TO_DAYS(log_time)) (
PARTITION p_week1 VALUES LESS THAN (TO_DAYS('2023-03-08')),
PARTITION p_week2 VALUES LESS THAN (TO_DAYS('2023-03-15')),
PARTITION p_week3 VALUES LESS THAN (TO_DAYS('2023-03-22')),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
6.4.2 분석 데이터 샤딩
분석 데이터는 테넌트(고객사) 기반으로 샤딩하여 멀티테넌트 환경을 지원합니다.
1
2
3
4
5
6
7
8
9
10
11
12
# 테넌트 기반 분석 데이터 샤딩 예시
class AnalyticsService:
def __init__(self, shard_manager):
self.shard_manager = shard_manager
def store_event(self, tenant_id, event_data):
shard = self.shard_manager.get_shard_for_tenant(tenant_id)
shard.insert_event(tenant_id, event_data)
def query_analytics(self, tenant_id, query_params):
shard = self.shard_manager.get_shard_for_tenant(tenant_id)
return shard.execute_query(tenant_id, query_params)
![분석 데이터 샤딩 아키텍처]
6.5 마이크로서비스 아키텍처에서의 활용
6.5.1 데이터베이스 per 서비스 패턴
마이크로서비스 아키텍처에서는 서비스별로 독립적인 데이터베이스를 사용하는 방식으로 자연스러운 샤딩이 이루어집니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 마이크로서비스 데이터베이스 구성 예시 (Docker Compose)
version: '3'
services:
user-service:
image: user-service:latest
depends_on:
- user-db
environment:
- DB_HOST=user-db
- DB_NAME=userdb
user-db:
image: postgres:13
volumes:
- user-db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=userdb
order-service:
image: order-service:latest
depends_on:
- order-db
environment:
- DB_HOST=order-db
- DB_NAME=orderdb
order-db:
image: postgres:13
volumes:
- order-db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=orderdb
volumes:
user-db-data:
order-db-data:
6.5.2 CQRS 패턴과 이벤트 소싱
명령 쿼리 책임 분리(CQRS) 패턴에서는 읽기와 쓰기 데이터베이스를 분리하여 최적화합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// CQRS 패턴을 활용한 주문 서비스 예시
public class OrderService {
private final OrderCommandRepository commandRepo;
private final OrderQueryRepository queryRepo;
private final EventPublisher eventPublisher;
public OrderId createOrder(CreateOrderCommand command) {
// 쓰기 전용 데이터베이스에 주문 생성
Order order = new Order(command);
OrderId orderId = commandRepo.save(order);
// 이벤트 발행 (읽기 데이터베이스 업데이트용)
eventPublisher.publish(new OrderCreatedEvent(order));
return orderId;
}
public OrderDetails getOrderDetails(OrderId orderId) {
// 읽기 전용 데이터베이스에서 조회
return queryRepo.findOrderDetails(orderId);
}
}
6.6 클라우드 환경에서의 활용
6.6.1 AWS DynamoDB의 파티셔닝
AWS DynamoDB는 파티션 키를 기반으로 자동 샤딩을 제공합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// AWS SDK를 사용한 DynamoDB 테이블 생성 예시
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB();
const params = {
TableName: 'Users',
KeySchema: [
{ AttributeName: 'userId', KeyType: 'HASH' }, // 파티션 키
{ AttributeName: 'createdAt', KeyType: 'RANGE' } // 정렬 키
],
AttributeDefinitions: [
{ AttributeName: 'userId', AttributeType: 'S' },
{ AttributeName: 'createdAt', AttributeType: 'N' }
],
ProvisionedThroughput: {
ReadCapacityUnits: 10,
WriteCapacityUnits: 10
}
};
dynamodb.createTable(params, (err, data) => {
if (err) console.error(err);
else console.log('Table created:', data);
});