Ruby on Rails

[레일즈] 액티브레코드 쿼리 인터페이스 관련 질문답변

_29 2021. 5. 6. 17:02

이전까지 혼자서 레일즈 가이드를 참고해 Active Record Query Interface 를 살펴봤습니다. 그 과정에서 여러 질문거리들이 생겨 문개키 수업을 진행하면서 삼촌께 여쭤본 내용을 정리해보겠습니다.


1. find_each 에서 batch란 무엇인가

batch 처리

일반적으로 'batch 처리' 는 특정 객체들을 모아서 불러내어 처리하는 것을 말합니다.

예를 들어, 결제 기간이 일주일 남은 회원들만 골라 이들에게 안내 메일을 보내야 할 때, batch 처리를 합니다. 즉, 특정 시점에 특정 작업을 수행하는 것을 batch 처리라고 할 수 있습니다.

 

find_each 파트를 보면 batch 개념이 나옵니다.

 

레일즈 가이드에서는 여러 사용자들에게 뉴스레터를 전송하거나 데이터를 내보낼 때처럼 여러 레코드 전송을 반복해야 하는 경우처럼 여러 객체를 조회할 때 find_each 를 사용한다고 합니다.

 

그런데 만약 쿼리를 날려서 객체를 조회할 때, 데이터베이스 서버에서 데이터들을 다운로드하는데 데이터 양이 많거나 데이터의 크기가 크면 다운받는 시간이 오래 걸립니다. 그래서 한번에 다운받는 것이 아니라 batch 를 설정해 그 크기만큼 끊어서 받아오는 것입니다.

 

batch 의 기본값은 1000이기 때문에 1000개씩 끊어서 받아오는 것입니다. 그리고 :batch_size 를 통해 batch 의 크기를 변경하기도 하는 것이고, :start 와 :finish 를 활용해 범위를 설정할 수도 있는 것입니다.

 

find_each 예시

find_each 에 대한 설명을 보면 find_each 는 묶음의 레코드를 검색하고 각 레코드를 블록에 모델로서 개별적으로 생성한다고 합니다. 즉, batch 를 통해 레코드를 찾고 모델 하나하나를 리턴합니다.

 

아래 이미지를 보면 Board 모델에 limit(10) 으로 조회하는 객체 수를 10개로 설정하고, find_each(batch_size: 5) 로 설정하고 각각 객체의 id 를 호출하였습니다.

이때 리턴을 보면, 쿼리가 총 두번 날아갔고, 각각의 모델을 조회해서 id를 5개씩 불러주고 있습니다.

이는 총 조회 객체 수가 10개이고 batch_size 가 5 이기 때문에 5개씩 두번 총 10개의 id 를 하나하나 씩 리턴해주고 있습니다.

 

 

find_in_batches 예시

find_in_batches 는 find_in_batches 는 레코드의 batch를 검색해 전체 batch 를 모델의 배열 형태로 블록에 생성한다고 하는데, 

 

다음 이미지는 find_each 가 아니라 find_in_batches로 호출한 결과인데 나머지 조건은 위와 동일합니다. 그리고 나서 a 의 size를 호출했습니다.

 

이때, size 를 호출한 이유는 find_in_batches 는 배열을 리턴하기 때문에 배열에 대한 id 는 없습니다. 따라서 에러가 발생할 것입니다.

 

따라서 리턴 값을 보면 5,5 로 나오는데 이는 각각의 배열 안에 5개의 값들이 들어있다는 의미입니다.

 


2. none 메소드의 활용

Null Relation 파트에 보면, none 메소드가 나오는데요, 이 메소드의 활용에 대해 궁금했습니다.

Null Relation
none 메소드는 레코드가 없는 연결된 관계를 반환합니다. 반환된 관계에 연결된 모든 후속 조건은 빈 관계를 계속 생성합니다. 이 방법은 0 결과를 반환할 수 있는 방법 또는 범위에 대한 연쇄 반응이 필요한 시나리오에서 유용합니다.

 

우선 0 결과를 반환할 수 있는 방법입니다.

 

레일즈 가이드에 나온 아래 예시를 다시 보겠습니다. 이때, visible_articles 에 대해 메소드를 설정했는데 Country Manager 일 경우 Article.where(country: country) 를, Reviewer 일 경우, Article.published 를 호출합니다. 그런데마지막 Bad User 일 경우 Article.none 을 호출해서 그 결과로 [] 나 nil 을 리턴한다고 나와 있습니다.

 

즉, 아래 예시에서는 Bad User 일때 0 결과를 반환하기 위해 Article.none 메소드를 활용한 것입니다.

Article.none # returns an empty Relation and fires no queries.

# The visible_articles method below is expected to return a Relation.

@articles = current_user.visible_articles.where(name: params[:name])
 
def visible_articles
  case role
  when 'Country Manager'
    Article.where(country: country)
  when 'Reviewer'
    Article.published
  when 'Bad User'
    Article.none # => returning [] or nil breaks the caller code in this case
  end
end

 

다음으로 범위에 대한 연쇄반응이 필요한 시나리오입니다.

 

코딩을 하다보면, 기본적으로 제공된 메소드를 사용하지 않고 개발자가 원하는 특정 메소드를 만들어야 할 경우가 있다고 합니다. 그때, 다른 메소드들과 연결하기 위해 none 메소드를 지정하고 뒤에 추가로 다른 메소드들을 붙여서 활용하기도 한다고 합니다.


3. lazy loading 과 n+1 queries problem

lazy loading 의 경우, 쿼리를 날리는 경우 조건을 추가해서 날리는데, lazy loading 하면 데이터의 양이 많을 경우 테이블을 조합하는 경우에 시간이 오래걸리기 때문에 이런 케이스에서 eager loading 을 활용합니다.

 

n+1 queries problem 예시

eager loading 을 통해 실행되는 총 쿼리 수를 줄여 n+1 queries problem 을 해결할 수 있다고 했는데요. 예시를 통해 확인해보겠습니다.

 

위 이미지는 제가 사전에 만들어 둔 게시판 화면과 index 컨트롤러입니다. 이 게시판 목록을 불러올때 로그를 확인해보겠습니다.

 

로그 기록을 보면, User의 id 를 불러올 때 우선 2개의 User 를 찾고, 각각의 id 를 불러오면서 총 3번의 쿼리를 실행하고 있습니다.

 

그 이유는 index.html.erb 에서 목록 테이블에 작성자 자리에 유저 id 가 나오도록 해두었는데 @volunteers = Volunteer.all 이라고 호출하면 유저들을 모델에서 각각 검색해서 id 를 찾아오기 때문입니다.

 

그렇다면, includes 메소드를 사용하고 비교해보겠습니다.

 

컨트롤러의 index 에서 @volunteers = Volunteer.all.includes(:user) 로 변경하고 다시 게시판 목록을 불러와 로그를 확인해보겠습니다.

 

이번에는 User를 찾고, 각각의 id 를 한꺼번에 가져오면서 2번의 쿼리를 실행하고 있습니다. 결국 User의 id 를 가져오기 위한 총 쿼리의 숫자가 줄었습니다.

 

만약 지금처럼 유저가 두 명만 있는 것이 아니라 수많은 수의 유저들이 존재한다면 첫번째 경우처럼 했을 경우, 실행되는 쿼리의 수가 늘어나 서비스의 속도가 줄어드는 상황이 발생할 것입니다.

 

이런 n+1 queries problem 은 초보 개발자들이 굉장히 많이 하는 실수라고 하는데요. 개발을 하면서 특정 기능이 동작하는 것에 만족하지 않고, 개발 후 로그 기록 등을 확인하며 목적에 맞게 최적화된 개발을 할 수 있도록 신경써야겠습니다.


4. Enum 의 활용은?

다음 질문은 enum 을 어떻게 활용하는 지에 대한 것이었습니다.

 

삼촌은 이 질문에 대한 대답으로 개발자들 간의 약속으로 활용하는 경우가 많다고 하셨는데요, 예를 들어 보겠습니다.

 

만약 서비스의 회원관리 시 일반 회원, 탈퇴 회원, 악성 회원 등 여러 종류의 회원들이 있는 경우 Enum 을 활용하지 않으면 회원 모델의 필드에 각각 회원들의 상태에 대한 값을 입력해주어야 합니다.

 

하지만, enum 을 사용하면 모든 케이스를 정해놓고 바로바로 그 회원들의 상태를 파악할 수 있다는 장점이 있습니다.

 

또한, 초기 개발자가 탈퇴, 정지라는 값을 입력해놓았는데 후임 개발자가 탈퇴, 정지가 아닌 '차단' 으로 만들어버리는 경우 제대로된 회원 관리에 어려움이 생길 수 있습니다.

 

enum 을 걸어두었는데 만약 enum 에 없는 값을 사용하면 컴파일러가 오류를 내주기 때문에 enum 으로 설정된 값만 사용할 수 있습니다.

 

즉, enum 을 활용해 개발자들 간 약속을 하는 것입니다.

 

추가적으로 오타로 인한 오류를 방지할 수도 있습니다.


이렇게 해서 ActiveRecord Query Interface 를 공부하며 생긴 궁금증들을 문개키 수업을 진행하며 해결해보았습니다. 추가적으로 생기는 질문과 그에 대한 답변은 계속해서 업데이트하겠습니다😊