Sebastian Cohnen

my tech-related blog

Native List Functions With CouchDB

Show and List Functions are used to transform single documents (show) and multiple documents e.g. from a view (list) into anything you want: JSON, HTML lists, SVG graphs…

You might know that you can write CouchDB Views in Erlang. But you can also write Show and List functions in Erlang as well. Today I played a bit around with a list function I’ve implemented in JavaScript first. I wanted to know, how much faster a native version would be and I wanted to write some Erlang too. I’ve not done any extensive benchmarking, just simple queries, but the difference is quite significant. Writing Erlang – if you aren’t used to it – might be an obstacle though.

So, what have I done? First you need to enable the Erlang view server. The “How to Enable Erlang Views” wiki page tells you what to do: Add a new section to your local.ini or via Futon.

1
2
[native_query_servers]
erlang = {couch_native_process, start_link, []}

Here is the list function, I’ve written in JavaScript: It’s purpose is to filter view results based upon uniqueness of a specific field ad_id in that case.

1
2
3
4
5
6
7
8
9
10
11
12
function(head, req) {
  var row, last_ad_id = "";

  start({ "headers": { "Content-Type": "text/plain" } });

  while(row = getRow()) {
    if (last_ad_id != row.doc.ad_id) {
      send(row.doc.ad_id + " with " + row.doc.text + "\n");
      last_ad_id = row.doc.ad_id
    }
  }
}

And here is the Erlang version with the same functionality. I’m very new to Erlang so there might be (much) room for improvement :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fun(Head, {Req}) ->
    Fun = fun({Row}, PrevAdID) ->
        {Doc} = couch_util:get_value(<<"doc">>, Row),
        Resp = case couch_util:get_value(<<"ad_id">>, Doc) of
          undefined ->
            undefined;
          AdID ->
            if
              AdID /= PrevAdID ->
                DocID = couch_util:get_value(<<"id">>, Row),
                Text = couch_util:get_value(<<"text">>, Doc),
                Send(list_to_binary(io_lib:format("Doc-ID ~p~n", [DocID]))),
                Send(list_to_binary(io_lib:format("Text ~p~n", [Text])));
              true ->
                null
            end,
            AdID
        end,
        {ok, Resp}
    end,
    FoldRows(Fun, nil),
    nil
end.

Although this might not be a very good example, I’ve learned some things. The Bindings available in the native Erlang views are defined in src/couchdb/couch_native_process.erl#299. Available are Log, Emit (for Views), Start, Send and GetRow which are the counterparts to log(), emit(key, value), start(), send() and getRow() in the JavaScript view server. Additionally FoldRows can be used as a helper to iterate over all rows in a list function.

Uh, I almost forgot got talk about speed: The native view was about 75-85% (processing a few hundred rows takes about 200ms with Erlang) faster than the JavaScript version. But speed is not everything and often fast enough is okay. Since I don’t speak Erlang very well, I’d think at least twice before I optimize my views using native Erlang.

Comments