Skip to main content

Mnesia

Continuing with my expiring records module (see my previous blog), I've now switched it over to using Mnesia, rather than a dictionary object stored in the state of my gen_server instance.
It is still not a truly global, cross-node key/value store for expiring records, but it is getting there. I wanted to focus first on getting my tests to pass again with a RAM based table on one node. I just need to tweak the values passed in when creating the Mnesia table, I think, to make this work with a disc based table, replicated across nodes.
Currently the table is created like this:
prepare_table() ->
    case catch mnesia:table_info(expiring_records, attributes) of
        {'EXIT', _} ->
            %% Table does not exist - create it
            erlang:display("Creating table"),
            mnesia:create_table(
                expiring_records, [
                    {attributes, record_info(fields, record)},
                    {record_name, record}
                ]
            ),
            ok;
        _Attributes ->
            ok
    end,
    mnesia:wait_for_tables([expiring_records], infinite).
I've also cleaned up the API, wrapping the gen_server:call calls:
get_non_expired_record(_Config) ->
    ok = expiring_records:store("bingo", "bongo", erlang:system_time(second) + 3600),
    {ok, "bongo"} = expiring_records:fetch("bingo").

get_expired_record(_Config) ->
    ok = expiring_records:store("bingo", "bongo", erlang:system_time(second) + 1),
    timer:sleep(2000),
    not_found = expiring_records:fetch("bingo").
I've also broken the handle_call callback into multiple definitions rather than using a case statement:
handle_call({add, {Key, Value, ExpiresAt}}, _From, State) ->
    Trans = fun() ->
        Record = #record{key=Key, value=Value, expires_at = ExpiresAt},
        mnesia:write(expiring_records, Record, write)
    end,
    {atomic, ok} = mnesia:transaction(Trans),
    {reply, ok, State};

handle_call({fetch, Key}, _From, State) ->
    Trans = fun() ->
        Result = mnesia:match_object(expiring_records, #record{key = Key, value = '_', expires_at = '_'}, read),
        case Result of
            [{record, Key, Value, ExpiresAt}] ->
                Now = erlang:system_time(second),
                case Now < ExpiresAt of
                    true ->
                        {ok, Value};
                    _ ->
                        mnesia:delete(expiring_records, Key, write),
                        not_found
                end;
            [] ->
                not_found
        end
    end,
    {atomic, Result} = mnesia:transaction(Trans),
    {reply, Result, State};

handle_call(size, _From, State) ->
    Size = mnesia:table_info(expiring_records, size),
    {reply, Size, State};
This looks much better, and is getting closer to my intended functionality. My next session will focus on getting a disc based, replicated Mnesia table. Then I also need add to add trimming, to remove expired records from the table even if they aren't being looked up.

Popular posts from this blog

Waiting for an answer

I want to describe my first iteration of exsim, the core server for the large scale simulation I described in my last blog post. A Listener module opens a socket for listening to incoming connections. Once a connection is made, a process is spawned for handling the login and the listener continues listening for new connections. Once logged in, a Player is created, and a Solarsystem is started (if it hasn't already). The solar system also starts a PhysicsProxy, and the player starts a Ship. These are all GenServer processes. The source for this is up on GitHub: https://github.com/snorristurluson/exsim Player The player takes ownership of the TCP connection and handles communication with the game client (or bot). Incoming messages are parsed in handle_info/2 and handled by the player or routed to the ship, as appropriate. The player creates the ship in its init/1 function. The state for the player holds the ship and the name of the player. Ship The ship holds the state of the ship - …

Mnesia queries

I've added search and trim to my expiring records module in Erlang. This started out as an in-memory key/value store, that I then migrated over to using Mnesia and eventually to a replicated Mnesia table. The fetch/1 function is already doing a simple query, with match_object. Result=mnesia:match_object(expiring_records, #record{key=Key, value='_', expires_at='_'}, read) The three parameters there are the name of the table - expiring_records, the matching pattern and the lock type (read lock). The fetch/1 function looks up the key as it was added to the table with store/3. If the key is a tuple, we can also do a partial match: Result=mnesia:match_object(expiring_records, #record{key= {'_', "bongo"}, value='_', expires_at='_'}, read) I've added a search/1 function the module that takes in a matching pattern and returns a list of items where the key matches the pattern. Here's the test for the search/1 function: search_partial_…

Large scale ambitions

Learning new things is important for every developer. I've mentioned this before, and in the spirit of doing just that, I've started a somewhat ambitious project.

I want to do a large-scale simulation, using Elixir and Go, coupled with a physics simulation in C++. I've never done anything in Elixir before, and only played a little bit with Go, but I figure, how hard can it be?



Exsim I've dubbed this project exsim - it's a simulation done in Elixir. Someday I'll think about a more catchy name - for now I'm just focusing on the technical bits. Here's an overview of the system as I see it today:

exsim sits at the heart of it - this is the main server, implemented in Elixir. exsim-physics is the physics simulation. It is implemented in C++, using the Bullet physics library. exsim-physics-viewer is a simple viewer for the state of the physics simulation, written in Go. exsim-bot is a bot for testing exsim, written in Go. exsim-client is the game client, for inter…