프로그래밍/개발

[개발] 샤딩이란 것을 해보자! (3)

riroan 2023. 4. 21. 19:20

샤딩 1

샤딩 2

 

조건이 포함된 조회 쿼리

특정 이름이 들어간 유저를 찾고싶다고 하자.

# datasource.py

class DataSource:
	...
    
    def select_by_name_exact(self, name):
        ix = insert_route(name)
        query = f"SELECT * FROM user WHERE name = '{name}'"
        cursor = self.cursors[ix]
        cursor.execute(query)

        result = cursor.fetchall()

        return result

삽입과 유사하게 사용하면 된다.

범위 조건이 포함된 조회 쿼리

이제 살짝 복잡해지기 시작한다. aa ~ bb를 찾는다고하면 1번 인스턴스에서 끝나지만 aa ~ zz를 찾는다면 1~4번 인스턴스를 모두 찾게 된다. 범위 조회를 위한 라우터를 만들자.

# router.py

...

def range_route(name_start: str, name_end: str):
    start = insert_route(name_start)
    end = insert_route(name_end)
    return start, end

이제 이것을 기준으로 범위 조회를 하면 된다.

# datasource.py

class DataSource:
	...
    def select_by_name_range(self, name_start, name_end):
        start, end = range_route(name_start, name_end)
        result = []
        query = f"SELECT * FROM user WHERE name >= '{name_start}' AND name <= '{name_end}'"
        for ix in range(start, end+1):
            cursor = self.cursors[ix]
            cursor.execute(query)

            datas = cursor.fetchall()

            for data in datas:
                result.append(data)

        return result

이런식으로 조회하면 된다!

하지만 여기에서 성능의 개선점이 보인다. aa~zz범위를 조회할때 인스턴스를 4개 쓰게되는데 2번째와 3번째 인스턴스는 조회범위에 포함되기 때문에 where문을 걸 필요가 없다. 따라서 쿼리문을 아래와 같이 변경할 수 있다.

# datasource.py

class DataSource:
	...
    def select_by_name_range(self, name_start, name_end):
        start, end = range_route(name_start, name_end)
        result = []

        for ix in range(start, end+1):
            if start < ix < end:
                query = "SELECT * FROM user"
            else:
                query = f"SELECT * FROM user WHERE name >= '{name_start}' AND name <= '{name_end}'"
            cursor = self.cursors[ix]
            cursor.execute(query)

            datas = cursor.fetchall()

            for data in datas:
                result.append(data)

        return result

이렇게 하면 where문을 줄임으로써 성능향상을 기대할 수 있고 대용량 데이터를 처리할 때 샤딩의 장점이기도 하다.

또 다시 개선할 수 있을것 같다. start는 name_end 조건이 필요없고 end일때는 name_start 조건이 필요없다.

 

# datasource.py

class DataSource:
	...
    def select_by_name_range(self, name_start, name_end):
        start, end = range_route(name_start, name_end)
        result = []

        for ix in range(start, end+1):
            if start < ix < end:
                query = "SELECT * FROM user"
            elif start == end:
                query = f"SELECT * FROM user WHERE name >= '{name_start}' AND name <= '{name_end}'"
            elif ix == start:
                query = f"SELECT * FROM user WHERE name >= '{name_start}'"
            elif ix == end:
                query = f"SELECT * FROM user WHERE name <= '{name_end}'"
            print(f"{ix}번째 인스턴스가 사용하는 쿼리 -> {query}")
            cursor = self.cursors[ix]
            cursor.execute(query)

            datas = cursor.fetchall()

            for data in datas:
                result.append(data)

        return result

이렇게 수정하고 테스트해보자.

if __name__ == "__main__":
    src = DataSource()

    src.select_by_name_range("aa", "zz")

# 0번째 인스턴스가 사용하는 쿼리 -> SELECT * FROM user WHERE name >= 'aa'
# 1번째 인스턴스가 사용하는 쿼리 -> SELECT * FROM user
# 2번째 인스턴스가 사용하는 쿼리 -> SELECT * FROM user
# 3번째 인스턴스가 사용하는 쿼리 -> SELECT * FROM user WHERE name <= 'zz'

너무 편안하다. 이렇게 우리는 범위조회 쿼리까지 해결할 수 있다.

샤딩한 컬럼과 상관없는 컬럼의 조회 쿼리

우리는 name기준으로 샤딩했기때문에 이런 방식이 가능했지만 만약 age기준으로 조회하려면 어떻게 해야 할까?

... 모든 인스턴스에 접근해야 한다.

# datasource.py

class DataSource:
	...
    def select_by_age_exact(self, age):
        result = []
        query = f"SELECT * FROM user WHERE age = {age}"
        for cursor in self.cursors:
            cursor.execute(query)

            datas = cursor.fetchall()

            for data in datas:
                result.append(data)
        return result

이런식으로 사용하여 쿼리성능이 나빠질 수 있다. 하지만 대용량 데이터를 위해서라면 뭐...

그렇다면 JOIN은?

join도 age조건과 같이 모든 인스턴스에 대해 조인하고 결과를 합치면 된다. 만약 join 대상 테이블도 샤딩되어있다면 좀 끔찍해진다... ㅋㅋ

 

마무리

이렇게 샤딩을 공부하고 직접 사용해봤다.

mongodb는 내장으로 샤딩을 지원한다고 한다. (좋네...)

그리고 range 샤딩을 사용했지만 modular샤딩이나 dynamic 샤딩같은 다양한 기법이 존재한다고 한다.

언젠가 막대한 데이터를 다루게 될 수 있으니 샤딩은 알고 있으면 좋은 것 같다.