Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Pablo committed Jul 19, 2021
0 parents commit 78a9349
Show file tree
Hide file tree
Showing 14 changed files with 564 additions and 0 deletions.
86 changes: 86 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: nmaglev
on:
push:
branches:
- '*'
pull_request:
branches:
- master

jobs:
build:
strategy:
matrix:
include:
- platform: ubuntu-20.04
lsb_release: focal
otp-version: 23.1-1
- platform: ubuntu-20.04
lsb_release: focal
otp-version: 22.3.4.8-1
- platform: ubuntu-20.04
lsb_release: focal
otp-version: 24.0.2-1
runs-on: ${{ matrix.platform }}
steps:

# Setup
- name: Checkout
uses: actions/checkout@v2

- name: Setup git
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
run: |
mkdir -p /home/runner/.ssh
ssh-keyscan github.com >> /home/runner/.ssh/known_hosts
echo "${{ secrets.SSH_PRIVATE_KEY }}" > /home/runner/.ssh/id_rsa
chmod 600 /home/runner/.ssh/id_rsa
ssh-agent -a $SSH_AUTH_SOCK > /dev/null
ssh-add /home/runner/.ssh/id_rsa
# Install Erlang
- name: Install Erlang/OTP
run: |
DEB_NAME="esl-erlang_${{ matrix.otp-version }}~ubuntu~${{ matrix.lsb_release }}_amd64.deb"
curl -f https://packages.erlang-solutions.com/erlang/debian/pool/$DEB_NAME -o $DEB_NAME
sudo dpkg --install $DEB_NAME
# Inspect rebar3 version
- name: Rebar version
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
run: rebar3 --version

# PRE Checks
- name: Checks
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
run: rebar3 fmt --check

# Compile
- name: Compile
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
run: |
git config --global user.email "[email protected]"
git config --global user.name "GitHub actions"
make
# Tests
- name: Run tests
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
run: make test
- name: Store test logs
uses: actions/upload-artifact@v1
if: always()
with:
name: ct-logs
path: _build/test/logs

# Cover reports
# - name: Create Cover Reports
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# run: rebar3 do cover,coveralls send
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
_build/
_checkouts/
.DS_Store

36 changes: 36 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
### INCLUDES
include app.mk

###-----------------------------------------------------------------------------
### APPLICATION LAYOUT
###-----------------------------------------------------------------------------
APPSRC = $(patsubst src/%.app.src,%.app.src,$(wildcard src/*.app.src))
APP = $(APPSRC:.app.src=.app)
APPNAME = $(basename $(APP))
ERLS = $(patsubst src/%.erl,%.erl,$(wildcard src/*.erl))

.PHONY: all clean doc test
.SUFFIXES: .erl .hrl .app.src .app

###-----------------------------------------------------------------------------
### TARGETS
###-----------------------------------------------------------------------------
all: compile

compile:
@$(REBAR) compile

clean:
@$(REBAR) clean

fmt:
$(REBAR) fmt

dialyze:
@$(REBAR) dialyzer

test: fmt
@$(REBAR) ct --spec test/conf/test.spec --cover --readable true

test-verbose: fmt
@$(REBAR) ct --spec test/conf/test.spec --cover --readable true --verbose
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# nmaglev

Erlang implementation of the consistent hashing maglev algorithm

## Introduction

MagLev is a [consistent hashing](https://en.wikipedia.org/wiki/Consistent_hashing) algorithm developed at Google and
[published](https://static.googleusercontent.com/media/research.google.com/es//pubs/archive/44824.pdf) in 2016.

It aims to be lightning fast and always accurate due to the use of a deterministic algorithm at the spend
of needing more computational power when there is a change in the list of possible outputs as it needs
to recalculate the internal hashing table.

## How to use it

1. Create your maglev table with the list of possible outputs: ```MaglevTable = nmaglev:create([a,b,c,d]).```
2. Use the table: ```Output = nmaglev:get(<<"some bin">>, MaglevTable).```


## About lookup table sizes

By default the lookup table size is 65537 as it should be enough for a normal scenario.
You can choose your own size using the nmaglev:create/2 function but please be aware that the size has to be
a prime number.

## Original paper

[https://static.googleusercontent.com/media/research.google.com/es//pubs/archive/44824.pdf](https://static.googleusercontent.com/media/research.google.com/es//pubs/archive/44824.pdf)
25 changes: 25 additions & 0 deletions app.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
VSN = 1.1.0

### Special characters
comma := ,
empty :=
space := $(empty) $(empty)

### Erlang compiler
ERL = erl
ERLC = erlc

### Flags
DFLAGS = -I .. --src --verbose -c
STUB_EFLAGS = -v -pz ../../vo/ebin/ -I ../.. -I .. -I ./stubs/ -W1 -o stubs

### Default apps
CD = cd
CP = cp -vf
ECHO = echo
ERLDOC = ndoc
MKDIR = mkdir
MV = mv -vf
RM = rm -vf
SED = sed
REBAR = rebar3
17 changes: 17 additions & 0 deletions rebar.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{erl_opts, [
debug_info,
bin_opt_info,
{i, "include"}
]}.

{plugins, [
{erlfmt, {git, "[email protected]:WhatsApp/erlfmt.git", {tag, "v1.0.0"}}}
]}.

{erlfmt, [write]}.

{provider_hooks, [
{pre, [{compile, fmt}]}
]}.

{cover_enabled, true}.
1 change: 1 addition & 0 deletions rebar.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[].
8 changes: 8 additions & 0 deletions src/nmaglev.app.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
%%% -*- mode:erlang -*-
{application, nmaglev, [
{description, "Erlang implementation of the consistent hashing maglev algorithm"},
{vsn, "1.0.0"},
{registered, []},
{applications, [kernel, stdlib]},
{env, []}
]}.
97 changes: 97 additions & 0 deletions src/nmaglev.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
%%% Copyright (c) 2013 [Nomasystems, S.L. http://www.nomasystems.com]
%%%
%%% This file contains Original Code and/or Modifications of Original Code as
%%% defined in and that are subject to the Nomasystems Public License version
%%% 1.0 (the 'License'). You may not use this file except in compliance with
%%% the License. BY USING THIS FILE YOU AGREE TO ALL TERMS AND CONDITIONS OF
%%% THE LICENSE. A copy of the License is provided with the Original Code and
%%% Modifications, and is also available at www.nomasystems.com/license.txt.
%%%
%%% The Original Code and all software distributed under the License are
%%% distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
%%% EXPRESS OR IMPLIED, AND NOMASYSTEMS AND ALL CONTRIBUTORS HEREBY DISCLAIM
%%% ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF
%%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR
%%% NON-INFRINGEMENT. Please see the License for the specific language
%%% governing rights and limitations under the License.
%%%
-module(nmaglev).

%%% INCLUDE FILES

%%% EXTERNAL EXPORTS
-export([create/1, create/2, get/2]).

%%% MACROS
-define(DEFAULT_LOOKUP_SIZE, 65537).

%%% RECORDS

%%%-----------------------------------------------------------------------------
%%% EXTERNAL EXPORTS
%%%-----------------------------------------------------------------------------
create(Nodes) ->
LookupSize = ?DEFAULT_LOOKUP_SIZE,
create(Nodes, LookupSize).

create(Nodes, LookupSize) ->
NodesLen = length(Nodes),
PermutationsTable = permutations(Nodes, LookupSize),
lookup_map(PermutationsTable, NodesLen, LookupSize, {0, #{}, NodesLen}).

get(Key, Lookup) ->
Offset = erlang:phash2(Key, maps:size(Lookup)),
maps:get(Offset, Lookup).

%%%-----------------------------------------------------------------------------
%%% INTERNAL FUNCTIONS
%%%-----------------------------------------------------------------------------
lookup_map(_PermutationsTable, _NodesLen, LookupSize, {Filled, FilledMap, _LastNodePos}) when
Filled >= LookupSize
->
FilledMap;
lookup_map(PermutationsTable, NodesLen, LookupSize, {Filled, FilledMap, LastNodePos}) ->
{Permutation, ChosenNode, ChosenNodePos, NewPermutationsTable} = choose_node(
PermutationsTable,
NodesLen,
FilledMap,
LastNodePos
),
lookup_map(
NewPermutationsTable,
NodesLen,
LookupSize,
{Filled + 1, FilledMap#{Permutation => ChosenNode}, ChosenNodePos}
).

choose_node(PermutationsTable, NodesLen, FilledMap, LastNodePos) ->
NodePos = mod(LastNodePos, NodesLen) + 1,
{Node, NodePermutations} = lists:nth(NodePos, PermutationsTable),
[Permutation | RestNodePermutations] = NodePermutations,
NewPermutationsTable = lists:keyreplace(
Node,
1,
PermutationsTable,
{Node, RestNodePermutations}
),
case maps:is_key(Permutation, FilledMap) of
false ->
{Permutation, Node, NodePos, NewPermutationsTable};
true ->
choose_node(NewPermutationsTable, NodesLen, FilledMap, NodePos)
end.

permutations(Nodes, LookupSize) ->
[{Node, node_permutations(Node, LookupSize)} || Node <- Nodes].

node_permutations(Node, LookupSize) ->
Offset = mod(erlang:phash(Node, LookupSize), LookupSize),
Skip = mod(erlang:phash2(Node, LookupSize), LookupSize - 1) + 1,
[mod(Offset + (Pos * Skip), LookupSize) || Pos <- lists:seq(0, LookupSize - 1)].

mod(A, B) when A > 0 ->
A rem B;
mod(A, B) when A < 0 ->
mod(A + B, B);
mod(0, _) ->
0.
6 changes: 6 additions & 0 deletions test/conf/test.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
%%% -*- mode:erlang -*-
{apps, [nmaglev]}.
{paths, []}.
{env, [{nmaglev, []}]}.
%{tpl_cases, [{api, [ets]}]}.
%{tp_cases, []}.
4 changes: 4 additions & 0 deletions test/conf/test.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
%%% -*- mode:erlang -*-
{config, "test.conf"}.
{alias, test, ".."}.
{suites, test, all}.
Loading

0 comments on commit 78a9349

Please sign in to comment.