Database 对于有可能进行查找的有序集,使用什么样的Erlang数据结构?

Database 对于有可能进行查找的有序集,使用什么样的Erlang数据结构?,database,data-structures,erlang,set,Database,Data Structures,Erlang,Set,我正在解决一个问题,需要记住接收事件的顺序,但也需要根据事件的id查找事件。如果没有第三方库,我如何在Erlang中高效地执行此操作?请注意,我有许多可能短暂的参与者,每个参与者都有自己的事件(已经考虑过记忆,但它需要表中的原子,如果我的参与者死了,表将继续存在) 您的问题清楚地表明您希望按ID查找,但不完全清楚您是否希望按时间或基于时间查找或遍历数据,以及您可能希望在这方面执行的操作;您说“记住事件的顺序”,但使用ID字段的索引存储记录将实现这一点 如果您只需要按ID进行查找,那么任何常见的可

我正在解决一个问题,需要记住接收事件的顺序,但也需要根据事件的id查找事件。如果没有第三方库,我如何在Erlang中高效地执行此操作?请注意,我有许多可能短暂的参与者,每个参与者都有自己的事件(已经考虑过记忆,但它需要表中的原子,如果我的参与者死了,表将继续存在)


您的问题清楚地表明您希望按ID查找,但不完全清楚您是否希望按时间或基于时间查找或遍历数据,以及您可能希望在这方面执行的操作;您说“记住事件的顺序”,但使用ID字段的索引存储记录将实现这一点

如果您只需要按ID进行查找,那么任何常见的可疑项都可以用作合适的存储引擎,因此ets、gb_树和dict等都是不错的选择。不要使用mnesia,除非您需要交易和安全以及所有这些良好的功能;mnesia很好,但所有这些东西都要付出很高的性能代价,从你的问题来看,你是否需要它还不清楚

如果您想按时间查找或遍历数据,则考虑ETS表<代码> OrdEdEdTest< /Cord>。如果这能满足你的需要,那么这可能是一个不错的选择。在这种情况下,您将使用两个表,一个

set
按ID提供哈希查找,另一个
ordered\u set
按时间戳进行查找或遍历


如果您有两种不同的查找方法,那么就无法回避需要两个索引的事实。您可以将整个记录同时存储在这两个文件中,或者,假设您的ID是唯一的,您可以将ID作为数据存储在
有序的\u集中。您选择哪一种,实际上是在存储利用率和读写性能之间进行权衡。

根据Michael答案评论中讨论的细节,一个非常简单的,可行的方法是在流程状态变量中创建一个元组,该元组分别存储事件顺序和事件的K-V存储

考虑:

%%% Some type definitions so we know exactly what we're dealing with.
-type id()     :: term().
-type type()   :: atom().
-type data()   :: term().
-type ts()     :: calendar:datetime().
-type event()  :: {id(), ts(), type(), data()}.
-type events() :: dict:dict(id(), {type(), data(), ts()}).

% State record for the process.
% Should include whatever else the process deals with.
-record(s,
        {log    :: [id()],
         events :: event_store()}).

%%% Interface functions we will expose over this module.
-spec lookup(pid(), id()) -> {ok, event()} | error.
lookup(Pid, ID) ->
    gen_server:call(Pid, {lookup, ID}).

-spec latest(pid()) -> {ok, event()} | error.
latest(Pid) ->
    gen_server:call(Pid, get_latest).

-spec notify(pid(), event()) -> ok.
notify(Pid, Event) ->
    gen_server:cast(Pid, {new, Event}).

%%% gen_server handlers
handle_call({lookup, ID}, State#s{events = Events}) ->
    Result = find(ID, Events),
    {reply, Result, State};
handle_call(get_latest, State#s{log = [Last | _], events = Events}) ->
    Result = find(Last, Events),
    {reply, Result, State};
% ... and so on...

handle_cast({new, Event}, State) ->
    {ok, NewState} = catalog(Event, State),
    {noreply, NewState};
% ...

%%% Implementation functions
find(ID, Events) ->
    case dict:find(ID, Events) of
        {Type, Data, Timestamp} -> {ok, {ID, Timestamp, Type, Data}};
        Error                   -> Error
    end.

catalog({ID, Timestamp, Type, Data},
        State#s{log = Log, events = Events}) ->
    NewEvents = dict:store(ID, {Type, Data, Timestamp}, Events),
    NewLog = [ID | Log],
    {ok, State#s{log = NewLog, events = NewEvents}}.
这是一个完全简单的实现,它将数据结构的细节隐藏在流程接口后面。为什么我要选一个口述?只是因为(很容易)。在不了解您的需求的情况下,我真的没有理由在gb_树上的映射上选择dict等。如果您有相对较小的数据(数百或数千个要存储的内容),这些结构之间的性能通常不会有明显的差异

重要的是,您应该清楚地确定这个过程应该响应什么消息,然后通过在这个模块上创建一个公开函数的接口,迫使自己在项目代码的其他地方坚持使用它。在这之后,你可以把口述换成别的东西。如果您确实只需要最新的事件ID,并且永远不需要从序列日志中提取第n个事件,那么您可以放弃日志,只需将最后一个事件的ID保留在记录中而不是列表中

所以,先做一些非常简单的工作,然后确定它是否真的适合你的需要。如果没有,那么调整它。如果现在还可以,就用它来运行吧——不要过分关注性能或存储(除非你真的被迫这么做)


如果您稍后发现您有性能问题,请将dict和list切换为其他内容——可能是gb_-tree或orddict或ETS或其他内容。关键是现在就开始工作,这样您就有了一个基础来评估功能,并在必要时运行基准测试。(但绝大多数情况下,我发现作为一个特定的原型,无论我开始使用什么,结果都非常接近最终的解决方案。)

我希望能够获得最新的最旧事件,并根据id查找或删除任意事件。可能
有序集
ets表对您来说会很好,正如您拥有的
ets:last/1
。但是,如果您只需要按时间取出最后一条记录,那么您可以只使用
set
ets表,按ID查找,并分别记住最新的记录,不是吗?你只需要一张桌子就可以大大减少你的开销。如果事件总是按顺序到达,则此最新记录始终是收到的最后一条记录,但如果不是,则仅比较时间戳并不难。我尝试单独进行比较,但问题是用于排序的数据结构是什么<代码>队列
是通过我所了解的列表实现的,
命令集
是相同的。队列使用列表,但它与列表不同。问题是我们现在正在深入到你的问题变得如此具体,以至于很难真正知道绝对最好的答案是什么。你的问题很笼统,但是,你得到了一个相当笼统的答案,但现在你开始认真考虑,完美的选择可能还取决于你一次存储了多少事件。一个足够小的列表可以执行一些更高级的结构。我不确定你为什么要考虑队列,我只能想象这是因为你想要一个管道,在管道的一端推动,另一端离开,但你的问题不是这样读的。。。我认为你应该考虑创建一个实现你的存储需求的外观模块,然后尝试用最有可能的候选来实现它,如ETS表、GBBREST和队列,如果你认为合适的话…然后,您可以为所需的操作对您的实现进行基准测试。它们不是参与者,而是过程/petpeeve@zxq9离我足够近了,每个人都有自己的。(^.^)我之所以特别强调这一点,只是因为这些术语实际上意味着一些略有不同的东西。Erlang是在卡尔·休伊特的构想之外开发的
%%% Some type definitions so we know exactly what we're dealing with.
-type id()     :: term().
-type type()   :: atom().
-type data()   :: term().
-type ts()     :: calendar:datetime().
-type event()  :: {id(), ts(), type(), data()}.
-type events() :: dict:dict(id(), {type(), data(), ts()}).

% State record for the process.
% Should include whatever else the process deals with.
-record(s,
        {log    :: [id()],
         events :: event_store()}).

%%% Interface functions we will expose over this module.
-spec lookup(pid(), id()) -> {ok, event()} | error.
lookup(Pid, ID) ->
    gen_server:call(Pid, {lookup, ID}).

-spec latest(pid()) -> {ok, event()} | error.
latest(Pid) ->
    gen_server:call(Pid, get_latest).

-spec notify(pid(), event()) -> ok.
notify(Pid, Event) ->
    gen_server:cast(Pid, {new, Event}).

%%% gen_server handlers
handle_call({lookup, ID}, State#s{events = Events}) ->
    Result = find(ID, Events),
    {reply, Result, State};
handle_call(get_latest, State#s{log = [Last | _], events = Events}) ->
    Result = find(Last, Events),
    {reply, Result, State};
% ... and so on...

handle_cast({new, Event}, State) ->
    {ok, NewState} = catalog(Event, State),
    {noreply, NewState};
% ...

%%% Implementation functions
find(ID, Events) ->
    case dict:find(ID, Events) of
        {Type, Data, Timestamp} -> {ok, {ID, Timestamp, Type, Data}};
        Error                   -> Error
    end.

catalog({ID, Timestamp, Type, Data},
        State#s{log = Log, events = Events}) ->
    NewEvents = dict:store(ID, {Type, Data, Timestamp}, Events),
    NewLog = [ID | Log],
    {ok, State#s{log = NewLog, events = NewEvents}}.