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.