Skip to content

Latest commit

 

History

History
247 lines (176 loc) · 8.62 KB

self_convergent.md

File metadata and controls

247 lines (176 loc) · 8.62 KB

Self Convergent Mode

See Introduction and Concepts for and introduction to NkBASE, the eventual consistent system and the definition of classes. Since this mode is a special case of the eventually consistent mode, see also Eventually Consistent Mode.

Introduction

Using this mode, you no longer send full objects to be stored at the database, but a set of modifications to be performed over the current object, if it exists. If not, an empty object is used as a base to perform modifications.

This special object is called a dmap. A dmap is an automatically convergent data structure, that can have any number of fields. Every field has a type and a value. Currently supported types and possible values are:

Type Values
flag enabled or disabled
register any()
counter integer()
set list() (with unique elements)
map nested dmap

You can perform a number of operations on a dmap to modify the value of any of its fields, add new fields or remove them. Depending on the type of field, you can describe a set of operations to be performed on it. If you perform an operation that is not allowed for the current type of the field, it will fail.

Flag

You can perform the following operations on fields of type flag:

  • enable: the field will switch to enabled.
  • disable: the field will switch to disabled. If you use a dcontext (see bellow), the flag will not switch to disabled until all enables have been disabled.
  • remove_flag: remove the field (same note about using the dcontext).

If a enable and disable is simultaneously ordered, the field will remain enabled.

Register

You can perform the following operations on fields of type register:

  • {assign, term()}: assigns a new value to the field. Last (using wallet time) assigned value wins.
  • {assign, term(), integer()}: assigns a new value to the field, but using your own concept of last, indicating the time to use.
  • remove_register: removes the field.

Counter

You can perform the following operations on fields of type counter:

  • increment: increments the counter by one.
  • {increment, integer()}: increments the counter.
  • decrement: decrements the counter by one.
  • {decrement, integer()}: decrements the counter.
  • remove_counter: removes the field.

If increments and decremenrs are simultaneously ordered, the resulting value is stored.

Set

You can perform the following operations on fields of type set:

  • {add, term()}: Adds this element to the set, if it is not yet present.
  • {remove, term()}: Removes this element from the set, if present.
  • {add_all, list()}: Adds all these elements to the set (atomic operation).
  • {remove_all, list()}: Removes all these elements from the set (atomic operation).
  • remove_set: Removes the full set.

If an element is added and removed simultaneously, it remains in the set. If you try to remove and element that does not exist, you get an error, unless you use a dcontext.

Map

You can perform the following operations on fields of type map:

  • list(): a new, nested map is created, and you can describe the operations to apply to it, using the previous types and behaviours. If the map already exists, the operations are applied to its fields.
  • remove_map: remove the map and all of its nested fields.

If an element in the map is updated and the map is removed at the same time, the map continues but only with the updated elements. If you remove and element that does not exist, you get an error, unless you use a dcontext.

Using dcontexts

Dcontexts are only used for disables and removals. You can obtain the current dcontext of a dmap calling nkbase_dmap:get/3,4, and the the current dcontext will be present in the special _dcontext field.

Normally, if you try to remove an element in a set or map that does no exist, you will get an error (it is supposed that other client must have removed it). You can indicate the dcontext in the update list, adding {'_dcontext', DContext}. This way, you are saying that you indeed know the object you are modifying, and that it is safe to remove the field.

Write operation

-spec nkbase_dmap:update(nkbase:domain(), nkbase:class(), nkbase:key(), update_spec()) ->
	ok | {error, term()}.

-spec nkbase_dmap:update(nkbase:domain(), nkbase:class(), nkbase:key(), 
			 nkbase_dmap:update_spec(), nkbase:put_meta()) ->
	ok | {error, term()}.

Use these functions to send a group of modifications to a dmap object, existing or not. The object is retrieved at the first vnode, and after appling the modifications, a new object is generated, that is indexed, stored and sent to the rest of vnodes.

Any conflict is automatically resolved with the rules described above. You can't specify the object's context (it is read from the base object), but you can include the dmap's dcontext (see above).

Indices can be added to the object (see search)

Read operation

-type type() :: flag | register | counter | set | map.

-type reply() :: 
	#{
		term() => {type(), term()}	
	}
	|
	#{
		fields => #{ term() => {type(), term()}},
		indices => #{ nkbase:index_name() => [term()]}
	}.

-spec get(nkbase:domain(), nkbase:class(), nkbase:key()) ->
	{ok, reply()} | {error, term()}.

-spec get(nkbase:domain(), nkbase:class(), nkbase:key(), nkbase:get_meta()) ->
	{ok, nkbase:reply()} | {error, term()}.

These functions are very similar to nkbase:get/3,4 (see Operation at Eventually Consistent Mode, but assumes requested object is a dmap, resolving conflicts on read automatically.

By default it returns the full description of the dmap, along with the dcontext. You can however indicate specific fields or indices to be returned (see get specification).

Delete operation

-spec del(nkbase:domain(), nkbase:class(), nkbase:key()) ->
	ok | {error, term()}.

-spec del(nkbase:domain(), nkbase:class(), nkbase:key(), nkbase:put_meta()) ->
	ok | {error, term()}.

Use these functions to delete a dmap. It will find an existing dmap, remove all of its fields, and add a ttl to schedule its removal (see Delete Operation at Eventually Consistent Mode).

Listing Domains, Classes and Keys

You can use the same functions for Eventually Consistent Mode.

Examples

We start updating a new object:

> nkbase_dmap:get(domain, class, dkey).
{error, not_found}

> nkbase_dmap:update(domain, class, dkey, 
	[
		{field1, enable},						% it is a flag
		{field2, increment},					% it is a counter
		{field3, {assign, "hi"}},				% it is a register
		{field4, {add_all, [1, a, <<"b">>]}},	% it is a set
		{field5, 								% it is a nested map
			[
				{field5a, {decrement, 5}}
			]
		}			
	]).
ok

> {ok, Dmap1} = nkbase_dmap:get(domain, class, dkey).
{ok, 
	#{
		'_dcontext' => ...,
  		field1 => {flag, enable},
      	field2 => {counter, 1},
      	field3 => {register, "hi"},
      	field4 => {set, [1, a, <<"b">>]},
      	field5 => {map, #{field4a => {counter, -5}}}}}
    }
}

Now we can send more updates, and get specific fields:

> nkbase_dmap:update(domain, class, dkey, 
	[
		{field2, increment},
		{field2, increment},
		{field3, remove_register},
		{field4, {remove, a}},
		{field5, [{field5a, decrement}]
		}
	]).
ok

> {ok, Dmap2} = nkbase_dmap:get(domain, class, dkey).
{ok,
	#{
		'_dcontext' => ...,
      	field1 => {flag, enabled},
      	field2 => {counter, 3},
        field4 => {set, [1, <<"b">>]},
        field5 => {map, #{field5a => {counter, -6}}}}}
    }
}

> nkbase_dmap:get(domain, class, key, #{get_fields=>[{field5, field5a}]}).
{ok, #{fields => #{{field5,field5a} => -6}}}

If we try to remove a non-existing element it will fail unless we use the dcontext:

> nkbase_dmap:update(domain, class, dkey, 
	[
		{field4, {remove, 2}}
	]).
{error, {field_not_present,2}}

> nkbase_dmap:update(domain, class, dkey, 
	[
		{field4, {remove, 2}},
		{'_dcontext', maps:get('_dcontext', Dmap2)}
	]).
ok

Finally, we can delete the object, lowering the default ttl to 5 seconds:

> nkbase_dmap:del(domain, class, dkey, #{ttl=>5}).
ok

> nkbase_dmap:get(domain, class, dkey).
{ok, #{'_dcontext' => ...}} 

%% After 5 seconds:
> nkbase_dmap:get(domain, class, dkey).
{error, not_found}