Skip to content

Instantly share code, notes, and snippets.

@jonalmeida
Created April 24, 2017 06:36
Show Gist options
  • Save jonalmeida/51ecb59332c1c4a3e06672a98b42ee5a to your computer and use it in GitHub Desktop.
Save jonalmeida/51ecb59332c1c4a3e06672a98b42ee5a to your computer and use it in GitHub Desktop.
Scaling the frequency server

Scaling the frequency server

In my solution I used a simple round robin implementation to choose between the two servers. I separated the balancer module from the frequency to make the implementation cleaner. I've also moved allocated/0 and deallocated/1 to this module since they are the interfaces.

Scaling up

Suppose that the frequency server needs to serve more frequencies. It would be possible to do this just by having a longer list of frequencies available, but it is likely that this scaling is required not only because more frequencies are needed but also because there are more requests to allocate and de-allocate them.

Sharding the handling of frequencies

The idea here is that we can “shard” the handling of the frequencies, so that multiple processes are used to handle different subsets of them.


Implementation

Design a shared server which has two (old) frequency servers running: one process will be handling the frequencies 10–15 and the other process 20–25. In order for this to present the same API to its clients, you will also need to implement a front-end “router” process that will ensure that requests for allocation are routed to the appropriate server.

Obviously, a request to de-allocate a frequency will need to be routed to the appropriate back-end server, but what should be done in the case of allocation requests? Options include:

  1. Alternately sending routing requests to one back-end server then the other.
  2. Keeping a record in the front end of the number of frequencies remaining in each back-end server, and sending the allocation request to the server with the larger number; in the case of a tie another choice mechanism will be needed.
-module(balancer).
-export([start/0, loop/1, allocate/0, deallocate/1]).
-export([track/2]).
start() ->
register(balancer,
spawn(balancer, loop, [server_one])),
init().
init() ->
try register(server_one,
spawn(frequency, init, [[10,11,12,13,14,15]])) of
_ -> ok
catch error:badarg ->
io:format("server_one already registered")
end,
try register(server_two,
spawn(frequency, init, [[20,21,22,23,24,25]])) of
_ -> ok
catch error:badarg ->
io:format("server_two already registered")
end.
%% Functional interface
%% Moved these here from the frequency module
allocate() ->
server_request(allocate).
deallocate(Freq) ->
server_request({deallocate, Freq}).
server_request(Msg) ->
balancer ! {request, self(), Msg},
receive
{reply, Reply} -> Reply
end.
% Sends the request to given server.
route(R, S) ->
Server = whereis(S),
case Server of
undefined ->
{error, server_down};
_ ->
Server ! R
end.
loop(S) ->
receive
{request, _Pid, allocate}=R ->
route(R, S),
loop(next(S));
{request, _Pid, {deallocate, Freq}}=R ->
case correct_server(Freq) of
out_of_range ->
bad_freq_request;
Server ->
Server ! R
end,
loop(next(S))
end.
%% (Unused) keeps a track of pids with existing allocations
%% and avoids re-allocating again.
track({reply, {ok, Freq}=R}, A) ->
IsAllocated = lists:keymember(Freq, 2, A),
case IsAllocated of
true ->
{{error, already_allocated}, A};
false ->
{R, [Freq|A]}
end;
track({reply, {error, _E}=R}, A) ->
{R, A}.
next(server_one) ->
server_two;
next(server_two) ->
server_one.
correct_server(Freq) when Freq>=10, Freq=<15 ->
server_one;
correct_server(Freq) when Freq>=20, Freq=<25 ->
server_two;
correct_server(_Freq) ->
out_of_range.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment