Эрланг и его потребление памяти кучи - PullRequest
4 голосов
/ 18 августа 2011

На моих серверах HP Proliant запущено приложение с высокой степенью одновременности. Приложение - это индексатор файловой системы, который я написал на erlang. Он порождает процесс для каждой папки, найденной в файловой системе, и записывает все пути к файлам во фрагментированной базе данных Mnesia. (База данных состоит из таблиц disc_only_copies и снимок экрана ее файловой системы можно просмотреть здесь .)

Фрагмент кода, который выполняет интенсивную работу по прохождению файловой системы, показан ниже:


%%% -------- COPYRIGHT NOTICE --------------------------------------------------------------------
%% @author Muzaaya Joshua, <joshmuza@gmail.com> [http://joshanderlang.blogspot.com]
%% @version 1.0 free software, but modification prohibited
%% @copyright Muzaaya Joshua (file_scavenger-1.0) 2011 - 2012 . All rights reserved
%% @reference <a href="http://www.erlang.org">OpenSource Erlang WebSite</a>
%% 
%%% ---------------- EDOC INTRODUCTION TO THE MODULE ----------------------------------------------
%% @doc This module provides the low level APIs for reading, writing,
%% searching, joining and moving within directories.The module implementation
%% took place on @date at @time.
%% @end

-module(file_scavenger_utilities).

%%% ------- EXPORTS -------------------------------------------------------------------------------
-compile(export_all).

%%% ------- INCLUDES -----------------------------------------------------------------------------

%%% -------- MACROS ------------------------------------------------------------------------------
-define(IS_FOLDER(X),filelib:is_dir(X)).
-define(IS_FILE(X),filelib:is_file(X)).
-define(FAILED_TO_LIST_DIR(X),error_logger:error_report(["*** File Scavenger Utilities Error ***** ",{error,"Failed to List Directory"},{directory,X}])).
-define(NOT_DIR(X),error_logger:error_report(["*** File Scavenger Utilities Error ***** ",{error,"Not a Directory"},{alleged,X}])).
-define(NOT_FILE(X),error_logger:error_report(["*** File Scavenger Utilities Error ***** ",{error,"Not a File"},{alleged,X}])).
%%%--------- TYPES -------------------------------------------------------------------------------

%% @type dir() = string(). 
%%  Must be containing forward slashes, not back slashes. Must not end with a slash
%%  after the exact directory.e.g this is wrong: "C:/Program Files/SomeDirectory/"
%%  but this is right: "C:/Program Files/SomeDirectory"
%% @type file_path() = string(). 
%%  Must be containing forward slashes, not back slashes.
%%  Should include the file extension as well e.g "C:/Program Files/SomeFile.pdf"

%% -----------------------------------------------------------------------------------------------
%% @doc Enters a directory and executes the fun ForEachFileFound/2 for each file it finds
%% If it finds a directory, it executes the fun %% ForEachDirFound/2. 
%% Both funs above take the parent Dir as the first Argument. Then, it will spawn an 
%% erlang process that will spread the found Directory too in the same way as the parent directory 
%% was spread. The process of spreading goes on and on until every File (wether its in a nested 
%% Directory) is registered by its full path.
%% @end
%%
%% @spec spread_directory(dir(),dir(),funtion(),function())-> ok.

spread_directory(Dir,Top_Directory,ForEachFileFound,ForEachDirFound) when is_function(ForEachFileFound),is_function(ForEachDirFound) ->
    case ?IS_FOLDER(Dir) of
        false -> ?NOT_DIR(Dir); 
        true -> 
            F = fun(X)->
                    FileOrDir = filename:absname_join(Dir,X),
                    case ?IS_FOLDER(FileOrDir) of
                        true -> 
                            (catch ForEachDirFound(Top_Directory,FileOrDir)),
                            spawn(fun() -> ?MODULE:spread_directory(FileOrDir,Top_Directory,ForEachFileFound,ForEachDirFound) end);
                        false -> 
                            case ?IS_FILE(FileOrDir) of
                                false -> {error,not_a_file,FileOrDir};
                                true -> (catch ForEachFileFound(Top_Directory,FileOrDir))
                            end
                    end
                end,
            case file:list_dir(Dir) of      
                {error,_} -> ?FAILED_TO_LIST_DIR(Dir);
                {ok,List} -> lists:foreach(F,List)
            end
    end.    

Функция spread_directory/4 является общей в том смысле, что она занимает два funs. Одно удовольствие: ForEachFileFound/2 берет вместе с каталогом Top Most, найденным файлом и делает с ним что угодно, а другое удовольствие: ForEachDirFound/2 берет вместе с каталогом Top Most, папкой, которую он находит, и использует его любым способом, каким захочет .

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

#!/usr/bin/env sh
echo "Starting File Scavenger System. Layer 1 on the P2P File Sharing System....."
erl \
    -name file_scavenger@127.0.0.1 \
    +P 13421779 \
    -pa ./ebin ./lib/*/ebin ./include \
    -mnesia dir '"./database"' \
    -mnesia dump_log_write_threshold 10000 \
    -eval "application:load(file_scavenger)" \
    -eval "application:start(file_scavenger)"

Существует gen_server, который связывает интенсивный модуль с базой данных, в которую я записываю все пути. Ниже приведен фрагмент, где начинается работа spread_directory:

handle_cast(index_dirs,#scavenger{directory_paths = Dirs} = State)->
    {File,Folder} = case {State#scavenger.verbose,State#scavenger.verbose_to} of
                        {true,tty} -> 
                            {
                            fun(TopDir,Fl)-> 
                                io:format(" File: ~p~n",[Fl]),
                                file_scavenger_database:insert_file(filename:basename(Fl),file,Fl,TopDir,filename:extension(Fl))
                            end,
                            fun(TopDir,Fd) -> 
                                io:format(" Folder: ~p~n",[Fd]),
                                file_scavenger_database:insert_file(Fd,folder,Fd,TopDir,undefined)
                            end
                            };
                        {true,SomeFile}-> 
                            {
                            fun(TopDir,Fl)-> 
                                os:cmd("echo File: " ++ Fl ++ " >> " ++ SomeFile),
                                file_scavenger_database:insert_file(filename:basename(Fl),file,Fl,TopDir,filename:extension(Fl))
                            end,
                            fun(TopDir,Fd)-> 
                                os:cmd("echo Folder: " ++ Fd ++ " >> " ++ SomeFile),
                                file_scavenger_database:insert_file(Fd,folder,Fd,TopDir,undefined)
                            end
                            }                       
                    end,
    Main = fun(Dir) -> 
                error_logger:info_msg("*** File scavenger Server indexing directory: ~p~n",[Dir]),
                spawn(fun() -> file_scavenger_utilities:spread_directory(Dir,Dir,File,Folder) end)
            end,
    lists:foreach(Main,Dirs),
    {noreply,State};    
handle_cast(stop, State) -> {stop, normal, State}.

Дополнительные сведения об источнике можно найти во всем приложении. Весь исходный код приложения и сборку можно найти здесь: File_scavenger-1.0.zip .

Теперь я запускаю приложение на сервере (HP Proliant G6, содержащем процессоры Intel (2 процессора, каждые 4 ядра, скорость 2,4 ГГц каждое ядро, размер кэша 8 МБ), объем ОЗУ 20 ГБ, дисковое пространство 1,5 ТБ. Теперь 2 из этих мощных машин находятся в нашем распоряжении. Системная база данных должна быть реплицирована на две. Каждый сервер работает под управлением Solaris 10, 64-битный), чей терминал теперь выглядит следующим образом:

bash-3.00# sh file_scavenger.sh
Starting File Scavenger System. Layer 1 on the P2P File Sharing System.....
Erlang R14B03 (erts-5.8.4) [source] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.4  (abort with ^G)
(file_scavenger@127.0.0.1)1>
=INFO REPORT==== 18-Aug-2011::09:36:04 ===
Starting File Scavenger Database......
=INFO REPORT==== 18-Aug-2011::09:36:04 ===
Database Successfully Started....

=INFO REPORT==== 18-Aug-2011::09:36:04 ===
Starting File Scavenger Database......
=INFO REPORT==== 18-Aug-2011::09:36:04 ===
Database Successfully Started....

=INFO REPORT==== 18-Aug-2011::09:36:04 ===
File Scavenger Server starting with default verbose settings....

(file_scavenger@127.0.0.1)1> file_scavenger_server:index_dirs().

Сервер начинает работать и выводит на терминал все файлы и папки, которые он находит. Сервер оснащен слишком большим объемом оперативной памяти (20 ГБ) и местом подкачки (объем подкачки составляет 16 ГБ). Однако он работал около 18 часов, и, наконец, виртуальная машина erlang сообщила следующее:

File: "/proc/4324/root/opt/csw/gcc4/share/locale/ja/LC_MESSAGES/gcc.mo"
 Folder: "/proc/4324/root/opt/csw/gcc4/share/locale/da"
 Folder: "/proc/4324/root/opt/csw/gcc4/share/locale/es/LC_MESSAGES"
 File: "/proc/4324/root/proc/4984/root/.thumbnails/normal/dc259e3897e8af4b379c6d956b6c1393.png"
 File: "/proc/4324/root/proc/4984/root/.thumbnails/fail/gnome-thumbnail-factory/223c19786421b7101d14075bdec46f61.png"
 File: "/proc/4324/root/opt/csw/gcc4/libexec/gcc/i386-pc-solaris2.10/4.5.1/install-tools/mkheaders"
 File: "/proc/4324/root/opt/csw/gcc4/libexec/gcc/i386-pc-solaris2.10/4.5.1/cc1plus"
 File: "/proc/4324/root/opt/csw/gcc4/lib/libsupc++.la"

Crash dump was written to: erl_crash.dump
eheap_alloc: Cannot allocate 153052320 bytes of memory (of type "heap").
Abort - core dumped
bash-3.00#

Вопрос 1. С таким мощным сервером, почему операционная система не может предоставить такую ​​память приложению (это было единственное запущенное приложение)?

Вопрос 2. Запущенный эмулятор Эрланга дает возможность создавать столько процессов, сколько ему нужно. значение +P 13421779. Erlang VM не может получить доступ к этой памяти или не может выделить ее для своих процессов?

Вопрос 3. Для Solaris он видит один процесс: epmd, возможно, содержащий и запускающий тысячи микропотоков. Какие конфигурации я могу сделать в Solaris, чтобы иметь возможность никогда не останавливать мое приложение, каким бы «голодным» оно ни было? Доступное пространство подкачки составляет 16 ГБ, ОЗУ 20 ГБ, честно говоря, должно быть что-то не так.

Вопрос 4. Какие конфигурации я могу настроить для эмулятора Erlang, чтобы избежать этих аварийных дампов динамической памяти, особенно когда вся необходимая память доступна на сервере? Как я буду запускать больше приложений, потребляющих память, на этом сервере, если Erlang все еще не в состоянии выделить такую ​​память простому индексатору файловой системы (ну, в общем, сильно параллельному)?

наконец, приветствуются все другие настройки, которые я мог бы сделать, чтобы избежать проблем с кучей памяти на таком способном оборудовании. Заранее спасибо

1 Ответ

6 голосов
/ 18 августа 2011

У меня не было времени взглянуть на источник, но вот некоторые комментарии:

Вопрос 1. Почему на таком мощном сервере операционная система не может предоставить такую ​​памятьприложение (оно было единственным запущенным приложением)?

Поскольку виртуальная машина Erlang пыталась использовать больше доступной свободной памяти.

Вопрос 2. Эмулятор Erlang istart запускается, чтобы иметь возможность создавать столько процессов, сколько ему нужно.значение + P 13421779. Erlang VM не может получить доступ к этой памяти или не может выделить ее для своих процессов?

Нет.Если бы у вас закончились процессы, виртуальная машина Erlang сказала бы так (и виртуальная машина все еще будет работать):

=ERROR REPORT==== 18-Aug-2011::10:04:04 ===
Error in process <0.31775.138> with exit value: {system_limit,[{erlang,spawn_link,    [erlang,apply,[#Fun<shell.3.130303173>,[]]]},{erlang,spawn_link,1},{shell,get_command,5},    {shell,server_loop,7}]}

Вопрос 3. Для Solaris он видит один процесс: epmd, возможно, содержит и запускает тысячи микропотоков.Какие конфигурации я могу сделать в Solaris, чтобы иметь возможность никогда не останавливать мое приложение, каким бы «голодным» оно ни было?Доступное пространство подкачки составляет 16 ГБ, ОЗУ - 20 ГБ, честно говоря, должно быть что-то не так.

epmd - демон отображения портов Erlang.Он отвечает за управление дистрибутивом Erlang и не имеет ничего общего с вашим отдельным приложением Erlang.Процессы, которые вы должны искать, скорее всего будут называться beam.smp.Они покажут потребление памяти ОС виртуальной машиной Erlang и т. Д.

Вопрос 4. Какие конфигурации можно выполнить для эмулятора Erlang, чтобы избежать этих аварийных дампов кучи памяти, особенно когда вся память может понадобитьсядоступно на сервере?Как я буду запускать больше приложений, потребляющих память, на этом сервере, если Erlang по-прежнему не может выделить такую ​​память простому индексатору файловой системы (ну, в общем, сильно параллельному)?

Виртуальная машина Erlang должна иметь возможность использовать вседоступной памяти на вашем компьютере.Однако это зависит от того, как написано ваше заявление.Причин утечки памяти может быть много:

  • Заполнение таблиц Atom (вы создаете слишком много уникальных атомов)
  • Таблицы ETS или Mnesia не удаляются мусором (вы не удаляете старыенеиспользуемые элементы)
  • Недостаточно памяти для процессов (вы породили слишком много процессов)
  • Создано слишком много двоичных файлов (вы можете сохранить неиспользуемые ссылки на старые двоичные файлы)
...