inaka

Latest blog entries

/
Erlang and Elixir Factory Lite B.A.

A brief introduction about what was the Erlang Factory Conference in Buenos Aires for some Inaka team members

Jul 07 2017 : Euen Lopez

/
The Art of Writing a Blogpost

The Art of Writing a Blogpost

Apr 11 2017 : Matias Vera

/
SpellingCI: No more spelling mistakes in your markdown flies!

Feb 14 2017 : Felipe Ripoll

/
Fast reverse geocoding with offline-geocoder

Do you need a blazing fast reverse geocoder? Enter offline-geocoder!

Jan 18 2017 : Roberto Romero

/
Using Jayme to connect to the new MongooseIM REST services

MongooseIM has RESTful services!! Here I show how you can use them in an iOS application.

Dec 13 2016 : Sergio Abraham

/
20 Questions, or Maybe a Few More

20 Questions, or Maybe a Few More

Nov 16 2016 : Stephanie Goldner

/
The Power of Meeting People

Because conferences and meetups are not just about the technical stuff.

Nov 01 2016 : Pablo Villar

/
Finding the right partner for your app build

Sharing some light on how it is to partner with us.

Oct 27 2016 : Inaka

/
Just Play my Sound

How to easily play a sound in Android

Oct 25 2016 : Giaquinta Emiliano

/
Opening our Guidelines to the World

We're publishing our work guidelines for the world to see.

Oct 13 2016 : Brujo Benavides

/
Using NIFs: the easy way

Using niffy to simplify working with NIFs on Erlang

Oct 05 2016 : Hernan Rivas Acosta

/
Function Naming In Swift 3

How to write clear function signatures, yet expressive, while following Swift 3 API design guidelines.

Sep 16 2016 : Pablo Villar

/
Jenkins automated tests for Rails

How to automatically trigger rails tests with a Jenkins job

Sep 14 2016 : Demian Sciessere

/
Erlang REST Server Stack

A description of our usual stack for building REST servers in Erlang

Sep 06 2016 : Brujo Benavides

/
Replacing JSON when talking to Erlang

Using Erlang's External Term Format

Aug 17 2016 : Hernan Rivas Acosta

/
Gadget + Lewis = Android Lint CI

Integrating our Android linter with Github's pull requests

Aug 04 2016 : Fernando Ramirez and Euen Lopez

/
Passwordless login with phoenix

Introducing how to implement passwordless login with phoenix framework

Jul 27 2016 : Thiago Borges

/
Beam Olympics

Our newest game to test your Beam Skills

Jul 14 2016 : Brujo Benavides

/
Otec

Three Open Source Projects, one App

Jun 28 2016 : Andrés Gerace

/
CredoCI

Running credo checks for elixir code on your github pull requests

Jun 16 2016 : Alejandro Mataloni

/
See all Inaka's blog posts >>

/
Everyday Erlang: Quick and effective caching using ETS

Marcelo Gornstein wrote this on March 05, 2013 under design, dev, engineering, erlang .

Introduction

Hello again! To continue the "Everyday Erlang" series, I'd like to show you how to implement a quick and simple (yet effective) cache using ETS, which is very good at wrapping your expensive function calls with it.

Although similar to what pcache offers, it's a bit more flexible, does not use processes, and makes your overall cache implementation a bit cleaner since you won't have to add different caches in your supervisor tree. It also allows concurrency, because it doesn't use a gen_server.

The complete source code is in the form of a rebar library application, at GitHub. You can easily include it for fun and/or profit in your own project.

How It Works

So the idea is to have a module that creates a small abstraction layer over ETS to use as a generic cache. Let's call it simple_cache.

Initializing the Cache

We're going to have an ETS table per cache to store all the cached values. To create a cache (the ETS table), we would call simple_cache:init/1, like:

simple_cache:init(mycache_name).

This function would have quite a simple code:

-module(simple_cache).

    ...

    -define(ETS_TID, atom_to_list(?MODULE)).
    -define(NAME(N), list_to_atom(?ETS_TID ++ "_" ++ atom_to_list(N))).

    ...

    %% @doc Initializes a cache.
    -spec init(string()) -> ok.
    init(CacheName) ->
      RealName = ?NAME(CacheName),
      RealName = ets:new(RealName, [
        named_table, {read_concurrency, true}, public, {write_concurrency, true}
      ]),
      ok.

Using It

Let's suppose we have an expensive db query (let's even say it returns the results as json documents) in a function like this:

expensive_query(DbRef, Arg1, Arg2) ->
        Elements = db:query(DbRef, "an incredible expensive query", [Arg1, Arg2]),
        [to_json(E) || E <- Elements].

To automatically add a cache here, we would rewrite the above function to call the caching function (simple_cache:get/4) that will first try to get the value from the ETS entry. Then, if not found, it will execute the original function and cache the result:

expensive_query(DbRef, Arg1, Arg2) ->
      simple_cache:get(mycache_name, infinity, {expensive_query_key, Arg1, Arg2}, fun() ->

        % This is actually the expensive operation, inside a fun.
        Elements = db:query(DbRef, "an incredible expensive query", [Arg1, Arg2]),
        [ to_json(E) || E <- Elements]

      end).

The first argument of simple_cache:get/4 is the name of the cache (already initialized with simple_cache:init/1), the second argument is the atom 'infinity' or a positive integer indicating the amount of milliseconds that this key can live before expiring. The third argument is the key name for this value, and the fourth argument is the function to execute to generate the value to be cached.

So the first time you call this function, the actual fun() is executed, and subsequent calls for that particular key will return the cached value (unless it's expired, in which case it needs to be recalculated and recached).

What's neat here is that we are actually forming a key using the arguments passed. It ends up as the tuple {expensive_query_key, Arg1, Arg2}, so you can cache different results based on the arguments passed to the expensive/3 function.

The Code (ETA: Now outdated, please see below.)

To accomplish this, we actually need just a few lines of code:

-module(simple_cache).

    ...

    %% @doc Tries to lookup Key in the cache, and execute the given FunResult
    %% on a miss.
    -spec get(string(), infinity|pos_integer(), term(), function()) -> term().
    get(CacheName, LifeTime, Key, FunResult) ->
      RealName = ?NAME(CacheName),
      case ets:lookup(RealName, Key) of
        [] -> create_value(RealName, LifeTime, Key, FunResult); % Not found, create it.
        [{Key, R, _CreatedTime, infinity}] -> R; % Found, wont expire, return the value.
        [{Key, R, CreatedTime, LifeTime}] ->
          TimeElapsed = now_usecs() - CreatedTime,
          if % expired? create a new value
            TimeElapsed > (LifeTime * 1000) ->
              create_value(RealName, LifeTime, Key, FunResult);
            true -> R % Not expired, return it.
          end
      end.

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %% Private API.
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %% @doc Creates a cache entry.
    -spec create_value(string(), pos_integer(), term(), function()) -> term().
    create_value(RealName, LifeTime, Key, FunResult) ->
      R = FunResult(),
      ets:insert(RealName, {Key, R, now_usecs(), LifeTime}),
      R.

    ...

Update

In the 0.2 version the simple_cache_expirer process was added. This is just a regular gen_server, usually started by your top supervisor. Its task is to flush the expired keys from the cache (and save precious ram, doh!).

So the above code is now actually something like this:

%% @doc Tries to lookup Key in the cache, and execute the given FunResult
%% on a miss.
-spec get(string(), infinity|pos_integer(), term(), function()) -> term().
get(CacheName, LifeTime, Key, FunResult) ->
  RealName = ?NAME(CacheName),
  case ets:lookup(RealName, Key) of
    [] ->
      % Not found, create it.
      V = FunResult(),
      ets:insert(RealName, {Key, V}),
      erlang:send_after(
        LifeTime, simple_cache_expirer, {expire, CacheName, Key}
      ),
      V;
    [{Key, R}] -> R % Found, return the value.
  end.

See how we now don't care about the expiration time. Instead, erlang:send_after/3 is used to schedule a message for the simple_cache_expirer process, that looks something like this:

-module(simple_cache_expirer).
-behavior(gen_server).
...
-spec handle_info(any(), state()) -> {noreply, state()}.
handle_info({expire, CacheName, Key}, State) ->
  simple_cache:flush(CacheName, Key),
  {noreply, State};
...

Flushing the Cache

To flush all cache entries, we can call simple_cache:flush/1:

simple_cache:flush_all(mycache_name).

To flush a single entry, just call simple_cache:flush/2:

simple_cache:flush_all(mycache_name, {expensive_query_key, Arg1, Arg2}).

Conclusion

Although this is more like a "poor man's cache," I find it a very effective solution to avoid computing expensive stuff, or as a buffer to avoid hitting hard rest services and/or databases. Also, it's something we get for free because of ETS, and a neat trick to have around. I hope it's as useful to you as it has been for me!

Author

Marcelo Gornstein marcelo@inakanetworks.com

Homepage: http://marcelog.github.com

Github: marcelog

Inaka's Github: inaka