como tratamos com eficiência as restrições de tempo nos registros de mnésia?
Pergunta
Estou escrevendo registros em mnésia que devem ser mantidos lá apenas por um tempo permitido (24 horas). após 24 horas, antes que um usuário modifique parte deles, o sistema deve removê-los automaticamente. por exemplo, um usuário recebe tempo de antena gratuito (para chamadas de voz) que eles devem usar em um determinado momento. se não o usarem, após 24 horas, o sistema deve remova essas reservas de recursos do registro de usuários.
Agora, isso trouxe cronômetros. um exemplo de estrutura de registro é:
-record(free_airtime, { reference_no, timer_object, %% value returned by timer:apply_after/4 amount }).
O objeto timer no registro é importante porque no caso do usuário
finalmente coloca em uso os recursos reservados antes que eles se esgotem
(ou se eles expirarem), o sistema pode chamar timer:cancel/1
de modo a aliviar
o servidor de cronômetro a partir deste objeto.
Agora o problema, eu tenho duas maneiras de lidar com temporizadores nesses registros:
Opção 1: cronômetros controlados na transação
reserve_resources(Reference_no,Amnt)-> F = fun(Ref_no,Amount) -> case mnesia:read({free_airtime,Ref_no}) of [] -> case mnesia:write(#free_airtime{reference_no = Ref_no,amount = Amount}) == ok of true -> case timer:apply_after(timer:hours(24),?MODULE,reference_no_timed_out,[Ref_no]) of {ok,Timer_obj} -> [Obj] = mnesia:read({free_airtime,Ref_no}), mnesia:write(Obj#free_airtime{timer_object = Timer_obj}); _ -> mnesia:abort({error,failed_to_time_object}) end; false -> mnesia:abort({error,write_failed}) end; [_] -> mnesia:abort({error,exists,Ref_no}) end end, mnesia:activity(transaction,F,[Reference_no,Amnt],mnesia_frag).
Sobre a opção acima.
Os documentos da Mnesia dizem que as transações podem ser repetidas pelo gerenciador de tm (por algum motivo)
até que tenham sucesso, e então quando você colocar o código que é io:format/2
ou qualquer outro que não tenha nada a ver com
escreve ou lê, pode ser executado várias vezes. Esta declaração me fez parar neste ponto
e pensar em uma maneira de lidar com temporizadores fora da própria transação, então modifiquei o código como
segue:
Opção 2: cronômetros controlados fora da transação
reserve_resources(Reference_no,Amnt)-> F = fun(Ref_no,Amount) -> case mnesia:read({free_airtime,Ref_no}) of [] -> P = #free_airtime{reference_no = Ref_no,amount = Amount}, ok = mnesia:write(P), P; [_] -> mnesia:abort({error,exists,Ref_no}) end end, Result = try mnesia:activity(transaction,F,[Reference_no,Amnt],mnesia_frag) of Any -> Any catch exit:{aborted,{error,exists,XX}} -> {exists,XX} E1:E2 -> {error,{E1,E2}} end, on_reservation(Result). on_reservation(#free_airtime{reference_no = Some_Ref})-> case timer:apply_after(timer:hours(24),?MODULE,reference_no_timed_out,[Some_Ref]) of {ok,Timer_obj} -> [Obj] = mnesia:activity(transaction,fun(XX) -> mnesia:read({free_airtime,XX}) end,[Some_Ref],mnesia_frag), ok = mnesia:activity(transaction,fun(XX) -> mnesia:write(XX) end,[Obj#free_airtime{timer_object = Timer_obj}],mnesia_frag); _ -> ok = mnesia:activity(transaction,fun(XX) -> mnesia:delete({free_airtime,XX}) end,[Some_Ref],mnesia_frag), {error,failed_to_time_object} end; on_reservation(Any)-> Any.
O código para controlar o tempo limite da reserva:
reference_no_timed_out(Ref_no)-> do_somethings_here..... then later remove this reservation from the database....below.. ok = mnesia:activity(transaction,fun(XX) -> mnesia:delete({free_airtime,XX}) end,[Ref_no],mnesia_frag).
Agora pensei que na opção 2, estou mais seguro mantendo o processamento do cronômetro codificar, mesmo quando mnesia_tm executa novamente a transação devido aos seus motivos , este trecho de código não é executado duas vezes (evito ter vários objetos timer contra o mesmo registro).
Pergunta 1: Qual dessas duas implementações está certa? e / ou errado? Me diga (também) se ambos estão errados
Pergunta 2: O cronômetro do módulo é adequado para lidar com um grande número de cronômetros empregos na produção?
Pergunta 3: em comparação com o timer_mn-1.1 , que roda no topo da mnésia, é o módulo do cronômetro (possivelmente rodando no topo das tabelas Ets) menos capaz (de verdade) na produção? (estou perguntando porque usando o timer_mn de Sean Hinde em um sistema que está usando mnesia aparece ser um problema em termos de mudanças de esquema, problemas de nó e.t.c)
Se alguém tiver outra maneira de lidar com problemas relacionados ao cronômetro com mnésia, me atualize obrigado pessoal ...
Solução
Pergunta 1:
Controle o cronômetro fora da transação. Quando as transações colidem na Mnesia, elas são simplesmente repetidas. Isso daria a você mais de uma referência de cronômetro e dois acionadores do cronômetro. Não é um problema em si, mas se você esperar até o sucesso da transação antes de instalar o cronômetro, poderá evitar o problema.
A segunda solução é o que eu faria. Se o TX estiver bom, você pode instalar um temporizador nele. Se o cronômetro disparar e não houver referência ao objeto, não importa. Você só deve se preocupar se essa situação acontecer muito, já que você teria um grande número de temporizadores perdidos.
Pergunta 2:
O módulo do cronômetro é legal, mas o guia de desempenho recomenda que você use os BIFs erlang:start_timer
, consulte
http://www.erlang.org/doc/efficiency_guide/commoncaveats. html # id58959
Eu introduziria um processo separado como um gen_server
que lida com as coisas de tempo. Você envia a ele uma mensagem remove(timer:hours(24), RefNo)
e então ele inicia um cronômetro, obtém um TRef
e instala um {TRef, RefNo, AuxData}
de mapeamento no Mnesia ou no ETS. Quando o cronômetro é acionado, o processo pode gerar um auxiliar removendo a entrada RefNo
da tabela principal.
Neste ponto, você deve estar se perguntando sobre travamentos. O gen_server
de remoção pode falhar. Além disso, todo o nó pode falhar. Como você deseja reinstalar os temporizadores caso isso aconteça é com você, mas você deve refletir sobre isso acontecer para que possa resolver. Suponha que voltemos e as informações do temporizador sejam carregadas do disco. Como você planeja reinstalar os temporizadores?
Uma maneira é fazer com que AuxData
contenha informações sobre o ponto de tempo limite. A cada hora ou 15 minutos, você examina toda a mesa, removendo caras que não deveriam estar lá. Na verdade, você pode optar por esta ser a principal forma de remover estruturas de temporizador. Sim, você dará às pessoas 15 minutos de tempo extra no pior caso, mas pode ser mais fácil de lidar com o código. Pelo menos ele lida melhor com o caso em que o nó (e, portanto, os temporizadores) morre.
Outra opção é trapacear e apenas armazenar temporizações em uma estrutura de dados, o que torna muito barato encontrar todos os RefNos expirados nos últimos 5 minutos e depois executá-los a cada 5 minutos. Fazer coisas em massa provavelmente será mais eficaz. Esse tipo de manipulação em massa é muito usado por kernels de sistema operacional, por exemplo.
Pergunta 3
Não sei nada sobre timer-tm
, desculpe :)