erlang ets:update_counter

Aug 19, 2014 09:41

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

У хранения данных в процессе есть и другой минус: когда этих данных очень много (мегабайты), они начинают создавать нагрузку на сборщик мусора и в случае падения процесса, они могут похоронить штатным error_logger-ом весь рантайм (тут надо, конечно, менять на lager).

Когда обращений за данными становится много (десятки и сотни тысяч в секунду), то выделенный процесс становится бутылочным горлышком и его либо разбивают на шарды, либо перекладывают данные в ets.



Чтение из публичной ets хорошо работает с нескольких ядер. Я не очень хорошо представляю себе эффект от флагов read_concurrency и write_concurrency, так что если кто-то поделится, будет прекрасно.

Однако запись в ets не через центральную точку может привести к классическим рейсам. У нас это выливалось в отрицательное количество клиентов.

Динамика ошибки такая:

case ets:lookup(sessions, Id) of
[#session{access = granted}] -> decrement_counter(), ets:delete(sessions, Id);
_ -> ets:delete(sessions, Id)
end

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

Для исправления пришлось отказаться от читаемых access = granted/denied и заменить на поле allowed = 0/1

Код поменялся на:

try ets:update_counter(sessions, Id, [ {#session.allowed,0}, {#session.allowed, -1, 0, 0} ]) of
[1,0] -> decrement_counter(), ets:delete(sessions, Id);
_ -> ets:delete(sessions, Id)
catch
_:_ -> ok
end

Происходит здесь следующее: в ets посылается атомарный пакет из нескольких команд на изменение счетчика. Первая команда говорит инкрементнуть счетчик на 0 и вернуть текущее значение. Вторая команда говорит увеличить счетчик на -1, но не ниже 0 и если ниже 0, то выставить 0 и вернуть новое значение.

Таким образом мы можем зафиксировать переход из состояния «сессия активирована» в «сессия нерабочая». Ни один другой тред такой комбинации не получит, все увидят [0,0] либо вообще провалятся в catch из-за того, что запись уже удалена.

Эта хитрая магия позволила не делать шардированный пул воркеров, занимающихся удалением сессий.

erlyvideo, erlang, магия

Previous post Next post
Up