Утечка памяти в модуле Ruby net / ldap - PullRequest
5 голосов
/ 23 июля 2010

Как часть моего приложения на Rails, я написал небольшой импортер, который всасывает данные из нашей системы LDAP и встраивает их в таблицу User. К сожалению, код, связанный с LDAP, теряет огромные объемы памяти при переборе наших 32-килобайтных пользователей, и я не смог выяснить, как решить эту проблему.

Эта проблема, похоже, как-то связана с библиотекой LDAP, поскольку, когда я удаляю вызовы LDAP-содержимого, использование памяти стабилизируется. Кроме того, распространяются следующие объекты: Net :: BER :: BerIdentifiedString и Net :: BER :: BerIdentifiedArray , обе являются частью библиотеки LDAP.

Когда я запускаю импорт, использование памяти в итоге достигает максимума более 1 ГБ. Мне нужно найти способ исправить мой код, если проблема есть, или обойти проблемы с памятью LDAP, если именно в этом проблема. (Или, если есть лучшая библиотека LDAP для больших импортов для Ruby, я тоже открыт для этого.)

Вот соответствующий бит нашего кода:

require 'net/ldap'
require 'pp'

class User < ActiveRecord::Base
  validates_presence_of :name, :login, :email

  # This method is resonsible for populating the User table with the
  # login, name, and email of anybody who might be using the system.
  def self.import_all
    # initialization stuff. set bind_dn, bind_pass, ldap_host, base_dn and filter

    ldap = Net::LDAP.new
    ldap.host = ldap_host
    ldap.auth bind_dn, bind_pass
    ldap.bind

    begin
      # Build the list
      records = records_updated = new_records = 0
      ldap.search(:base => base_dn, :filter => filter ) do |entry|
        name = entry.givenName.to_s.strip + " " + entry.sn.to_s.strip
        login = entry.name.to_s.strip
        email = login + "@txstate.edu"
        user = User.find_or_initialize_by_login :name => name, :login => login, :email => email
        if user.name != name
          user.name = name
          user.save
          logger.info( "Updated: " + email )
          records_updated = records_updated + 1
        elsif user.new_record?
          user.save
          new_records = new_records + 1
        else
          # update timestamp so that we can delete old records later
          user.touch
        end
        records = records + 1
      end

      # delete records that haven't been updated for 7 days
      records_deleted = User.destroy_all( ["updated_at < ?", Date.today - 7 ] ).size

      logger.info( "LDAP Import Complete: " + Time.now.to_s )
      logger.info( "Total Records Processed: " + records.to_s )
      logger.info( "New Records: " + new_records.to_s )
      logger.info( "Updated Records: " + records_updated.to_s ) 
      logger.info( "Deleted Records: " + records_deleted.to_s )

    end

  end
end

Заранее спасибо за любую помощь / указатели!

Кстати, я спрашивал об этом и на форуме поддержки net / ldap, но там не было никаких полезных указателей.

1 Ответ

8 голосов
/ 27 июля 2010

Очень важно отметить, что вы никогда не используете результат вызова метода. Это означает, что вы должны передать :return_result => false ldap.search:

ldap.search(:base => base_dn, :filter => filter, :return_result => false ) do |entry|

Из документов: "Когда: return_result => false, #search будет возвращать только логическое значение, чтобы указать, успешно ли выполнена операция. Это может повысить производительность при очень больших наборах результатов, поскольку библиотека может отбрасывать каждую запись из памяти после того, как ваш блок обработает его. "

Другими словами, если вы не используете этот флаг, все записи будут сохранены в памяти, даже если они вам не нужны вне блока! Итак, используйте эту опцию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...