Skip to content

Commit

Permalink
Merge pull request #103
Browse files Browse the repository at this point in the history
Traverse relations graph right to left when checking relations
  • Loading branch information
uatuko authored Jun 4, 2024
2 parents bef3937 + c83821e commit 5714f1b
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 28 deletions.
3 changes: 3 additions & 0 deletions docs/rebac.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ To derive the relation `[]user:jane/reader/doc:notes.txt` using a BFS[^bfs] grap
(which has **O(v+e)** complexity), we will need to read 10,003 tuples. This can be really slow depending
on DB load and number of concurrent requests.

> 💡 This is only an illustrative example. In reality, Sentium traverse the relations graphs from right
> to left which will result in only 3 reads in this instance.
![Relations Graph #02](./assets/rebac-relations-graph-02.svg)

In order to maintain a consistent and a predictable throughput (QPS), Sentium offers different optimisation
Expand Down
40 changes: 19 additions & 21 deletions src/svc/relations.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "relations.h"

#include <queue>
#include <unordered_set>

#include <google/protobuf/util/json_util.h>
Expand Down Expand Up @@ -83,9 +84,8 @@ rpcCheck::result_type Impl::call<rpcCheck>(

auto *path = response.mutable_path();
path->Reserve(r.path.size());
while (!r.path.empty()) {
map(r.path.front(), path->Add());
r.path.pop();
for (const auto &t : r.path) {
map(t, path->Add());
}
}

Expand Down Expand Up @@ -362,7 +362,7 @@ Impl::graph_t Impl::graph(

class vertex_t {
public:
using path_t = std::queue<db::Tuple>;
using path_t = std::deque<db::Tuple>;

struct hasher {
void combine(std::size_t &seed, const std::string &v) const noexcept {
Expand All @@ -371,7 +371,7 @@ Impl::graph_t Impl::graph(

std::size_t operator()(const vertex_t &v) const noexcept {
std::size_t seed = 0;
combine(seed, v.relation());
combine(seed, v.strand());
combine(seed, v.entityType());
combine(seed, v.entityId());

Expand All @@ -384,44 +384,44 @@ Impl::graph_t Impl::graph(
// Copy constructor is _only_ used when keeping track of visited vertices. In order to
// _potentially_ save memory `_path` is ignored.
vertex_t(const vertex_t &v) noexcept :
_entityId(v._entityId), _entityType(v._entityType), _relation(v._relation){};
_entityId(v._entityId), _entityType(v._entityType), _strand(v._strand){};

vertex_t(db::Tuple &&t) :
_entityId(t.rEntityId()), _entityType(t.rEntityType()), _relation(t.relation()) {
_path.push(std::move(t));
_entityId(t.lEntityId()), _entityType(t.lEntityType()), _strand(t.strand()) {
_path.push_front(std::move(t));
}

vertex_t(const vertex_t &v, db::Tuple &&t) :
_entityId(t.rEntityId()), _entityType(t.rEntityType()), _path(v._path),
_relation(t.relation()) {
_path.push(std::move(t));
_entityId(t.lEntityId()), _entityType(t.lEntityType()), _path(v._path),
_strand(t.strand()) {
_path.push_front(std::move(t));
}

bool operator==(const vertex_t &rhs) const noexcept {
return (
_entityId == rhs._entityId && _entityType == rhs._entityType &&
_relation == rhs._relation);
_strand == rhs._strand);
}

const std::string &entityId() const noexcept { return _entityId; }
const std::string &entityType() const noexcept { return _entityType; }
const std::string &relation() const noexcept { return _relation; }
const std::string &strand() const noexcept { return _strand; }

path_t &path() noexcept { return _path; }

private:
std::string _entityId;
std::string _entityType;
path_t _path;
std::string _relation;
std::string _strand;
};

std::int32_t cost = 0;
std::queue<vertex_t> queue;

// Assume there's no direct relation between left and right entities to begin with
{
auto tuples = db::ListTuplesRight(spaceId, left, {}, {}, limit);
auto tuples = db::ListTuplesLeft(spaceId, right, relation, {}, limit);
for (auto &t : tuples) {
queue.emplace(std::move(t));
}
Expand All @@ -439,16 +439,14 @@ Impl::graph_t Impl::graph(
}

visited.insert(v);
for (auto &t :
db::ListTuplesRight(spaceId, {v.entityType(), v.entityId()}, {}, {}, limit)) {
if (v.relation() != t.strand()) {
for (auto &t : db::ListTuplesLeft(spaceId, {v.entityType(), v.entityId()}, {}, {}, limit)) {
if (v.strand() != t.relation()) {
continue;
}

if (t.relation() == relation && t.rEntityId() == right.id() &&
t.rEntityType() == right.type()) {
if (t.lEntityId() == left.id() && t.lEntityType() == left.type()) {
// Found
v.path().push(std::move(t));
v.path().push_front(std::move(t));
return {cost, v.path()};
}

Expand Down
4 changes: 2 additions & 2 deletions src/svc/relations.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once
#include <deque>
#include <optional>
#include <queue>
#include <string_view>

#include <google/rpc/status.pb.h>
Expand Down Expand Up @@ -45,7 +45,7 @@ class Impl {
private:
struct graph_t {
std::int32_t cost;
std::queue<db::Tuple> path;
std::deque<db::Tuple> path;
};

struct spot_t {
Expand Down
19 changes: 14 additions & 5 deletions src/svc/relations_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,9 @@ TEST_F(svc_RelationsTest, Check) {
// member | group:writers | member | group:readers
// member | group:readers | reader | doc:notes.txt
// member | group:readers | member | group:loop
// member | group:loop | member | group:writers
// member | group:loop | reader | doc:notes.txt
// owner | group:writers | owner | doc:notes.txt
// owner | group:writers | reader | doc:notes.txt
//
// Checks:
// 1. []user:jane/reader/doc:notes.txt - ✓
Expand Down Expand Up @@ -360,8 +361,8 @@ TEST_F(svc_RelationsTest, Check) {
{{
.lEntityId = "group:loop",
.lEntityType = "svc_RelationsTest.Check-with_graph_strategy",
.relation = "member",
.rEntityId = "group:writers",
.relation = "reader",
.rEntityId = "doc:notes.txt",
.rEntityType = "svc_RelationsTest.Check-with_graph_strategy",
.strand = "member",
}},
Expand All @@ -373,6 +374,14 @@ TEST_F(svc_RelationsTest, Check) {
.rEntityType = "svc_RelationsTest.Check-with_graph_strategy",
.strand = "owner",
}},
{{
.lEntityId = "group:writers",
.lEntityType = "svc_RelationsTest.Check-with_graph_strategy",
.relation = "reader",
.rEntityId = "doc:notes.txt",
.rEntityType = "svc_RelationsTest.Check-with_graph_strategy",
.strand = "owner",
}},
});

for (auto &t : tuples) {
Expand Down Expand Up @@ -401,7 +410,7 @@ TEST_F(svc_RelationsTest, Check) {
EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code());
ASSERT_TRUE(result.response);
EXPECT_EQ(true, result.response->found());
EXPECT_EQ(4, result.response->cost());
EXPECT_EQ(7, result.response->cost());
EXPECT_FALSE(result.response->has_tuple());
ASSERT_EQ(4, result.response->path().size());

Expand Down Expand Up @@ -429,7 +438,7 @@ TEST_F(svc_RelationsTest, Check) {
EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code());
ASSERT_TRUE(result.response);
EXPECT_EQ(false, result.response->found());
EXPECT_EQ(7, result.response->cost());
EXPECT_EQ(1, result.response->cost());
EXPECT_FALSE(result.response->has_tuple());
EXPECT_TRUE(result.response->path().empty());
}
Expand Down

0 comments on commit 5714f1b

Please sign in to comment.