2016-12-09 3 views
1

5MM 라인 파일을 읽으려고하고 있는데, 지금은 heroku에서 내 메모리 사용량을 초과하고 있습니다. 내 방법은 다소 빠르다 ~ 200 초/초 .. 나는 그것이 수입에 충돌하고 있다고 생각한다. 그래서 나의 계획은 1,000 또는 10,000의 배치로 수입했다. 나는 내 파일의 끝 부분에있어 말할 어떻게 내 질문은, 루비는 .eof 방법하지만 그것 File 방법이 있고, 나는CSV 구문 분석이 너무 많은 메모리를 가져옴

def self.import_parts_db(file) 
     time = Benchmark.measure do 
      Part.transaction do 
       parts_db = [] 
       CSV.parse(File.read(file), headers: true) do |row| 
        row_hash = row.to_hash 
        part = Part.new(
         part_num: row_hash["part_num"], 
         description: row_hash["description"], 
         manufacturer: row_hash["manufacturer"], 
         model: row_hash["model"], 
         cage_code: row_hash["cage_code"], 
         nsn: row_hash["nsn"] 
         ) 
        parts_db << part 
       end 
       Part.import parts_db 
      end 
     end 
     puts time 
    end 

답변

2

1 내 루프에서 호출하는 방법을 잘 모르겠어요 문제

거대한 파일에 File.read(file)을 사용하자마자 스크립트에서 많은 메모리를 사용합니다 (너무 많음). CSV은 줄 단위로 읽지 만 전체 파일을 1 개의 거대한 문자열로 읽습니다.

수천 개의 행이있는 파일을 사용할 때 문제가 없을 수 있습니다. 그래도 CSV.foreach을 사용해야합니다. this 예에서

CSV.foreach(file, headers: true) do |row| 

CSV.parse(File.read(file), headers: true) do |row| 

변경 , 메모리 사용량은 0.5MB로 1기가바이트에서 갔다.

두번째 문제는

parts_db은 CSV 파일의 매우 끝날 때까지 계속 성장 부품의 거대한 배열,이된다. 트랜잭션을 제거해야합니다 (가져 오기 속도는 느리지 만 1 행보다 많은 메모리가 필요하지 않음). 또는 CSV를 일괄 적으로 처리해야합니다.

하나의 가능성이 있습니다.

def self.import_parts_db(filename) 
    time = Benchmark.measure do 
    File.open(filename) do |file| 
     headers = file.first 
     file.lazy.each_slice(2000) do |lines| 
     Part.transaction do 
      rows = CSV.parse(lines.join, write_headers: true, headers: headers) 
      parts_db = rows.map do |_row| 
      Part.new(
       part_num: row_hash['part_num'], 
       description: row_hash['description'], 
       manufacturer: row_hash['manufacturer'], 
       model: row_hash['model'], 
       cage_code: row_hash['cage_code'], 
       nsn: row_hash['nsn'] 
      ) 
      end 
      Part.import parts_db 
     end 
     end 
    end 
    puts time 
    end 
end 

3 문제 : 우리는 CSV.parse는하지만 2000 라인의 배치와 다시 사용할 수 있습니까?

이전 답변은 메모리를 많이 사용하지 않아야하지만 모든 내용을 가져 오는 데 오랜 시간이 걸릴 수 있으며 원격 서버의 경우 너무 많을 수 있습니다.

열거자를 사용하는 이점은 일괄 처리를 건너 뛰고 원하는 것만 가져 오는 것이 쉽다는 것입니다.

가져 오기가 너무 오래 걸리고 424000 회 성공적으로 가져온 후에 어떤 이유로 중지한다고 가정 해 봅시다. 첫 번째 424000 개 CSV 라인을 건너 뛰려면

file.lazy.each_slice(2000) do |lines| 

file.lazy.drop(424_000).take(300_000).each_slice(2000) do |lines| 

에 의해, 다음 300000 사람을 구문 분석 :

당신은 대체 할 수 있습니다. 다음 가져 오기

는 사용 후

file.lazy.drop(424_000+300_000).take(300_000).each_slice(2000) do |lines| 

과 :

file.lazy.drop(424_000+2*300_000).take(300_000).each_slice(2000) do |lines| 

을 ...

+0

위의 경우에는 .foreach가 권장됩니다.이 인스턴스에서는 작동하지 않습니까? – gemart

+0

'CSV.foreach'는'each_slice'에 필요한 Enumerable을 반환하지 않습니다. 'File.read()'를 사용하지 않는 한,'CSV.parse'를 사용해도됩니다. 두 번째 경우에는 2000 줄만입니다. –

+0

당신의 예제를 사용했고 424,000 수입 후에 heroku 콘솔에서'ETIMEDOUT : read ETIMEDOUT' 오류가 발생했습니다. 내가 어떻게 돌아갈 수 있는지 아십니까? – gemart

0

CSV.parse은 처리하는 블록에 하나의 구문 분석 된 CSV 행을 전달하는 데 매우 효율적입니다. 문제는 CSV 파서에서 온 것이 아니며 parts_db 어레이를 메모리에 구축하면 발생합니다. 한 번에 전체 레코드 배열 대신 한 줄씩 데이터를 가져 오도록 Part.import 메서드를 다시 작성하는 것이 좋습니다.