erlang functional

Спасаем данные из ets таблиц

Dmitry Vasiliev 14:28, 2011 3 26

На днях прочитал отличную статью от Steve Vinoski - Don’t Lose Your ets Tables. Давно задумывался над этой проблемой - если процесс использует ets таблицы для хранения данных и перезапускается супервизором после неожиданного завершения, то все данные в таблицах будут потеряны. Во многих случаях данные могут накапливаться за время жизни процесса и не могут быть просто пересозданы даже перезапуском всех процессов. Steve предлагает воспользоваться возможностью указания наследника (heir) для таблицы и возможностью передавать таблицу другому процессу с помощью функции ets:give_away.

Вчера решил более плотно заняться этой проблемой и реализовать gen_server модуль ets_manager, который делает следующее:

  • При создании через него таблицы, ets_manager указывает свой рабочий процесс в качестве наследника;
  • При закрытии процесса работающего с таблицей рабочий процесс ets_manager получает и сохраняет таблицу у себя;
  • Пересозданный супервизором процесс забирает таблицу с данными у рабочего процесса ets_manager;

Рассмотрим работу ets_manager подробнее. Я буду рассматривать только функции относящиеся к менеджеру, опуская функционал обычно присутствующий в любом модуле реализующем gen_server.

Основная функция модуля с которой общаются остальные процессы называется new и повторяет поведение ets:new:

new(Name, Options) when is_atom(Name), is_list(Options) ->
    Key = {Name, Options},
    Server = whereis(?MODULE),
    case gen_server:call(Server, {get, Key}) of
        {ok, Tag} ->
            receive
                {'ETS-TRANSFER', Table, Server, Tag} ->
                    Table
            after
                ?TIMEOUT ->
                    exit(timeout)
            end;
        {error, not_found} ->
            O = case lists:keymember(heir, 1, Options) of
                true ->
                    Options;
                false ->
                    [{heir, Server, Key} | Options]
            end,
            ets:new(Name, O)
    end.

При вызове функции она сначала запрашивает у рабочего процесса ets_manager таблицу с переданными данными и если таблица найдена, то ожидается сообщение передачи таблицы функцией ets:give_away. Если у менеджера нет такой таблицы, то она создается с указанием рабочего процесса ets_manager как наследника.

На стороне рабочего процесса менеджера вызов gen_server:call обрабатывается с помощью функции-обработчика handle_call:

handle_call({get, Key}, {Process, Tag}, Tables) ->
    case dict:find(Key, Tables) of
        {ok, Table} ->
            true = ets:give_away(Table, Process, Tag),
            {reply, {ok, Tag}, dict:erase(Key, Tables)};
        error ->
            {reply, {error, not_found}, Tables}
    end;
handle_call(_Request, _From, State) ->
    {reply, {error, badarg}, State}.

Первым делом мы ищем таблицу в переменной состояния процесса - Tables, являющейся экземпляром dict. Если таблица найдена, то она передается клиенту с помощью вызова ets:give_away.

Последняя значимая функция менеджера - обработчик сообщения о наследовании таблицы handle_info:

handle_info({'ETS-TRANSFER', Table, _, Key}, Tables) ->
    {noreply, dict:store(Key, Table, Tables)};
handle_info(_Info, State) ->
    {noreply, State}.

При получении сообщения о наследовании таблицы (в случае завершения процесса работающего с таблицей) таблица сохраняется в состоянии рабочего процесса ets_managet - Tables.

Так как функциональность рабочего процесса ets_manager минимальна (функции handle_call и handle_info), вероятность его неожиданного завершения намного меньше чем процессов работающих с таблицами ets. Таким образом ets_manager позволяет сохранять данные ets таблиц между перезапусками процессов работающих с ets таблицами. Функция ets_manager:new повторяет поведение ets:new, что позволяет просто заменить вызов ets:new на вызов ets_manager:new. Рассмотри работу менеджера подробнее.

Первым делом запускаем рабочий процесс ets_manager:

1> ets_manager:start().
{ok,<0.34.0>}

После этого создаем таблицу с помощью функции ets_manager:new (используя такие же параметры как и при вызове ets:new) и добавляем тестовые данные:

2> T = ets_manager:new(test, [set, private]).
16400
3> ets:insert(T, [{1, one}, {2, two}]).
true
4> ets:i(T).
<1   > {2,two}
<2   > {1,one}
EOT  (q)uit (p)Digits (k)ill /Regexp -->q
ok

Теперь завершаем текущий процесс оболочки, владеющий таблицей:

5> exit().
*** Shell process terminated! ***
Eshell V5.8.3  (abort with ^G)

После повторного создания таблицы через ets_manager:new старые данные все еще на месте:

1> T = ets_manager:new(test, [set, private]).
16400
2> ets:i(T).
<1   > {2,two}
<2   > {1,one}
EOT  (q)uit (p)Digits (k)ill /Regexp -->q
ok

Add comment

Name:
Email: (Never will be published.)
Web site:
Comment: (Paragraphs divided by empty lines, line breaks and links will be automatically formatted.)