diff --git a/README.md b/README.md index 1e531ac..551565d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ [![LICENSE](https://img.shields.io/hexpm/l/dynamic_rtree)](https://rawcdn.githack.com/windfish-studio/rtree/1479e8660336fb0a63fc6a39185c10e1ab940d7b/LICENSE) [![VERSION](https://img.shields.io/hexpm/v/dynamic_rtree)](https://hexdocs.pm/dynamic_rtree/api-reference.html) -# :ddrt +# :ddrt (README) + A __D__ynamic, __D__istributed [__R__-__T__ree](https://en.wikipedia.org/wiki/R-tree) (DDRT) library written in Elixir. The 'dynamic' part of the title refers to the fact that this implementation is optimized for a high volume of update operations. Put another way, this is an R-tree best suited for use with spatial data _in constant movement_. The 'distributed' part refers to the fact that this library is designed to maintain a spatial index (rtree) across a cluster of distributed elixir nodes. The library uses [@derekkraan](https://github.com/derekkraan)'s [MerkleMap](https://github.com/derekkraan/merkle_map) and [CRDT](https://github.com/derekkraan/delta_crdt_ex) implementations to ensure reliable, "eventually consistent" distributed behavior. @@ -231,10 +232,10 @@ warmup: 2 s time: 5 s memory time: 0 ns parallel: 1 -inputs: delete all leafs of tree [1000] +inputs: delete all leaves of tree [1000] Estimated total run time: 28 s -##### With input delete all leafs of tree [1000] ##### +##### With input delete all leaves of tree [1000] ##### Name ips average deviation median 99th % map bulk 175.20 5.71 ms ±9.18% 5.60 ms 9.47 ms merklemap bulk 80.27 12.46 ms ±21.27% 11.74 ms 25.37 ms @@ -255,10 +256,10 @@ warmup: 2 s time: 10 s memory time: 0 ns parallel: 1 -inputs: all leafs of tree [1000], all leafs of tree [100000] +inputs: all leaves of tree [1000], all leaves of tree [100000] Estimated total run time: 48 s -##### With input all leafs of tree [1000] ##### +##### With input all leaves of tree [1000] ##### Name ips average deviation median 99th % map 133.88 7.47 ms ±22.82% 6.92 ms 14.83 ms merklemap 65.74 15.21 ms ±21.93% 14.18 ms 26.42 ms @@ -267,7 +268,7 @@ Comparison: map 133.88 merklemap 65.74 - 2.04x slower +7.74 ms -##### With input all leafs of tree [100000] ##### +##### With input all leaves of tree [100000] ##### Name ips average deviation median 99th % map 0.68 1.46 s ±15.84% 1.47 s 1.82 s merklemap 0.33 3.01 s ±8.23% 3.09 s 3.21 s @@ -332,10 +333,10 @@ warmup: 2 s time: 5 s memory time: 0 ns parallel: 1 -inputs: 1000 leafs +inputs: 1000 leaves Estimated total run time: 28 s -##### With input 1000 leafs ##### +##### With input 1000 leaves ##### Name ips average deviation median 99th % map bulk 305.53 3.27 ms ±39.39% 2.79 ms 7.96 ms merklemap bulk 190.61 5.25 ms ±62.06% 4.37 ms 17.65 ms diff --git a/bench/delete_benchmark.ex b/bench/delete_benchmark.ex index 736e39e..0c2f5aa 100644 --- a/bench/delete_benchmark.ex +++ b/bench/delete_benchmark.ex @@ -51,7 +51,7 @@ Benchee.run(%{ end}, }, inputs: %{ - cyan() <>"delete all leafs of tree ["<> color(195) <>"1000" <> cyan() <> "]" <> reset() => generate.(1000,1), - #cyan() <>"all leafs of tree ["<> color(195) <>"100000" <> cyan() <> "]" <> reset() => generate.(100000,1) + cyan() <>"delete all leaves of tree ["<> color(195) <>"1000" <> cyan() <> "]" <> reset() => generate.(1000,1), + #cyan() <>"all leaves of tree ["<> color(195) <>"100000" <> cyan() <> "]" <> reset() => generate.(100000,1) } ) diff --git a/bench/insert_benchmark.ex b/bench/insert_benchmark.ex index dbb4301..da3a16f 100644 --- a/bench/insert_benchmark.ex +++ b/bench/insert_benchmark.ex @@ -10,9 +10,9 @@ generate = fn n,s -> BoundingBoxGenerator.generate(n,s,[]) |> Enum.map(fn x -> {:os.system_time(:nanosecond),x} end) end -new_tree = fn leafs,typ -> +new_tree = fn leaves,typ -> DynamicRtree.new(%{type: typ}) - generate.(leafs,1) + generate.(leaves,1) |> Enum.each(fn l -> DynamicRtree.insert(l) end) @@ -64,8 +64,8 @@ Benchee.run(%{ end} }, inputs: %{ - yellow() <> "1000 " <> green() <>"leafs" <> reset() => generate.(1000,1), - #yellow() <> "1000000 " <> green() <>"leafs" <> reset() => generate.(1000000,1) + yellow() <> "1000 " <> green() <>"leaves" <> reset() => generate.(1000,1), + #yellow() <> "1000000 " <> green() <>"leaves" <> reset() => generate.(1000000,1) }, time: 5) diff --git a/bench/update_benchmark.ex b/bench/update_benchmark.ex index e32feed..c9bafd6 100644 --- a/bench/update_benchmark.ex +++ b/bench/update_benchmark.ex @@ -11,9 +11,9 @@ generate = fn n,s -> BoundingBoxGenerator.generate(n,s,[]) |> Enum.map(fn x -> {UUID.uuid1(),x} end) end -new_tree = fn leafs,typ -> +new_tree = fn leaves,typ -> DynamicRtree.new(%{type: typ}) - DynamicRtree.insert(leafs) + DynamicRtree.insert(leaves) end unit_move = fn -> @@ -58,6 +58,6 @@ Benchee.run(%{ end}, }, inputs: %{ - cyan() <>"all leafs of tree ["<> color(195) <>"1000" <> cyan() <> "]" <> reset() => generate.(1000,1), - cyan() <>"all leafs of tree ["<> color(195) <>"100000" <> cyan() <> "]" <> reset() => generate.(100000,1) + cyan() <>"all leaves of tree ["<> color(195) <>"1000" <> cyan() <> "]" <> reset() => generate.(1000,1), + cyan() <>"all leaves of tree ["<> color(195) <>"100000" <> cyan() <> "]" <> reset() => generate.(100000,1) }, time: 10) diff --git a/lib/ddrt.ex b/lib/ddrt.ex index 642f0cc..316c1cc 100644 --- a/lib/ddrt.ex +++ b/lib/ddrt.ex @@ -3,93 +3,22 @@ defmodule DDRT do alias DDRT.DynamicRtree @moduledoc """ - This is the top level module, which one you should include at your application supervision tree. - - - If you want the distributed dynamic r-tree, start this process is a MUST. - DDRT.start_link(%{}) - or - children = [ - ... - {DDRT, %{}} - ] - - Else, if you want just the dynamic r-tree this module is not a MUST, but you can use it anyways. - - ## Configuration - - Let's talk about which parameters you can pass to init the DDRT. - - `name`: the name of the r-tree. - - `mode`: the mode of the r-tree. There are two: - - - `:dynamic`: all the r-trees with same name in different nodes will be sync. - - - `:standalone`: a dynamic r-tree that just will be at your node. - - `width`: the max width (the number of childs) than can handle every node. - - `type`: the type of data structure that maintains the r-tree. There are two: - - - `Map`: faster way. Recommended if you don't need sync. - - - `MerkleMap`: a bit slower, but perfect to get minimum r-tree modifications. - - `verbose`: allows `Logger` to report console logs. (Decrease performance) - - `seed`: the start seed for the middle nodes uniq ID of the r-tree. Same seed will always reach same sequence of uniq ID's. - - ## Distributed part - - You have to config the Erlang node interconnection with `libcluster`. - - The easy way is that: - - At `config.exs` define the nodes you want to connect: - - use Mix.Config - config :libcluster, - topologies: [ - example: [ - # The selected clustering strategy. Required. - strategy: Cluster.Strategy.Epmd, - # Configuration for the provided strategy. Optional. - config: [hosts: [:"a@localhost", :"b@localhost"]], - ] - ] - - - Then you should start you application for example like that: - - eduardo@elixir_rtree $ iex --name a@localhost -S mix - iex(a@localhost)1> - - eduardo@elixir_rtree $ iex --name b@localhost -S mix - iex(b@localhost)1> - - Finally, if you started in both nodes a `DDRT` with the same name you can simply use the `DynamicRtree` API module and you will have the r-tree sync between nodes. - - `Note`: is important that you have the same configuration for the DDRT at the different nodes. - + This is the top-level `DDRT` module. Use this to create a distributed r-tree. If you're only interested in using this package for the r-tree implementation, you should instead use `DDRT.DynamicRtree` """ - + + #DDRT party begins. @doc """ - DDRT party begins. - - ## Examples - `Note`: if you select mode distributed I'll force you anyways to use MerkleMap. - - iex> DDRT.start_link(%{mode: :distributed, name: Rupert, type: MerkleMap, seed: 5}) - {:ok, #PID<0.224.0>} + These are all of the possible DDRT configuration parameters and their default values. - ## Default values - [ - name: DynamicRtree - width: 6, - type: Map, - verbose: false, - seed: 0 - ] + ``` + [ + name: DDRT, + width: 6, + verbose: false, + seed: 0 + ] + ``` """ @spec start_link(DynamicRtree.tree_config()) :: {:ok, pid} def start_link(opts) do diff --git a/lib/ddrt/dynamic_rtree.ex b/lib/ddrt/dynamic_rtree.ex index ab78c38..98421ad 100644 --- a/lib/ddrt/dynamic_rtree.ex +++ b/lib/ddrt/dynamic_rtree.ex @@ -64,81 +64,7 @@ defmodule DDRT.DynamicRtree do name: nil @moduledoc """ - This is the API module of the elixir r-tree implementation where you can do the basic actions. - - ## Easy to use: - - Starts a local r-tree named as Peter - iex> DDRT.start_link(%{name: Peter}) - {:ok, #PID<0.214.0>} - - Insert "Griffin" on r-tree named as Peter - iex> DynamicRtree.insert({"Griffin",[{4,5},{6,7}]},Peter) - {:ok, - %{ - 43143342109176739 => {["Griffin"], nil, [{4, 5}, {6, 7}]}, - :root => 43143342109176739, - :ticket => [19125803434255161 | 82545666616502197], - "Griffin" => {:leaf, 43143342109176739, [{4, 5}, {6, 7}]} - }} - - - Insert "Parker" on r-tree named as Peter - iex> DynamicRtree.insert({"Parker",[{10,11},{16,17}]},Peter) - {:ok, - %{ - 43143342109176739 => {["Parker", "Griffin"], nil, [{4, 11}, {6, 17}]}, - :root => 43143342109176739, - :ticket => [19125803434255161 | 82545666616502197], - "Griffin" => {:leaf, 43143342109176739, [{4, 5}, {6, 7}]}, - "Parker" => {:leaf, 43143342109176739, [{10, 11}, {16, 17}]} - }} - - - Query which leafs at Peter r-tree overlap with box `[{0,7},{4,8}]` - iex> DynamicRtree.query([{0,7},{4,8}],Peter) - {:ok, ["Griffin"]} - - Updates "Griffin" bounding box - iex> DynamicRtree.update("Griffin",[{-6,-5},{11,12}],Peter) - {:ok, - %{ - 43143342109176739 => {["Parker", "Griffin"], nil, [{-6, 11}, {6, 17}]}, - :root => 43143342109176739, - :ticket => [19125803434255161 | 82545666616502197], - "Griffin" => {:leaf, 43143342109176739, [{-6, -5}, {11, 12}]}, - "Parker" => {:leaf, 43143342109176739, [{10, 11}, {16, 17}]} - }} - - Repeat again the last query - iex> DynamicRtree.query([{0,7},{4,8}],Peter) - {:ok, []} # Peter "Griffin" left the query bounding box - - Let's punish them - iex> DynamicRtree.delete(["Griffin","Parker"],Peter) - {:ok, - %{ - 43143342109176739 => {[], nil, [{0, 0}, {0, 0}]}, - :root => 43143342109176739, - :ticket => [19125803434255161 | 82545666616502197] - }} - - ## Easy concepts: - - Bounding box format. - - `[{x_min,x_max},{y_min,y_max}]` - - Example: & & & & & y_max & & & & & - A unit at pos x: 10, y: -12 , & & - with x_size: 1 and y_size: 2 & & - would be represented with & pos & - the following bounding box x_min (x,y) x_max - [{9.5,10.5},{-13,-11}] & & - & & - & & - & & & & & y_min & & & & & - + Use this module if you're interested in creating an R-Tree optimized to run on a single machine. If you'd instead like to run a distributed R-Tree on a cluster of Elixir nodes, use the `DDRT` module. """ def start_link(opts) do @@ -195,13 +121,13 @@ defmodule DDRT.DynamicRtree do def insert(_a, name \\ DDRT) @doc """ - Insert `leafs` at the r-tree named as `name` + Insert `leaves` at the r-tree named as `name` Returns `{:ok,map()}` ## Parameters - - `leafs`: the data to insert. + - `leaves`: the data to insert. - `name`: the r-tree name where you wanna insert. ## Examples @@ -234,8 +160,8 @@ defmodule DDRT.DynamicRtree do """ - def insert(leafs, name) when is_list(leafs) do - GenServer.call(name, {:bulk_insert, leafs}, :infinity) + def insert(leaves, name) when is_list(leaves) do + GenServer.call(name, {:bulk_insert, leaves}, :infinity) end def insert(leaf, name) do @@ -274,7 +200,7 @@ defmodule DDRT.DynamicRtree do def delete(_a, name \\ DDRT) @doc """ - Delete the leafs with the given `ids`. + Delete the leaves with the given `ids`. Returns `{:ok,map()}` @@ -301,7 +227,7 @@ defmodule DDRT.DynamicRtree do end @doc """ - Update a bunch of r-tree leafs to the new bounding boxes defined. + Update a bunch of r-tree leaves to the new bounding boxes defined. Returns `{:ok,map()}` @@ -523,7 +449,7 @@ defmodule DDRT.DynamicRtree do end @impl true - def handle_call({:bulk_insert, leafs}, _from, state) do + def handle_call({:bulk_insert, leaves}, _from, state) do r = {_atom, t} = case state.tree do @@ -532,7 +458,7 @@ defmodule DDRT.DynamicRtree do _ -> final_rbundle = - leafs + leaves |> Enum.reduce(get_rbundle(state), fn l, acc -> %{acc | tree: acc |> tree_insert(l)} end) diff --git a/lib/ddrt/dynamic_rtree_impl.ex b/lib/ddrt/dynamic_rtree_impl.ex index 6421157..9642e82 100644 --- a/lib/ddrt/dynamic_rtree_impl.ex +++ b/lib/ddrt/dynamic_rtree_impl.ex @@ -107,7 +107,7 @@ defmodule DDRT.DynamicRtreeImpl do def tree_query(rbundle, box) do t1 = :os.system_time(:microsecond) - r = find_match_leafs(rbundle, box, [get_root(rbundle)], [], []) + r = find_match_leaves(rbundle, box, [get_root(rbundle)], [], []) t2 = :os.system_time(:microsecond) if rbundle.verbose, @@ -379,28 +379,28 @@ defmodule DDRT.DynamicRtreeImpl do ## Query - defp find_match_leafs(rbundle, box, dig, leafs, flood) do + defp find_match_leaves(rbundle, box, dig, leaves, flood) do f = hd(dig) tail = if length(dig) > 1, do: tl(dig), else: [] {content, _dad, fbox} = rbundle.tree |> rbundle[:type].get(f) - {new_dig, new_leafs, new_flood} = + {new_dig, new_leaves, new_flood} = if Utils.overlap?(fbox, box) do if is_atom(content) do - {tail, [f] ++ leafs, flood} + {tail, [f] ++ leaves, flood} else if Utils.contained?(box, fbox), - do: {tail, leafs, [f] ++ flood}, - else: {content ++ tail, leafs, flood} + do: {tail, leaves, [f] ++ flood}, + else: {content ++ tail, leaves, flood} end else - {tail, leafs, flood} + {tail, leaves, flood} end if length(new_dig) > 0 do - find_match_leafs(rbundle, box, new_dig, new_leafs, new_flood) + find_match_leaves(rbundle, box, new_dig, new_leaves, new_flood) else - new_leafs ++ explore_flood(rbundle, new_flood) + new_leaves ++ explore_flood(rbundle, new_flood) end end @@ -417,26 +417,26 @@ defmodule DDRT.DynamicRtreeImpl do if length(next_floor) > 0, do: explore_flood(rbundle, next_floor), else: flood end - defp find_match_depth(rbundle, box, dig, leafs, depth) do + defp find_match_depth(rbundle, box, dig, leaves, depth) do {f, cdepth} = hd(dig) tail = if length(dig) > 1, do: tl(dig), else: [] {content, _dad, fbox} = rbundle.tree |> rbundle[:type].get(f) - {new_dig, new_leafs} = + {new_dig, new_leaves} = if Utils.overlap?(fbox, box) do if cdepth < depth and is_list(content) do childs = content |> Enum.map(fn c -> {c, cdepth + 1} end) - {childs ++ tail, leafs} + {childs ++ tail, leaves} else - {tail, [f] ++ leafs} + {tail, [f] ++ leaves} end else - {tail, leafs} + {tail, leaves} end if length(new_dig) > 0, - do: find_match_depth(rbundle, box, new_dig, new_leafs, depth), - else: new_leafs + do: find_match_depth(rbundle, box, new_dig, new_leaves, depth), + else: new_leaves end ## Delete diff --git a/mix.exs b/mix.exs index b461513..9452e2a 100644 --- a/mix.exs +++ b/mix.exs @@ -10,7 +10,11 @@ defmodule DynamicRtree.MixProject do deps: deps(), source_url: "https://github.com/windfish-studio/dynamic-rtree", description: description(), - package: package() + package: package(), + docs: [ + main: "readme", + extras: ["README.md"] + ] ] end @@ -47,7 +51,7 @@ defmodule DynamicRtree.MixProject do It's mainly a R-tree. - Why dynamic? Because it's optimized to do fast updates at the tree leafs spatial index. + Why dynamic? Because it's optimized to do fast updates at the tree leaves spatial index. Why distributed? Well.. you can run the DDRT on different nodes and they will have the same r-tree data. "