Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(namespaces): Initial support for multi-tenant #3260

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open

Conversation

chakaz
Copy link
Collaborator

@chakaz chakaz commented Jul 3, 2024

This PR introduces a way to create multiple, separate and isolated namespaces in Dragonfly. Each user can be associated with a single namespace, and will not be able to interact with other namespaces.

This is still experimental, and lacks some important features, such as:

  • Replication and RDB saving completely ignores non-default namespaces
  • Defrag and statistics either use the default namespace or all namespaces without separation

To associate a user with a namespace, use the ACL command with the NAMESPACE:<namespace> flag:

ACL SETUSER user NAMESPACE:namespace1 ON >user_pass +@all ~*

For more examples and up to date info check
tests/dragonfly/acl_family_test.py - specifically the test_namespaces function.

This PR introduces a way to create multiple, separate and isolated
namespaces in Dragonfly. Each user can be associated with a single
namespace, and will not be able to interact with other namespaces.

This is still experimental, and lacks some important features, such as:
* Replication and RDB saving completely ignores non-default namespaces
* Defrag and statistics either use the default namespace or all
  namespaces without separation

To associate a user with a namespace, use the `ACL` command with the
`TENANT:<namespace>` flag:

```
ACL SETUSER user TENANT:namespace1 ON >user_pass +@ALL ~*
```

For more examples and up to date info check
`tests/dragonfly/acl_family_test.py` - specifically the
`test_namespaces` function.
@chakaz chakaz changed the title feat(namespaces): Initial support for multi-tenant #3050 feat(namespaces): Initial support for multi-tenant Jul 3, 2024
@chakaz chakaz requested a review from adiholden July 3, 2024 20:05
@romange
Copy link
Collaborator

romange commented Jul 4, 2024

Is it the whole PR or the first one in the series?
I am asking because, we talked about having an HLD .md document in docs/, and also having non-functional changes first to ease the review.

// TODO allow reset all
// bool reset_all{false};

std::optional<std::string> ns{};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subjective nit (related to the previous comment) - can be just a string with empty indicating it's not set.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG. + @kostasrim to make sure this UpdateRequest class is always expected to contain all information (i.e. that there can't be only deltas in it)


namespace dfly {

class Namespace {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add class description, including a threading/ownership model?
can also be a reference to .md file if you write it there.


class Namespace {
public:
explicit Namespace();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicit not needed?

@@ -310,15 +310,15 @@ class ElementAccess {
};

std::optional<bool> ElementAccess::Exists(EngineShard* shard) {
auto res = shard->db_slice().FindReadOnly(context_, key_, OBJ_STRING);
auto res = context_.ns->GetCurrentDbSlice().FindReadOnly(context_, key_, OBJ_STRING);
Copy link
Collaborator

@romange romange Jul 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our call chain is already designed to pass shard by argument, therefore it's already in a register. I suggest that you actually use here GetDbSlice(ShardId) and avoid doing it implicitly by calling GetCurrentDbSlice.
Now, it complicates the whole expression in so many places, so I also suggest to add a convenience wrapper ConnectionContext::FindReadOnly.
why? because we use context_ twice here, so instead we could shorten this
in some many places to just context_.FindReadOnly(key_, OBJ_STRING)
Of course it also applies to other frequent operations like FindMutable

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a small challenge with this proposal: transaction that's part of the connection context can sometimes be null. If it's null, we can't tell which tx time to pass to FindReadOnly() (which requires a DbContext)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, sorry, my bad. I thought that your proposal was to make this change in ConnectionContext. I now understand you meant in DbContext..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the only place which uses DbContext, but not using OpArgs, is bitops family, so I didn't do that.
But I used shard->shard_id() as you proposed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I confused both types.

@@ -355,7 +355,7 @@ OpResult<bool> BitNewValue(const OpArgs& args, std::string_view key, uint32_t of
bool bit_value) {
EngineShard* shard = args.shard;
ElementAccess element_access{key, args};
auto& db_slice = shard->db_slice();
auto& db_slice = args.db_cntx.ns->GetCurrentDbSlice();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly, please add a convenience accessor GetDbSlice in OpArgs that uses the shard variable inside OpArgs

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding it in OpArgs is a great idea, it saves a lot of boilerplate, thanks!

@chakaz
Copy link
Collaborator Author

chakaz commented Jul 7, 2024

Is it the whole PR or the first one in the series?

This is the whole PR.
Indeed, as you mentioned, we talked about adding a stub where there's an empty API that always returns the same pointer.
However, after getting back to work on it, I decided that it's kinda ridiculous to do that, because the "implementation" is just a map... (check out namespaces.h and .cc). The challenges are:

  1. Getting everything to compile
  2. Getting the initialization and turn down to work properly
    Both are not affected by whether the API works or not.
    So I decided to have it all in the same PR

I am asking because, we talked about having an HLD .md document in docs/, and also having non-functional changes first to ease the review.

Indeed, I forgot about the .md hld. I'll add that very soon.

namespace_ = ns;
}

std::string User::Namespace() const {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const ref

Transaction*, std::string_view key) -> bool {
return owner->db_slice().FindReadOnly(context, key, req_obj_type).ok();
auto ns = &trans->GetNamespace();
const auto key_checker = [req_obj_type, ns](EngineShard* owner, const DbContext& context,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems you do not use ns ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See line 343 below: ns->GetDbSlice()...

@chakaz
Copy link
Collaborator Author

chakaz commented Jul 7, 2024

I added the high level design .md doc. Please let me know what else you would like me to include in it. I captured everything I could think of, but I probably have missed some details...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants