{"id":13801213,"url":"https://github.com/mochi/statebox","last_synced_at":"2025-04-24T10:32:29.203Z","repository":{"id":137582672,"uuid":"1651949","full_name":"mochi/statebox","owner":"mochi","description":"Erlang state \"monad\" with merge/conflict-resolution capabilities. Useful for Riak.","archived":false,"fork":false,"pushed_at":"2015-02-03T18:48:37.000Z","size":358,"stargazers_count":251,"open_issues_count":2,"forks_count":20,"subscribers_count":26,"default_branch":"master","last_synced_at":"2024-08-04T00:06:01.413Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mochi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2011-04-22T23:46:55.000Z","updated_at":"2024-07-20T17:27:55.000Z","dependencies_parsed_at":"2023-03-12T19:11:11.853Z","dependency_job_id":null,"html_url":"https://github.com/mochi/statebox","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochi%2Fstatebox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochi%2Fstatebox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochi%2Fstatebox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochi%2Fstatebox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mochi","download_url":"https://codeload.github.com/mochi/statebox/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223948387,"owners_count":17230132,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T00:01:20.593Z","updated_at":"2024-11-10T12:15:10.236Z","avatar_url":"https://github.com/mochi.png","language":"Erlang","readme":"statebox - state \"monad\" for automated conflict resolution\n==========================================================\n\n\u003cbob@redivi.com\u003e\n\nOverview:\n---------\n\nstatebox is a data structure you can use with an eventually consistent\nsystem such as riak to resolve conflicts between siblings in a deterministic\nmanner.\n\nStatus:\n-------\n\nUsed in production at Mochi Media for multiple backend services.\n\nTheory:\n-------\n\nA statebox wraps a current value and an event queue. The event queue is\nan ordered list of `{timestamp(), op()}`. When two or more statebox\nare merged with `statebox:merge/1`, the event queues are merged with\n`lists:umerge/1` and the operations are performed again over the current\nvalue of the newest statebox, producing a new statebox with conflicts\nresolved in a deterministic manner.\n\nAn `op()` is a `{fun(), [term()]}`, with all but the last argument specified\nin the term list. For example `{ordsets:add_element/2, [a]}`. To evaluate\nthis op, `ordsets:add_element(a, value(Statebox))` will be called. It is also\npossible to specify an `op()` as a `{module(), atom(), [term()]}` tuple, or\nas a list of `op()` when performing several operations at the same timestamp.\n\nThere are several important limitations on the kinds of `op()` that are safe\nto use (`{F, [Arg]}` is the example `op()` used below):\n\n* An `op()` must be repeatable: `F(Arg, F(Arg, Value)) =:= F(Arg, Value)`\n* If the `{fun(), [term()]}` form is used, the `fun()` should be a reference\n  to an exported function.\n* `F(Arg, Value)` should return the same type as `Value`.\n\nSome examples of safe to use `op()` that ship with Erlang:\n\n* `{fun ordsets:add_element/2, [SomeElem]}` and\n  `{fun ordsets:del_element/2, [SomeElem]}`\n* `{fun ordsets:union/2, [SomeOrdset]}` and\n  `{fun ordsets:subtract/2, [SomeOrdset]}`\n* `{fun orddict:store/3, [Key, Value]}`\n\nSome examples of functions you can not use as `op()`:\n\n* `{fun orddict:update_counter, [Key, Inc]}` - it is not repeatable.\n  `F(a, 1, [{a, 0}]) =/= F(a, 1, F(a, 1, [{a, 0}]))`\n\nOptimizations:\n--------------\n\nThere are two functions that modify a statebox that can be used to\nreduce its size. One or both of these should be done every time before\nserializing the statebox.\n\n* `truncate(N, Statebox)` return Statebox with no more than `N` events in its\n  queue.\n* `expire(Age, Statebox)` return Statebox with no events older than\n  `last_modified(Statebox) - Age`. If using `new/1` and `modify/2`, then this\n  is in milliseconds.\n\nUsage:\n------\n\nSimple `ordsets()` example:\n\n    New = statebox:new(fun () -\u003e [] end),\n    ChildA = statebox:modify({fun ordsets:add_element/2, [a]}, New),\n    ChildB = statebox:modify({fun ordsets:add_element/2, [b]}, New),\n    Resolved = statebox:merge([ChildA, ChildB]),\n    statebox:value(Resolved) =:= [a, b].\n\nWith manual control over timestamps:\n\n    New = statebox:new(0, fun () -\u003e [] end),\n    ChildA = statebox:modify(1, {fun ordsets:add_element/2, [a]}, New),\n    ChildB = statebox:modify(2, {fun ordsets:add_element/2, [b]}, New),\n    Resolved = statebox:merge([ChildA, ChildB]),\n    statebox:value(Resolved) =:= [a, b].\n\nUsing the `statebox_orddict` convenience wrapper:\n\n    New = statebox_orddict:from_values([]),\n    ChildA = statebox:modify([statebox_orddict:f_store(a, 1),\n                              statebox_orddict:f_union(c, [a, aa])],\n                             New),\n    ChildB = statebox:modify([statebox_orddict:f_store(b, 1),\n                              statebox_orddict:f_union(c, [b, bb])],\n                             New),\n    Resovled = statebox_orddict:from_values([ChildA, ChildB]),\n    statebox:value(Resolved) =:= [{a, 1}, {b, 1}, {c, [a, aa, b, bb]}].\n\nResources\n---------\n\nOn Mochi Labs\n=============\n\n[statebox, an eventually consistent data model for Erlang (and Riak)][labs0]\non the Mochi Labs blog describes the rationale for statebox and shows how it\nworks.\n\n[labs0]: http://labs.mochimedia.com/archive/2011/05/08/statebox/\n\nConvergent / Commutative Replicated Data Types\n==============================================\n\nThe technique used to implement this is similar to what is described in\nthis paper:\n[A comprehensive study of Convergent and Commutative Replicated Data Types][CRDT].\nstatebox was developed without knowledge of the paper, so the terminology and\nimplementation details differ.\n\nI think the technique used by statebox would be best described as a\nstate-based object, although the merge algorithm and event queue\nis similar to how op-based objects are described.\n\n[CRDT]: http://hal.archives-ouvertes.fr/inria-00555588/","funding_links":[],"categories":["Algorithms and Datastructures","Erlang"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmochi%2Fstatebox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmochi%2Fstatebox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmochi%2Fstatebox/lists"}