Мнезия: Как заблокировать несколько строк одновременно, чтобы я мог писать / читать «согласованный» набор записей - PullRequest
7 голосов
/ 30 октября 2011

КАК Я ХОЧУ, Я ПОДРАЗУМЕЛ МОЙ ВОПРОС, ЧТОБЫ НАЧАТЬ

Возьмите таблицу с 26 клавишами, a-z, и пусть они имеют целочисленные значения. Создайте процесс, Ой, который делает две вещи снова и снова

  1. В одной транзакции запишите случайные значения для a , b и c , чтобы эти значения всегда были суммой до 10
  2. В другой транзакции прочитайте значения для a , b и c и пожалуйтесь, если их значения не составляют 10

Если вы раскрутите даже некоторые из этих процессов, вы увидите, что очень быстро a , b и c находятся в состоянии, в котором их значения не суммируйте до 10. Я считаю, что нет способа попросить mnesia «заблокировать эти 3 записи до начала записи (или чтения)», mnesia может блокировать записи только по мере их поступления (так сказать), который допускает, чтобы набор значений записей нарушал мое ограничение "необходимо суммировать до 10".

Если я прав, решения этой проблемы включают

  1. заблокировать всю таблицу перед записью (или чтением) набора из 3 записей - я не хочу блокировать всю таблицу для 3 записей,
  2. Создайте процесс, который отслеживает, кто читает или пишет какие ключи, и защищает массовые операции от записи или чтения кем-либо еще, пока операция не будет завершена. Конечно, я должен убедиться, что все процессы использовали это ... дерьмо, я думаю, это означает, что я должен написать свой собственный AccessMod в качестве четвертого параметра для Activity / 4, что выглядит как нетривиальное упражнение
  3. Еще кое-что, что я недостаточно умен, чтобы понять.

мысли

Хорошо, я амбициозный новичок Эрланга, извините, если это глупый вопрос, но

Я создаю распределенный кэш в памяти для конкретного приложения, и мне нужно иметь возможность записывать наборы пар Key, Value в одной транзакции, а также получать наборы значений в одной транзакции. Другими словами мне нужно 1) Записать 40 ключ, значение пар в кеш и убедиться, что никто другой не может прочитать или записать любой из этих 40 ключей во время этой операции многоключевой записи; а также, 2) Считать 40 ключей за одну операцию и вернуть 40 значений, зная, что все 40 значений не изменились с момента начала этой операции чтения и до ее завершения.

Единственный способ сделать это - заблокировать всю таблицу в начале fetch_keylist ([ListOfKeys]) или в начале write_keylist ([KeyValuePairs], но я не хочу этого делать потому что у меня есть много процессов, одновременно выполняющих свои собственные чтения и записи multi_key, и я не хочу блокировать всю таблицу в любое время, когда любому процессу нужно читать / записывать относительно небольшое подмножество записей.

Помощь

Попытка быть более ясной: я не думаю, что это просто использование ванильных транзакций

Я думаю Я задаю более тонкий вопрос, чем этот. Представьте, что у меня есть процесс, который в рамках транзакции повторяет 10 записей, блокируя их по ходу. Теперь представьте, что этот процесс начинается, но перед тем, как перейти к третьей записи, ДРУГОЙ процесс обновляет третью запись. Это будет хорошо, если транзакции идут, потому что первый процесс еще не заблокировал 3-ю запись (пока), а ДРУГОЙ процесс изменил ее и выпустил ее до того, как первый процесс получил к ней доступ. Что я хочу, так это быть гарантированным, что, как только мой первый процесс запустит , никакой другой процесс не сможет коснуться 10 записей, пока первый процесс не будет завершен с ними.

Проблема решена - я идиот ... наверное ... Спасибо всем за ваших пациентов, особенно Hynek -Pichi- Vychodil!Я подготовил свой тестовый код, чтобы показать проблему, и я мог бы фактически воспроизвести проблему.Затем я упростил код для удобства чтения, и проблема исчезла.Я не смог снова воспроизвести проблему.Это и смущает, и загадочно для меня, так как у меня была эта проблема в течение нескольких дней.Кроме того, mnesia никогда не жаловалась на то, что я выполняю операции вне транзакции, и в моем коде нет грязных транзакций, я понятия не имею, как мне удалось внедрить эту ошибку в мой код!

Я набралПонятие Изоляции в мою голову, и я не буду сомневаться, что оно снова существует.

Спасибо за образование.

На самом деле, выясняется, что проблема заключалась в том, чтобы использовать попытку / ловить вокруг операций mnesia в пределах транзакция.См. здесь для получения дополнительной информации.

Ответы [ 3 ]

2 голосов
/ 01 ноября 2011

Mnesia сделка сделает именно эту вещь для вас. Это то, что является транзакцией, если вы не делаете грязные операции. Так что просто поместите ваши операции записи и чтения в одну транзакцию, а mnesia сделает все остальное. Все операции в одной транзакции выполняются как одна атомарная операция. Уровень изоляции транзакции Mnesia - это то, что иногда называют «сериализуемым», т. Е. Самый сильный уровень изоляции.

Edit:

Кажется, вы упустили один важный момент о параллельных процессах в Erlang. (Чтобы быть справедливым, это верно не только в Erlang, но и в любой действительно параллельной среде, и когда кто-то спорит, это не совсем параллельная среда.) Вы не можете различить, какие действия происходят первыми, а какие - вторыми, если вы не выполняете некоторую синхронизацию. Единственный способ сделать эту синхронизацию - передача сообщений. Вы гарантировали только одну вещь о сообщениях в Erlang, порядок сообщений, отправленных одним процессом другому процессу. Это означает, что когда вы отправляете два сообщения M1 и M2 из процесса A в процесс B, они приходят в том же порядке. Но если вы отправите сообщение M1 с A на B и сообщение M2 с C на B, они могут прийти в любом порядке. Просто потому, что как вы можете узнать, какое сообщение вы отправили первым? Еще хуже, если вы отправляете сообщение M1 с A на B, а затем M2 с A на C и когда M2 приходит на C отправляет M3 с C на B вы не гарантировали, что M1 прибудет на B до M3. Даже это произойдет в одной виртуальной машине в текущей реализации. Но вы не можете полагаться на это, потому что это не гарантировано и может измениться даже в следующей версии виртуальной машины только из-за реализации передачи сообщений между различными планировщиками.

Это иллюстрирует проблемы упорядочения событий в параллельных процессах. Теперь вернемся к сделке Mnesia. Mnesia сделка должна быть без побочных эффектов fun. Это означает, что никакое сообщение не может быть отправлено извне из транзакции. Таким образом, вы не можете сказать, какая транзакция начинается первой и когда начинается. Единственное, что вы можете сказать, если транзакция прошла успешно и они упорядочены, вы можете определить только по ее эффекту. Когда вы думаете об этом, ваше тонкое разъяснение не имеет смысла. Одна транзакция будет считывать все ключи в атомарной операции, даже если она реализована как чтение одного ключа за другим в реализации транзакции, и ваша операция записи также будет выполняться как атомарная операция. Вы не можете сказать, была ли запись в 4-й ключ во второй транзакции произошла после того, как вы прочитали 1-й ключ в первой транзакции, потому что там это не наблюдается снаружи. Обе транзакции будут выполняться в определенном порядке как отдельная атомарная операция. С внешней точки зрения все ключи будут прочитаны в один и тот же момент времени, и это задача mnesia - заставить его. Если вы отправляете сообщение изнутри транзакции, вы нарушаете свойство транзакции mnesia и не удивляетесь, что это будет вести себя странно. Чтобы быть конкретным, это сообщение может быть отправлено много раз.

Edit2:

Если вы раскрутите даже несколько из этих процессов, вы увидите, что очень быстро a, b и c находятся в состоянии, когда их значения не равны 10.

Мне любопытно, почему вы думаете, что это произойдет, или вы проверили это? Покажите мне ваш тестовый пример, и я покажу мой:

-module(transactions).

-export([start/2, sum/0, write/0]).

start(W, R) ->
  mnesia:start(),
  {atomic, ok} = mnesia:create_table(test, [{ram_copies,[node()]}]),
  F = fun() ->
      ok = mnesia:write({test, a, 10}),
      [ ok = mnesia:write({test, X, 0}) || X <-
        [b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z]],
      ok
  end,
  {atomic, ok} = mnesia:transaction(F),
  F2 = fun() ->
    S = self(),
    erlang:send_after(1000, S, show),
    [ spawn_link(fun() -> writer(S) end) || _ <- lists:seq(1,W) ],
    [ spawn_link(fun() -> reader(S) end) || _ <- lists:seq(1,R) ],
    collect(0,0)
  end,
  spawn(F2).

collect(R, W) ->
  receive
    read -> collect(R+1, W);
    write -> collect(R, W+1);
    show ->
      erlang:send_after(1000, self(), show),
      io:format("R: ~p, W: ~p~n", [R,W]),
      collect(R, W)
  end.

keys() ->
  element(random:uniform(6),
    {[a,b,c],[a,c,b],[b,a,c],[b,c,a],[c,a,b],[c,b,a]}).

sum() ->
  F = fun() ->
      lists:sum([X || K<-keys(), {test, _, X} <- mnesia:read(test, K)])
  end,
  {atomic, S} = mnesia:transaction(F),
  S.

write() ->
  F = fun() ->
      [A, B ] = L = [ random:uniform(10) || _ <- [1,2] ],
      [ok = mnesia:write({test, K, V}) || {K, V} <- lists:zip(keys(),
          [10-A-B|L])],
      ok
  end,
  {atomic, ok} = mnesia:transaction(F),
  ok.

reader(P) ->
  case sum() of
    10 ->
      P ! read,
      reader(P);
    _ ->
      io:format("ERROR!!!~n",[]),
      exit(error)
  end.

writer(P) ->
  ok = write(),
  P ! write,
  writer(P).

Если это не сработает, это будет действительно серьезная проблема. Есть серьезные приложения, в том числе платежные системы, которые полагаются на это. Если у вас есть тестовый пример, который показывает, что он не работает, сообщите об ошибке по адресу erlang-bugs@erlang.org

1 голос
/ 01 ноября 2011

Вы пробовали mnesia Events ? Вы можете сделать так, чтобы читатель подписывался на события Table Events mnesia, особенно write, чтобы не прерывать процесс записи. Таким образом, mnesia просто продолжает посылать копию того, что было написано в режиме реального времени, другому процессу, который проверяет, какие значения есть в любой момент времени. взгляните на это:

subscriber()->
    mnesia:subscribe({table,YOUR_TABLE_NAME,simple}),
    %% OR mnesia:subscribe({table,YOUR_TABLE_NAME,detailed}),
    wait_events().<br>
wait_events()->
receive
     %% For simple events
    {mnesia_table_event,{write, NewRecord, ActivityId}} -> 
        %% Analyse the written record as you wish
        wait_events();
     %% For detailed events
    {mnesia_table_event,{write, YOUR_TABLE, NewRecord, [OldRecords], ActivityId}} ->
        %% Analyse the written record as you wish
        wait_events();
    _Any -> wait_events()
end.
Теперь вы создаете свой анализатор как процесс, подобный этому:
spawn(?MODULE,subscriber,[]).

Это заставляет весь процесс работать без блокировки какого-либо процесса, mnesia не нужно блокировать никакие табели или записи, потому что теперь у вас есть процесс writer и процесс analyser. Все это будет работать в режиме реального времени. Помните, что есть много других событий, которые вы можете использовать, если хотите, сопоставляя их с шаблоном в теле подписчика wait_events().
Можно построить heavy duty gen_server или полный application, предназначенный для приема и анализа всех ваших событий mnesia. Обычно лучше иметь одного способного подписчика, чем много провальных подписчиков. Если я правильно понял ваш вопрос, это unblocking решение соответствует вашим требованиям.

0 голосов
/ 01 ноября 2011

мнезия: чтение / 3 с блокировками записи кажется достаточным.

Транзакция Mnesia реализуется с помощью блокировки чтения-записи, и блокировки правильно сформированы (удержание блокировки до конца транзакции). Таким образом, уровень изоляции сериализуем.

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

...