riak_test/tests/sibling_explosion.erl
Hans Svensson ace21438f0 Fix OTP20 incompatibilities
nowarn_export_all, random -> rand, gen_fsm -> gen_fsm_compat, now ->
os:timestamp, rand_bytes -> strong_rand_bytes
2019-03-08 14:09:35 +01:00

91 lines
3.4 KiB
Erlang

-module(sibling_explosion).
-include_lib("eunit/include/eunit.hrl").
-export([confirm/0]).
-compile([export_all, nowarn_export_all]).
-define(B, <<"b">>).
-define(K, <<"k">>).
%% This tests provokes a sibling explosion. It does so with a single
%% node and a single client. All it does to acheive the explosion is
%% interleave fetch / resolve / writes. It works like this:
%% - The aim of the clients is to write a single set of intergers from 0-99
%% - one client gets odds, the other evens
%% - The test interleaves writes from the clients
%% - Each client, in turn:
%% -- fetchs the key from riak
%% -- resolves the sibling values (by performing a set union on the values in all siblings)
%% -- adds a new value to the set
%% -- Puts the new value back to riak
%% This results in 99 siblings, each a subset of the following sibling [0] | [0, 1] | [0, 1, 2], [0, 1, 2, 3] etc
confirm() ->
Conf = [{riak_core, [{default_bucket_props, [{allow_mult, true},
{dvv_enabled, true}]}]}],
[Node1] = rt:deploy_nodes(1, Conf),
N = 100,
lager:info("Put new object in ~p via PBC.", [Node1]),
PB = rt:pbc(Node1),
A0 = riakc_obj:new(<<"b">>, <<"k">>, sets:from_list([0])),
B0 = riakc_obj:new(<<"b">>, <<"k">>, sets:from_list([1])),
_ = explode(PB, {A0, B0}, N),
{ok, SibCheck1} = riakc_pb_socket:get(PB, <<"b">>, <<"k">>),
%% there should now be only two siblings
?assertEqual(2, riakc_obj:value_count(SibCheck1)),
%% siblings should merge to include all writes
assert_sibling_values(riakc_obj:get_values(SibCheck1), N),
pass.
%% Check that everywrite was wrote.
assert_sibling_values(Values, N) ->
V = resolve(Values, sets:new()),
L = lists:sort(sets:to_list(V)),
Expected = lists:seq(0, N-2),
?assertEqual(Expected, L).
%% Pick one of the two objects, and perform a fetch, resolve, update
%% cycle with it. The point is that the two object's writes are
%% interleaved. First A is updated, then B. Each object already has a
%% "latest" vclock returned from it's last put. This simulates the
%% case where a client fetches a vclock before PUT, but another write
%% lands at the vnode after the vclock is returned and before the next
%% PUT. Each PUT sees all but one write that Riak as seen, meaning
%% there is a perpetual race / sibling. Without DVV that means ALL
%% writes are added to the sibling set. With DVV, we correctly capture
%% the resolution of seen writes.
explode(_PB, {A, B}, 1) ->
{A, B};
explode(PB, {A0, B}, Cnt) when Cnt rem 2 == 0 ->
A = resolve_mutate_store(PB, Cnt, A0),
explode(PB, {A, B}, Cnt-1);
explode(PB, {A, B0}, Cnt) when Cnt rem 2 /= 0 ->
B = resolve_mutate_store(PB, Cnt, B0),
explode(PB, {A, B}, Cnt-1).
%% resolve the fetch, and put a new value
resolve_mutate_store(PB, N, Obj0) ->
Obj = resolve_update(Obj0, N),
{ok, Obj2} = riakc_pb_socket:put(PB, Obj, [return_body]),
Obj2.
%% simply union the values, and add a new one for this operation
resolve_update(Obj, N) ->
case riakc_obj:get_values(Obj) of
[] -> Obj;
Values ->
Value0 = resolve(Values, sets:new()),
Value = sets:add_element(N, Value0),
lager:info("Storing ~p", [N]),
riakc_obj:update_metadata(riakc_obj:update_value(Obj, Value), dict:new())
end.
%% Set union on each value
resolve([], Acc) ->
Acc;
resolve([V0 | Rest], Acc) ->
V = binary_to_term(V0),
resolve(Rest, sets:union(V, Acc)).