티스토리 뷰

Ruby on Rails 코드를 짤 때 있어서 남들한테 홈페이즈 결과(프론트)가 보여지는거도 중요하지만, 내부 퍼포먼스도 중요합니다.

 

사실 레일즈에서는 SQL 문법으로 안쓰고 ORM 방식, 즉 모델 참조 방식으로 데이터 조작을 하다보니 SQL 문법에 관심이 없을 수도 있는데 이는 먼 미래에 있어선 큰 치명적일 수 있습니다.

결국 최종적으로는 Rails에서 작성된 ORM이 SQL로 변환되서 데이터 탐색이 이루어지기 때문입니다.

 

이번에는 SQL N+1 문제에 대해 맛보기 형식으로 한번 글을 써나아가보고자 합니다.

 

테이블 쿼리 탐색에 있어, 테이블 및 데이터는 위 테이블 내 자료를 기반으로 진행합니다.

 

 

  • SQL N+1 문제 발생

N+1 문제란?

SQL 구문 한번의 입력만으로 결과가 나와야 하지만 추가적으로 N번을 더 SQL 탐색을 진행하는 문제


말로 설명하는것 보다 직접 눈으로 보여드리겠습니다.

1) N+1 문제

N+1 문제 발생 사례 : 데이터 결과를 도출하기 전에 항상 User 테이블을 참조합니다.

 

2) N+1 문제가 완화된 쿼리

N+1문제는 주로 다른 테이블을 참조해서 데이터 결과를 보여줄 때 많이 발생합니다.

 

이러한 해결을 위해 SQL 정석상으로는 다음 2가지의 해결 방법이 존재합니다 :

사전 테이블 참조(Eager Load), LEFT JOIN(Left Outer Join)

 읽을 거리 : https://zetawiki.com/wiki/N%2B1_%EC%BF%BC%EB%A6%AC_%EB%AC%B8%EC%A0%9C

 

하지만 이번 시간에는 Ruby on Rails 시선으로서 해결법에 대해 알아보겠습니다.

(정석이랑 Rails 방식이랑 용어가 다릅니다.)

 

 

1. Preload : 사전 테이블 참조

@data = Post.preload(:user).to_a
@data.each do |t|
	puts "[#{t.id}] 제목 : #{t.title} / 작성자 : #{t.user.email}"
end

기존의 SQL 정석으로 치면 사전 데이터 참고로서, 데이터 탐색 전에 사전에 테이블을 참조하는 방식입니다.

SQL 쿼리는 총 2번 쓰입니다.

 

하지만 :preload 속성은 타 테이블을 참고해서 조건을 표현하는 where, find_by와 같은 조건절 사용할 수 없습니다.

 

 

2. Eager_load : LEFT OUTER JOIN

@data = Post.eager_load(:user).to_a
@data.each do |t|
	puts "[#{t.id}] 제목 : #{t.title} / 작성자 : #{t.user.email}"
end

Left Outer Join 방식은 2개의 테이블이 있다 할 때, 주체가 될 왼쪽 테이블을 기준으로 두 테이블간에 서로 맵핑을 하는 방식입니다.

SQL 쿼리는 총 1번 쓰입니다.

그림으로 알아보는 LEFT OUTER JOIN

더불어 eager_load 메소드는 타 테이블을 참조하는 조건절 사용이 가능합니다.

@data = Post.eager_load(:user).where("users.email LIKE ?", "%.com%").to_a
@data.each do |t| 
	puts "[#{t.id}] 제목 : #{t.title} / 작성자 : #{t.user.email}" 
end

 

 

3. includes

Preload(사전 테이블 참조)와 Eagerload(Left Outer Join) 2가지 속성을 모두 가지고 있는 메소드입니다.

 

1) :includes:preload 처럼

includes의 Default는 preload입니다.

@data = Post.includes(:user).to_a
@data.each do |t|
	puts "[#{t.id}] 제목 : #{t.title} / 작성자 : #{t.user.email}"
end

 

2) :includes  :eager_load 처럼

where절을 사용하면 LEFT OUTER JOIN 방식으로 SQL 탐색을 하게 됩니다.

@data = Post.includes(:user).where(:users => { "email": "lakeshia@torphy.info" }).to_a
@data.each do |t| 
	puts "[#{t.id}] 제목 : #{t.title} / 작성자 : #{t.user.email}" 
end

 

where 뿐만 아니라 references 메소드를 통해서도 LEFT OUTER JOIN을 하게됩니다.

@data = Post.includes(:user).references(:user).to_a

 

 

4. 속도 밴치마킹

www.chrisrolle.com/en/blog/benchmark-preload-vs-eager_load

 

어떤분이 밴치마킹 테스트를 하셨었는데, 조인보다 preload가 빠르단 결과가 나왔습니다.

 

 

  • 조인만으로 테이블 퍼포먼스가 해결될까?

꼭 그렇진 않습니다.

테이블 검색 성능을 향상을 시키기 위해선 색인(Index) 이라는 기능을 도입하는게 좋습니다.

 

책에서도 볼 수 있는 색인(INDEX)

 

색인은 Original Table의 Key-Value 를 가지고 있는 별도의 테이블이며, Key를 받아내면, Key에 매칭되는 Value값을 추적해내어 테이블 내 데이터 검색 속도를 높입니다.

' 오 그러면 모든 테이블마다 색인을 적용하면 되겠네! '

위 생각같이 저희 뜻대로 이루어지면 좋겠지만.. 융통성 없이 모든 테이블에 이런 짓을 행해서는 안됩니다.

 

  • 장점   색인은 테이블 내 데이터 탐색 속도를 높인다는 장점이 있습니다.

  • 장점   색인은 JOIN이 자주 쓰이는 작업에 쓰이는게 좋습니다.

  • 단점   테이블 내 내용이 삽입(INSERT)/수정(UPDATE)가 될 경우 삽입(INSERT)/수정(UPDATE) 에 대해 Indexing 함에 있어서 성능이 떨어질 수 있다는 문제점이 발생합니다.

  • 단점   데이터 변경 작업이 너무 잦다면 오버헤드 이슈 때문에 오히려 색인(INDEX)을 안쓰는게 나을 수 있습니다.

  • 단점   인덱스도 결국 데이터다보니 DB 내 공간을 사용합니다.

  • 단점   색인(INDEX)은 테이블 내 데이터가 많을 때 그 힘이 발휘되나, 데이터 양이 너무 적으면 오히려 성능이 떨어집니다.

 

 

  • 강제로 조인이 된 상황에선 어떻게 인덱싱을 해야할까?

Order.includes(:user).where(order_status: "Cancelled")

만약 위와같이 includes 메소드를 통해 강제로 LEFT JOIN이 된 상황인 경우이고, order_status 에트리뷰트를 조건으로 사용을 해낸다면

add_index :orders, :order_status

다음과 같이 where 절에 있어 기준이 되는 에트리뷰트를 인댁싱을 해주면 됩니다.

 

 

  • 연결고리

1. Ruby on Rails : Table JOIN

 

 

  • 자료 참고

1. 제타위키 : N+1 문제 정의 [클릭]

2. Rails Compare :includes, :preload, :eager_load, Benchmark [클릭]

3. Rails Compare :includes, :preload, :eager_load 2 [클릭]

4. DB INDEX란? [클릭]

5. Rails에 테이블 색인 적용하기 [클릭]

6. Rails : How to table Index? [클릭]

7. 색인 테이블 제작에 있어 Attribute 순서의 중요성 [클릭]

8. Rails : includes 메소드를 쓴 상황에서 테이블 색인을 적용해야 한다면? [클릭]

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
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
글 보관함