insert_all을 사용하여 Rails에서 많은 레코드 만들기

11365 단어 rubyrails
Rails 앱에서 많은 데이터를 가져오는 것을 상상해 보십시오. 나는 > 100,000 레코드에 대해 이야기하고 있습니다. 그냥 100,000 x Address.create(data)를 실행하는 것이 좋은 생각인가요? 아니, 아마 아닐거야. Rails 데이터베이스에서 데이터를 대량으로 생성하는 더 좋은 방법이 무엇인지 알아보겠습니다.



고객 데이터가 포함된 csv 파일에서 데이터를 가져오는 앱을 만들고 있습니다. 파일에는 6개의 필드로 구성된 1,000개의 행이 있습니다. 다음과 같이 보입니다.

id,first_name,last_name,email,gender,ip_address
1,Lianne,Hosten,[email protected],Female,112.33.73.252
2,Aubrie,Dorin,[email protected],Female,254.88.120.47
...


요구 사항은 간단합니다. csv의 각 행에 Customer라는 저장된 ActiveRecord 대응 항목이 있어야 합니다.

모델



모델 및 관련 마이그레이션:

# The model
class Customer < ApplicationRecord
end

# And its migration
class CreateCustomer < ActiveRecord::Migration[7.0]
  def change
    create_table :uploads do |t|
      t.string :first_name
      t.string :last_name
      t.string :email
      t.string :gender
      t.string :ip_address

      t.timestamps      
    end
  end
end


데이터 삽입



앱에 csv 파일을 도입하는 방법에는 여러 가지가 있습니다. URL에서 다운로드하거나 파일 시스템 또는 파일 입력이 있는 제출된 양식에서 읽을 수 있습니다. 어느 쪽이든 대부분 컨트롤러 작업에서 csv 파일로 끝납니다.

작동시키기



소프트웨어를 개발할 때 대부분의 경우 성능에 집중하기 전에 먼저 작업을 수행하는 데 집중해야 합니다. 따라서 작동하는 것을 만들어 봅시다.

require 'csv'
require 'benchmark'

class CustomersController < ApplicationController

  def create_bulk
    file = params[:csv_file] # Uploaded through a form

    time = Benchmark.measure do
      CSV.open(file.path, headers: true).each do |row|
        Customer.create(row)
      end
    end

    puts time
  end
end



벤치마크 결과: 3.884280 0.871490 4.755770 ( 13.748814)예, 내 컴퓨터에서 1,000명의 고객을 생성하는 데 13초가 걸렸습니다. 250,000개를 수입한다고 상상해보세요...

빠르게 작업하기



너무 느립니다. 우리 뭐 할까? insert_all 이라는 ActiveRecord의 메서드가 있습니다.

메서드insert_all는 레코드당 속성 배열을 수신하고 SQLINSERT 문을 생성하여 모든 레코드를 한 여행에 삽입합니다. 예를 들어:

customers = [
  {first_name: 'Lianne', last_name: 'Hosten', email: '[email protected]', gender: 'Female', ip_address: '112.33.73.252'},
  {first_name: 'Aubrie', last_name: 'Dorin', email: '[email protected]', gender: 'Female', ip_address: '254.88.120.47'},
  ...
]
Customer.insert_all(customers)


결과 SQL 문:

Customer Bulk Insert (7.9ms)  INSERT INTO "customers"
("first_name","last_name","email","gender","ip_address","created_at","updated_at") 
VALUES 
('Lianne', 'Hosten', '[email protected]', 'Female', '112.33.73.252', STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW'), STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), 
('Aubrie', 'Dorin', '[email protected]', 'Female', '254.88.120.47', STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW'), STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),
...


앱에서 시도해 봅시다.

...
class CustomersController < ApplicationController

  def create_bulk
    file = params[:csv_file] # Uploaded through a form

    time = Benchmark.measure do
      CSV.open(file.path, headers: true).each_slice(250) do |rows|
        Customer.insert_all(rows.map(&:to_h))
      end
    end

    puts time
  end
end



벤치마크 결과: 0.047267 0.035247 0.082514 ( 0.130676)
이봐, each_slice가 뭐야? 일반적으로each는 배열을 열거하고 작업할 각 요소를 제공합니다. 그러나 each_slice는 작업할 n개의 요소를 다시 제공합니다. 네, 배열을 돌려줍니다.

csv에는 1,000개의 행이 포함되어 있으므로 위의 코드는 1,000INSERT 쿼리 대신 1,000/250 = 4 SQLINSERT 쿼리를 생성합니다. 가져오기 속도를 높이는 또 다른 점은 Customer 모델이 인스턴스화되지 않아 훨씬 더 많은 CPU 주기를 줄일 수 있다는 것입니다.

13초가 아닌 0.13초... 100배 빨라졌습니다.
알겠습니다. #create를 다시 사용해야 하는 이유는 무엇입니까? 계속 읽어보세요.

가능할 때마다 insert_all을 사용해야 합니까?



와우, 100배 빠르다, 캐치가 있어야 한다. 예, 있고 큰 것입니다. 여기서 큰 단점은 콜백과 유효성 검사가 트리거되지 않는다는 것입니다.

반복합니다: insert_all 를 사용할 때 콜백 및 유효성 검사가 트리거되지 않습니다.

따라서 insert_all와 함께 사용하는 데이터가 유효한지 확인하십시오. 콜백을 확인하여 insert_all 이전 또는 이후에 실행해야 하는 항목이 있는지 확인하십시오.

중요 사항


  • insert_all는 콜백 및 유효성 검사를 무시해도 되는 경우에만 사용하십시오.
  • 내가 비교한 벤치마크 결과는 소위 경과된 실시간입니다. 경과 시간에 대한 인간의 감각을 제공하는 데는 좋지만 CPU가 코드를 씹느라 바빴던 시간을 정말로 비교하고 싶을 때는 덜 이상적입니다.
  • 좋은 웹페이지 즐겨찾기