How GraphQL would work with Multi-tenancy

Let’s look at how GraphQL endpoints would work along with multi-tenancy. Users usually start with supplying a GraphQL schema from which we generate an API for them.

Schema Updates

What happens when user updates the schema

Currently, the GraphQL schema can be updated by the user by posting it to /admin/schema or calling the updateGQLSchema endpoint on the /admin endpoint. With multi-tenancy, we would also need to accept the namespace which would be extracted from the Dgraph ACL JWT. If not JWT is supplied, then we would assume that the request is for the default namespace. On getting the schema update request, if the namespace doesn’t exist yet, we would return an error otherwise we would

  • Store the schema inside the dgraph.graphql.schema predicate corresponding to the namespace that the request was for.
  • Construct the Dgraph schema from the GraphQL schema and perform an Alter operation on the provided namespace.
  • Update the dgraph.schema.schema_history to have this new schema update.
  • Keep an in-memory copy of the schema corresponding to the namespaces. Since there can be a lot of different schemas, we’ll keep an LRU cache of say the 500 most recent schema’s. Rest of the schemas can be lazily loaded.

The three steps above happen at dgraph/schema.go at be9ce74e8c7dac0b0fa856e447a5fa6736022f50 · dgraph-io/dgraph · GitHub

Reading schema from disk on Alpha restart

When Alpha starts, we query the dgraph.graphql.schema predicate and rebuild the GraphQL schema to store it in memory so that we can serve the GraphQL API. With multi-tenancy, we would still do this query for dgraph.graphql.schema for the default namespace that exist and keep an in-memory copy of our schema. For the rest of the namespaces, we would lazily load the schema, when we get a request for that namespace. Such a request could be a query, mutation, or even a /health/probe request with the JWT for the namespace.

query {
  ExistingGQLSchema(func: has(dgraph.graphql.schema)) {
    uid
    dgraph.graphql.schema
  }
}

The part mentioned above happens in initServer() method at dgraph/admin.go at be9ce74e8c7dac0b0fa856e447a5fa6736022f50 · dgraph-io/dgraph · GitHub

Alpha nodes subscribing to schema updates

We needed a way to stream the schema updates that happen on one Alpha node to other nodes. This happens via badger subscriptions. Since dgraph.graphql.schema is an internal predicate, it is stored only on Group 1 nodes. All alpha nodes open a grpc stream to an Alpha node in Group 1. The alpha nodes in Group 1 are listening to updates on the dgraph.graphql.schema and stream it out to other nodes in other groups. With multi-tenancy, it would be difficult to subscribe to updates to dgraph.graphql.schema across all namespaces. Hence we would need a way to signal to other Alpha nodes that the GraphQL schema for a namespace has been updated. Maybe this can be done by Zero using membership updates. This is an open question to figure out.

The client code that subscribes for updates is present at dgraph/admin.go at be9ce74e8c7dac0b0fa856e447a5fa6736022f50 · dgraph-io/dgraph · GitHub and the server code is present at dgraph/worker.go at be9ce74e8c7dac0b0fa856e447a5fa6736022f50 · dgraph-io/dgraph · GitHub

Queries

The namespace would be figured out from the Dgraph ACL JWT.

  • If the schema for the namespace exists, then we’d use that to validate the request otherwise we would query the schema for the namespace.
  • The rewritten DQL query would finally have to be made for the correct namespace.
  • This should work with Auth as well because we would validate the JWT using the Dgraph.Authorization value mentioned in the schema for the namespace for which the query is.
  • If a query is for a namespace that doesn’t exist, then that’s an error.

Mutations

Same as queries

Subscriptions

Similar to queries and mutations. The only difference being that we have goroutines for subscriptions that run. We’ll need to have separate goroutines for the same query across different namespaces.

Admin endpoint

The Admin endpoint would also know which namespace to perform export, backup and other operations on using the ACL JWT. If no JWT is given, then it would perform these operations in the default namespace.

1 Like

@pawan Wouldn’t the endpoint itself change for every tenant? Doesn’t every tenant have its own /admin/schema rather than one endpoint for all tenants?

I mean, why is header even relevant here? Since the endpoints would typically be like /tenant/123/admin/schema or am I wrong here?

Does Dgraph graphql currently support multi-tenancy?