박종훈 기술블로그

[SQL] JOIN 알고리즘

JOIN 알고리즘

관계형 데이터베이스에서는 여러 테이블의 데이터를 연결하여 원하는 결과를 얻기 위해 JOIN을 자주 사용하게 된다. 데이터베이스에서 JOIN을 효율적으로 수행하기 위해 다양한 알고리즘이 사용된다. 데이터베이스는 효율적인 실행 계획을 짜기 위해 테이블의 데이터량, 분포 등 통계 정보를 이용하며, 상황에 따라 nested loop, hash join, merge join 등 최적 알고리즘을 선택한다. 데이터베이스 옵티마이저는 항상 최소 자원 소비를 목표로 하며, 이를 비용 기반 옵티마이저(cost-based optimizer)라고 한다.

일반적으로 사용되는 주요 JOIN 알고리즘은 다음과 같다.

Nested Loop Join

중첩 반복문을 생각하면 된다. 한 컬렉션을 반복하면서 다른 컬렉션을 반복하여 조인 조건에 따라 일치하는 레코드를 찾아 projection을 생성한다. 데이터가 적을 때는 효율적으로 동작하지만, 수백만 건의 데이터로 확장되면 성능 저하가 심하다. (O(n*m) 복잡도)

Hash Join

대용량 데이터셋에서 효율적인 알고리즘이다. 해시 맵이나 해시 테이블을 만든다. 내부적으로 버킷이 있고, 각 버킷은 레코드들의 해시 값으로 식별된다. 중첩 루프(nested loops) 대신 훨씬 적은 비교로 일치하는 데이터를 찾을 수 있다. Hash Join은 선형(Linear) 복잡도에 가깝다. (O(n + m) 복잡도) 메모리를 많이 사용할 수 있다.

Merge Join

Merge Join은 정렬된 상태에서 사용할 수 있는 알고리즘이다.

두 테이블을 조인 조건에 따라 처음부터 끝까지 한 번씩만 순회하며, 같은 방향으로 스캔한다. 좌측과 우측 레코드를 하나씩 비교하면서 일치하는 경우만 결과에 추가한다. 이터레이터 기반으로 진행되며, 여러 번 반복하지 않아도 된다는 점이 특징이다. 결과물 역시 정렬된 상태이다.

로그 복잡도(O(n*log(n) + m*log(m)))를 가진다.

예시

문제 상황: 댓글이 가장 많은 상위 게시글 3개와 그 댓글을 가져오기

초기 솔루션

SELECT
  p.title AS post_title,
  COUNT(pc.*) AS comment_count
FROM post p
JOIN post_comment pc ON p.id = pc.post_id
GROUP BY p.title
ORDER BY comment_count DESC

실행계획을 확인보면 PostgreSQL 에서는 Hash Join 을, MySQL 에서는 Nested Loop Join 를 사용하였다.

개선 솔루션

위 솔루션은 개선의 여지가 있다.

댓글이 가장 많은 상위 게시글 3개를 찾아내는 과정 자체는 post 테이블이 필요하지 않다. post_comment 만 있으면 된다. 따라서 아래와 같이 수정해볼 수 있다. 쿼리는 더 복잡해졌지만. 속도는 훨씬 줄어든다.

SELECT
  p.title AS post_title,
  pc.comment_count AS comment_count
FROM post p
JOIN (
  SELECT
    post_id,
    COUNT(*) as comment_count
  FROM post_comment
  GROUP BY post_id
  ORDER BY comment_count DESC
  LIMIT 3
) pc ON p.id = pc.post_id
ORDER BY comment_count DESC

위와 같이 개선 하면 약 3배 더 빠르게 동작하게 되었다. 실행 계획도 더 단순해졌다.

조인을 하기 전에 집계를 선행하면, 조인의 대상이 되는 레코드 수가 매우 줄어들어 훨씬 빠른 실행이 가능해진다.

정리

효율적인 JOIN을 위해서는 실행 계획을 이해하고 이를 활용할 줄 알아야 하겠고 그에 따라 쿼리 구조를 최적화하는 것이 중요하겠다.