DB

급격한 db 데이터 증가에 대처하는 방법 - Sharding

코드모헨 2024. 5. 27. 13:00
데이터 베이스에 급격스런 데이터가 추가 되었을 때 
전략


데이터 베이스에 급격스런 데이터가 추가 되면 성능에 큰 하자가 생길 때가 있다. 이럴 경우 어떤 방식으로 데이터를 분산할까?

 

1. DB Sharding

2. DB Partitioning

3. Replication

4. Distributed db system

 

다음과 같은 전략이 사용될 수 있을 것이다. 이중 이번엔 sharding에 대하 기록하도록 한다.

 

1. 샤딩이랑 무엇인가?

샤딩(sharding)은 db를 나누어 다른 machine(샤드(shard) 라고 불린다)에 db를 저장하는 방식을 말한다.

 

Sharding - 출처:https://learn.microsoft.com/ko-kr/azure/azure-sql/database/elastic-scale-shard-map-management?view=azuresql

 

2. 샤딩의 전략

A. 수평적 샤딩(Horizontal Sharding)

 

수평적 샤딩 출처:https://shardingsphere.apache.org/document/5.1.0/en/features/sharding/

 

row단위로 테이블을 나누어 갹 샤드마다 row의 범위를 지정하여 저장한다. 

위의 그림과 같이 샤드마다 range[0,100], range[100,200]이런식으로 저장하는 방법이다.

저장된 data는 pk의 범위에 따라 불러오는 db가 다르다.

보통 database level에서 사용하지만 application level에서도 사용할 수 있다.

 

B. 수직적 샤딩 (Vertical Sharding)

수직적 샤딩 출처:https://shardingsphere.apache.org/document/5.1.0/en/features/sharding/

 

테이블 단위로 각 샤드마다 나누어 저장한다.

테이블에 따라 샤드를 다르게 해서 데이터를 가져와야한다.

주로 application level에서 사용된다.

 

※ Vertical Sharding 의문점? M-N관계에서 호출테이블 혹은 bridge table은 어떻게 해야하는가?

M-N 관계일 때 중간역할을 하는 테이블을 어디에 저장해야 될 지 고민이 될 것이다. 

 

다음과 같은 예시를 보자

 

user table
item table
user-item table

 

방법 A. Centralized DB

  • centralized DB에  user-item table을 넣는다.
  • 샤드 1에 user table을 넣는다.
  • 샤드 2에 item table을 넣는다.

장점

  • 간단하게 관계를 관리할 수 있다.
  • centralized를 통해 m-n관계를 통제가능하다.

단점

  • 병목현상이 centralized db에서 발생할 수 있다.
-- Central database
CREATE TABLE User_Item (
    user_id INT,
    item_id INT,
    PRIMARY KEY (user_id, item_id),
    FOREIGN KEY (user_id) REFERENCES shard1.User(id),
    FOREIGN KEY (item_id) REFERENCES shard2.Item(id)
);

-- Shard 1 (User database)
CREATE TABLE User (
    id INT PRIMARY KEY,
    username VARCHAR(50)
    -- other user fields
);

-- Shard 2 (Item database)
CREATE TABLE Item (
    id INT PRIMARY KEY,
    item_name VARCHAR(50)
    -- other item fields
);

 

방법 B. Applicaation level Join

  • 샤드 1에 user table / user-item table을 넣는다.
  • 샤드 2에 item table을 넣는다.
def get_user_items(user_id):
    # Query the User database
    user_shard = connect_to_user_shard()
    user_items = user_shard.execute("SELECT item_id FROM User_Item WHERE user_id = ?", (user_id,))

    # Collect item_ids
    item_ids = [row['item_id'] for row in user_items]

    # Query the Item database
    item_shard = connect_to_item_shard()
    items = item_shard.execute("SELECT * FROM Item WHERE id IN (?)", (item_ids,))

    return items

다음과 같이 app단위에서 user와 item을 조인하여 데이터를 조회한다.

 

장점

  • 샤드에 부하를 분산할 수 있다.
  • 단일 실패 지점(single point of failure) 병목현상의 위험이 없다

※ 단일 실패 지점이란 시스템이나 네트워크의 한 부분이 고장 났을 때 전체 시스템이 다운되는 것을 말한다.

단점

  • 로직이 길어지고 복잡해진다
  • 쿼리가 훨씬 더 복잡해질 가능성이 높고 데이터 핸들링이 복잡하다.

 

더보기

추가적인 vertical shard 코드 예시

 

샤드 1에 user table / user-item table을 만든다

샤드 2에 item table을 만든다

-- User table in user_shard database
CREATE TABLE User (
    id INT PRIMARY KEY,
    username VARCHAR(50)
    -- other user fields
);

-- User_Item bridge table in user_shard database
-- Note: Foreign key constraints to Item table are not enforced
-- Note: foreign key는 선언하지 않는다.
CREATE TABLE User_Item (
    user_id INT,
    item_id INT,
    PRIMARY KEY (user_id, item_id)
);



-- Item table in item_shard database
CREATE TABLE Item (
    id INT PRIMARY KEY,
    item_name VARCHAR(50)
    -- other item fields
);

 

 각 샤드에 연결한 뒤 데이터를 삽입 혹은 조회하는 방법

import sqlite3

def connect_to_user_shard():
    conn = sqlite3.connect('user_shard.db')
    conn.row_factory = sqlite3.Row
    return conn

def connect_to_item_shard():
    conn = sqlite3.connect('item_shard.db')
    conn.row_factory = sqlite3.Row
    return conn

def add_user_item_relation(user_id, item_id):
    # Connect to the user shard
    user_shard = connect_to_user_shard()
    
    # Connect to the item shard
    item_shard = connect_to_item_shard()
    
    # Verify that the user exists in the user shard
    user = user_shard.execute("SELECT id FROM User WHERE id = ?", (user_id,)).fetchone()
    if not user:
        raise ValueError("User ID does not exist")
    
    # Verify that the item exists in the item shard
    item = item_shard.execute("SELECT id FROM Item WHERE id = ?", (item_id,)).fetchone()
    if not item:
        raise ValueError("Item ID does not exist")
    
    # Insert into the User_Item bridge table
    user_shard.execute("INSERT INTO User_Item (user_id, item_id) VALUES (?, ?)", (user_id, item_id))
    user_shard.commit()

# Example usage
try:
    add_user_item_relation(1, 101)
    print("User-Item relation added successfully")
except ValueError as e:
    print(e)