2011-07-12 9 views
30

Mam aplikację Rails, która przetwarza dużą (miliony) liczbę rekordów w bazie danych mysql. Gdy zacznie działać, jego wykorzystanie pamięci szybko rośnie z prędkością 50 MB na sekundę. Za pomocą takich narzędzi jak oink udało mi się zawęzić główną przyczynę do jednej pętli, która przechodzi przez wszystkie rekordy w dużej tabeli w bazie danych.Wyciek pamięci Ruby on Rails podczas przechodzenia przez dużą liczbę rekordów; find_each nie pomaga

Rozumiem, że jeśli użyję czegoś w rodzaju Person.all.each, wszystkie rekordy zostaną załadowane do pamięci. Jednak jeśli przejdę na find_each, nadal widzę ten sam problem z pamięcią. Aby jeszcze bardziej wyizolować problem, stworzyłem następujący kontroler testowy, który nie robi nic poza zapętlaniem rekordów. Przypuszczam, że find_each zachowuje tylko małą liczbę obiektów w pamięci za każdym razem, ale użycie pamięci rośnie liniowo podczas jej wykonywania.

class TestController < ApplicationController 
    def memory_test 
    Person.find_each do |person| 
    end 
end 

Podejrzeń, że ma to do czynienia z buforowaniem ActiveRecord wyników zapytania. Ale sprawdziłem ustawienia środowiska i wszystkie opcje związane z buforowaniem są ustawione na false (używam domyślnych ustawień tworzonych przez szyny). Zrobiłem kilka wyszukiwania w Internecie, ale nie mogłem znaleźć rozwiązania.

Używam szyn 3.1.0 RC1 i Ruby 1.9.2

dzięki!

+0

Domyślam się, że w ActiveRecord istnieje funkcja o nazwie 'find_in_batches'. Może to pomoże kontrolować wybuch pamięci. – rubish

+0

Myślałem też, że wygląda to tak, jakby 'find_each' używał' find_in_batches' pod okładką. Może każdy pojedynczy wiersz jest duży i może skorzystać z opcji ': batch_size' (domyślnie do 1000 wierszy). – Brian

+0

Co to jest kod, który faktycznie robi, że musi przechodzić przez każdy rekord? – Maran

Odpowiedz

37

Udało mi się to rozgryźć. Są dwa miejsca do zmiany.

Po pierwsze, wyłącz IdentityMap. W config/environment.rb

config.active_record.identity_map = false 

drugie, należy bez używania pamięci podręcznej omotać pętli

class MemoryTestController < ApplicationController 
    def go 
    ActiveRecord::Base.uncached do 
     Person.find_each do |person| 
     # whatever operation 
     end 
    end 
    end 
end 

Teraz moje użycie pamięci jest pod kontrolą. Mam nadzieję, że to pomaga innym ludziom.

+0

DZIĘKUJEMY DZIĘKUJEMY, DZIĘKUJĘ –

+0

Czy powinienem używać tego w widokach ilekroć przechodzę przez duże ilości danych? – bcackerman

+0

W oparciu o dokumentację, ['identity_map' jest domyślnie wyłączona] (http://apidock.com/rails/ActiveRecord/IdentityMap), więc musisz tylko upewnić się, że nie jest ustawiona na wartość true w twojej bieżącej konfiguracji (conajmniej chciałbym pomyśleć o tym, aby przetestować to sam). – MaxGabriel

3

Tak samo dobry jak ActiveRecord, nie jest najlepszym narzędziem do wszystkich problemów. Zalecam zejść do rodzimej karty bazy danych i wykonać pracę na tym poziomie.

+0

Nie wszystkie zadania można wykonać w SQL, większość czasu potrzebujemy do przetworzenia skomplikowanej logiki biznesowej ... – linjunhalida

2

dzwoni find_in_batches z partią wielkości 1000 pod maską.

Wszystkie zapisy w partii zostaną utworzone i zachowane w pamięci tak długo, jak długo przetwarzana jest partia.

Jeśli rekordy są duże lub jeśli zużywają dużo pamięci poprzez kolekcjach proxy (np bufory HAS_MANY wszystkie jego elementy w każdej chwili go używać), można także spróbować mniejszą wielkość wsadu:

Person.find_each batch_size: 100 do |person| 
    # whatever operation 
    end 

Możesz również spróbować ręcznie wywoływać GC.start okresowo (np. Co 300 pozycji).