[RUBY-ON-RAILS] 액티브의 find_each 제한 및 주문과 함께
RUBY-ON-RAILS액티브의 find_each 제한 및 주문과 함께
나는 액티브의 find_each 방법을 사용하여 50,000에 대한 기록의 쿼리를 실행하기 위해 노력하고있어,하지만 지금처럼 내 다른 매개 변수를 무시하는 것 같다 :
Thing.active.order("created_at DESC").limit(50000).find_each {|t| puts t.id }
대신 내가 좋아하는 것 50,000에서 중지 및 created_at에 의해 정렬로, 여기에 전체 데이터 세트를 통해 실행됩니다 결과 쿼리는 다음과 같습니다
Thing Load (198.8ms) SELECT "things".* FROM "things" WHERE "things"."active" = 't' AND ("things"."id" > 373343) ORDER BY "things"."id" ASC LIMIT 1000
find_each에하지만 내 정렬 기준을 존중 총 최대 한계와 비슷한 동작을 얻을 수있는 방법이 있나요?
해결법
-
==============================
1.문서는 find_each 및 find_in_batches가 정렬 순서 및 제한 때문에 유지되지 않는 것을 말한다 :
문서는 find_each 및 find_in_batches가 정렬 순서 및 제한 때문에 유지되지 않는 것을 말한다 :
@rorra했던 것처럼이 기능의 자신의 버전을 작성할 수 있습니다. 개체를 돌연변이 때 당신은 곤경에 얻을 수 있습니다. 예를 들어, 당신은 종류 created_at에 의해 객체 저장이 다음 배치 중 하나에 다시 올 수 있다면. 결과의 순서가 변경 되었기 때문에 다음 배치를 얻을 수있는 쿼리를 실행할 때 마찬가지로 당신은 객체를 건너 뛸 수 있습니다. 만 개체 만 읽어와 해당 솔루션을 사용합니다.
이제 나의 주요 관심사는 내가 한 번에 메모리에 30000+ 객체를로드 할 않았다이었다. 내 관심은 쿼리 자체의 실행 시간이 아니었다. 그러므로 나는 원래 쿼리를 실행하지만, 단지 ID의 캐시 솔루션을 사용했다. 그러므로 ID의 청크 및 검색어로의 배열 / 청크 당 객체를 생성 나눈다. 정렬 순서가 메모리에 저장되기 때문에이 방법으로 안전하게 개체를 변이 할 수 있습니다.
여기에 내가했던 것과 유사한 최소한의 예입니다 :
batch_size = 512 ids = Thing.order('created_at DESC').pluck(:id) # Replace .order(:created_at) with your own scope ids.each_slice(batch_size) do |chunk| Thing.find(chunk, :order => "field(id, #{chunk.join(',')})").each do |thing| # Do things with thing end end
이 솔루션에 대한 장단점은 다음과 같습니다 :
도움이 되었기를 바랍니다!
-
==============================
2.후드 find_each 용도의 find_in_batches.
후드 find_each 용도의 find_in_batches.
find_in_batches에 설명 된대로 레코드의 순서를 선택하는 것이 그것의 불가능은 자동으로 일괄 주문 작품을 만들기 위해 기본 키 ( "아이디 ASC")에 오름차순으로 설정됩니다.
그러나, 기준은 무엇 당신이 할 수있는 것은, 적용된다 :
Thing.active.find_each(batch_size: 50000) { |t| puts t.id }
한계에 대해서는, 아직 구현되지 않은 : https://github.com/rails/rails/pull/5696
두 번째 질문에 응답하면, 당신은 논리를 직접 만들 수 있습니다 :
total_records = 50000 batch = 1000 (0..(total_records - batch)).step(batch) do |i| puts Thing.active.order("created_at DESC").offset(i).limit(batch).to_sql end
-
==============================
3.제 ID를 검색하고 처리 in_groups_of
제 ID를 검색하고 처리 in_groups_of
ordered_photo_ids = Photo.order(likes_count: :desc).pluck(:id) ordered_photo_ids.in_groups_of(1000, false).each do |photo_ids| photos = Photo.order(likes_count: :desc).where(id: photo_ids) # ... end
또한 내부 호출에 질의하여 추가 주문하는 것이 중요합니다.
-
==============================
4.하나의 옵션 (ID 보통, created_at 중복이있을 수 있습니다 주문 레코드에 대한 더 나은 선택이다, 말하자면) 모델 자체에 특정 모델에 맞는 구현을 배치하는 것입니다 :
하나의 옵션 (ID 보통, created_at 중복이있을 수 있습니다 주문 레코드에 대한 더 나은 선택이다, 말하자면) 모델 자체에 특정 모델에 맞는 구현을 배치하는 것입니다 :
class Thing < ActiveRecord::Base def self.find_each_desc limit batch_size = 1000 i = 1 records = self.order(created_at: :desc).limit(batch_size) while records.any? records.each do |task| yield task, i i += 1 return if i > limit end records = self.order(created_at: :desc).where('id < ?', records.last.id).limit(batch_size) end end end
아니면 당신은 일을 조금 일반화, 모든 모델에 그것을 작업을 할 수 있습니다 :
lib 디렉토리 / active_record_extensions.rb :
ActiveRecord::Batches.module_eval do def find_each_desc limit batch_size = 1000 i = 1 records = self.order(id: :desc).limit(batch_size) while records.any? records.each do |task| yield task, i i += 1 return if i > limit end records = self.order(id: :desc).where('id < ?', records.last.id).limit(batch_size) end end end ActiveRecord::Querying.module_eval do delegate :find_each_desc, :to => :all end
설정 / 초기화 / extensions.rb :
require "active_record_extensions"
추신 나는이 대답에 따라 파일의 코드를 걸었습니다.
-
==============================
5.당신은 표준 루비 반복자에 의해 뒤쪽을 반복 할 수 있습니다 :
당신은 표준 루비 반복자에 의해 뒤쪽을 반복 할 수 있습니다 :
Thing.last.id.step(0,-1000) do |i| Thing.where(id: (i-1000+1)..i).order('id DESC').each do |thing| #... end end
참고 : 쿼리에있을 것입니다 것은 모두 경계를 포함하지만 우리가 하나를 포함 할 필요가있는 BETWEEN 때문에 하나입니다.
물론, 그들 중 일부는 이미 삭제되기 때문에이 방법으로 일괄 이하 1,000 이상의 레코드를 가져올있을 수 있지만 내 경우에는 괜찮습니다.
-
==============================
6.나는 같은 동작을 찾고이 액까지 생각되었다. 이 created_at에 의해 NOT 명령을합니까하지만 난 어쨌든 게시 할 것이라고 생각했다.
나는 같은 동작을 찾고이 액까지 생각되었다. 이 created_at에 의해 NOT 명령을합니까하지만 난 어쨌든 게시 할 것이라고 생각했다.
max_records_to_retrieve = 50000 last_index = Thing.count start_index = [(last_index - max_records_to_retrieve), 0].max Thing.active.find_each(:start => start_index) do |u| # do stuff end
이 방법의 단점 : - 당신은이 쿼리가 필요합니다 (첫 번째는 빨라야한다) -이 50K 기록의 최대 보장하지만 ID를 생략하는 경우 당신은 더 적은 얻을 것이다.
-
==============================
7.당신은 AR-AS-일괄 보석을 시도 할 수 있습니다.
당신은 AR-AS-일괄 보석을 시도 할 수 있습니다.
자신의 문서에서 당신은 이런 식으로 뭔가를 할 수
Users.where(country_id: 44).order(:joined_at).offset(200).as_batches do |user| user.party_all_night! end
-
==============================
8.코멘트 중 하나에 @Kirk에 의해 언급 된 바와 같이, find_each 지원 버전 5.1.0의로 제한합니다.
코멘트 중 하나에 @Kirk에 의해 언급 된 바와 같이, find_each 지원 버전 5.1.0의로 제한합니다.
변경 로그의 예 :
Post.limit(10_000).find_each do |post| # ... end
문서는 말합니다 :
(사용자 지정 순서를 설정하는 것은 여전히 있지만 지원되지 않음)
-
==============================
9.미나리 또는 다른 뭔가를 사용하면 쉽게 될 것입니다.
미나리 또는 다른 뭔가를 사용하면 쉽게 될 것입니다.
module BatchLoader extend ActiveSupport::Concern def batch_by_page(options = {}) options = init_batch_options!(options) next_page = 1 loop do next_page = yield(next_page, options[:batch_size]) break next_page if next_page.nil? end end private def default_batch_options { batch_size: 50 } end def init_batch_options!(options) options ||= {} default_batch_options.merge!(options) end end
class ThingRepository include BatchLoader # @param [Integer] per_page # @param [Proc] block def batch_changes(per_page=100, &block) relation = Thing.active.order("created_at DESC") batch_by_page do |next_page| query = relation.page(next_page).per(per_page) yield query if block_given? query.next_page end end end
repo = ThingRepository.new repo.batch_changes(5000).each do |g| g.each do |t| #... end end
-
==============================
10.주문 find_in_batches를 추가하는 것은 이미 필요 일괄 처리 및 주문하지만,이 ID를 가진 한 경우, 내 유스 케이스를 해결했다. 그것은 @ 더크 - geurs 솔루션에 의해 영감을했다
주문 find_in_batches를 추가하는 것은 이미 필요 일괄 처리 및 주문하지만,이 ID를 가진 한 경우, 내 유스 케이스를 해결했다. 그것은 @ 더크 - geurs 솔루션에 의해 영감을했다
# Create file config/initializers/find_in_batches_with_order.rb with follwing code. ActiveRecord::Batches.class_eval do ## Only flat order structure is supported now ## example: [:forename, :surname] is supported but [:forename, {surname: :asc}] is not supported def find_in_batches_with_order(ids: nil, order: [], batch_size: 1000) relation = self arrangement = order.dup index = order.find_index(:id) unless index arrangement.push(:id) index = arrangement.length - 1 end ids ||= relation.order(*arrangement).pluck(*arrangement).map{ |tupple| tupple[index] } ids.each_slice(batch_size) do |chunk_ids| chunk_relation = relation.where(id: chunk_ids).order(*order) yield(chunk_relation) end end end
여기에 요점을 떠나 https://gist.github.com/the-spectator/28b1176f98cc2f66e870755bb2334545
-
==============================
11.하나의 쿼리와 피할 반복하는 그것을 수행
하나의 쿼리와 피할 반복하는 그것을 수행
User.offset (2) .order ( '이름 DESC'). 마지막으로 (3)
이 같은 의지 제품 A를 쿼리
SELECT "사용자". * "사용자"순서와 이름 ASC의 LIMIT BY $ 1 [ "OFFSET"[[ "LIMIT", 3] $ 2 OFFSET 2]
from https://stackoverflow.com/questions/15189937/activerecord-find-each-combined-with-limit-and-order by cc-by-sa and MIT license
'RUBY-ON-RAILS' 카테고리의 다른 글
[RUBY-ON-RAILS] 이미지 사용 루비와 같은 base64로 문자열을 저장하는 방법 (0) | 2020.03.01 |
---|---|
[RUBY-ON-RAILS] 당신의 Gemfile이 들러의 이전 버전을 필요로하는 경우 어떻게`install '이다 번들? (0) | 2020.03.01 |
[RUBY-ON-RAILS] getaddrinfo는 : 노드 이름도 servname는 제공 여부를 알려 (0) | 2020.02.29 |
[RUBY-ON-RAILS] AWS S3 : 당신은 액세스를 시도하는 버킷은 지정된 엔드 포인트를 사용하여 해결해야 (0) | 2020.02.29 |
[RUBY-ON-RAILS] 루비 온 레일즈 버튼 아약스처럼 (0) | 2020.02.29 |