GraphQL was originally not designed as a database language yet it's quickly becoming a popular choice to interact with databases. What if databases natively support GraphQL default? What are the advantages of native GraphQL support? How is a GraphQL query handled behind the scenes in FaunaDB and what database guarantees does such an approach provide?
Native GraphQL, GraphQL as a Database Query Language
AI Generated Video Summary
The Talk introduces NativeGraphQL, which translates a GraphQL query into one FQL query, offering advantages over traditional GraphQL resolvers. The VANA approach to NativeGraphQL avoids the n plus one problem and provides several benefits. FQL is a good fit for NativeGraphQL as it solves the tree traversal problem and offers the same advantages as the rest of the native FQL language. Native GraphQL also provides multi-region scalability, ACID, and transactionality out-of-the-box.
1. Introduction to NativeGraphQL at VANA
Hi everyone, I'm Brichts, I'm super excited today to talk at the GraphQL Galaxy conference. Today I'm going to talk about NativeGraphQL or GraphQL as a database query language. NativeGraphQL means that a GraphQL query is going to translate into one FQL query. This one-to-one translation has huge advantages. Let's take a detour and understand how GraphQL resolvers work. GraphQL resolvers work by mapping fields to functions. However, this approach can lead to the n plus one problem, where multiple database calls are made. To solve this problem, batching and caching techniques can be used. But these solutions introduce complexity and have limitations. The VANA approach to NativeGraphQL avoids these problems and provides several advantages.
Hi everyone, I'm Brichts, I'm super excited today to talk at the GraphQL Galaxy conference. I'm Databrichts on Twitter, I work for the Vana database and today I'm going to talk about NativeGraphQL or GraphQL as a database query language.
Now if we talk about Native GraphQL at VANA what does it mean? Well, first of all, we have a VANA query language which we call FQL and basically NativeGraphQL means that a GraphQL query is going to translate into one FQL query. That one-to-one translation has huge advantages, so first of all, you might wonder which advantages we'll look into that, and question 2, why doesn't everyone do this if there are such advantages?
To answer these questions, we actually have to answer other questions like how do GraphQL resolvers work? So let's take a detour. How do GraphQL resolvers work? Well, typically if you have a query like this with getList, toDo, title, every field in here, like getList and toDo's and title are fields, will map on a function. So getList will be a function and that will delegate to the toDo's function, that will delegate again to the title function, for example, to the title attribute. This is a resolver chain, which is a chain of functions, but it's actually more of a resolver tree of function calls.
Because here there's one function that calls n functions. And if we turn this around, we get n plus 1 and it's basically a problem. And this is actually called the n plus one problem. That's why I turned it around. And when is this a problem? Well, basically, if you're going to call the database for each of these resolvers, because then you get n plus one database calls, which is not efficient. So question four, how can we solve the n plus one problem? Well, there are multiple solutions. Solution one is batching or n in-memory caching. So in that approach, we're going to hook into these functions, for example, todo.titles, and just wait until all the todo.titles are called and then combine these. So instead of going to do n calls for these todo.titles, we're going to do one call. So in total, two calls. That's batching and that's often combined with caching. So if a similar call comes in, then instead of going to the database, we can go to an in-memory cache, so we don't hit the database at all.
A very popular implementation is Facebook's data loader, which you can just plug in on top of your resolvers and it will handle it for you. However, there's a problem with this solution as well. It should in fact be a last resort. Well, why? Your data is no longer live. It's no longer consistent. You can't apply it on everything. You can't patch everything. So you will have still multiple calls. What about caching validation, memory pressure that you have to do deal with suddenly. So it introduces complexity. So the first question, which advantages that FANAS approach provides? Well, it doesn't deal with these problems because it doesn't have these problems.
2. Advantages and Fit of FQL in Native GraphQL
It is live by default. It is consistent. It requires no extra work. And there is no memory constraint problem. It just works out of the box. So you don't have to do this. The problem here is that what joins solve, which is a join between two tables, is a different problem than what the actual problem is, which is more like a tree traversal problem or a graph-like problem. So joins are maybe the wrong tool for the job. There is an implementation, a very impressive implementation, called JoinMonster, which actually comes from the problem they're trying to solve. A monster join that might be the result of a GraphQL query. That's why FQL actually fits the problem. Because we have the same advantages as the rest of the normal native FQL language, we can actually combine that with FQL and use FQL for the flexibility and power and GraphQL for the ease of use. We have multi-region out-of-the-box, scalability out-of-the-box, we have 100% ACID and transactionality out-of-the-box. So that's what native GraphQL is.
It is live by default. It is consistent. It requires no extra work. And there is no memory constraint problem. It just works out of the box. So you don't have to do this.
Solution two, generate one query, which is what FANAS does behind the scenes. But why doesn't everyone do that? If you would look at SQL, for example, and let's say we would select a star from lists where ID is equal to something, then we would go to the to-do calls and do the same and try to concatenate that query. Of course, we'll have to do it for multiple to-do's, so we'll end up with a join. And the problem is if we go deeper like that in a GraphQL traversal, we might end up with a lot of joins. Now, not only is it super complex to analyze this query and then generate SQL from it and then transform the results back to a GraphQL format, it might also be inefficient depending on the joins. You might overfetch a lot and then have to throw away things. And then how are we going to paginate this? Limit 100 might not be exactly what you're looking for. So the problem here is that what joins solve, which is a join between two tables, is a different problem than what the actual problem is, which is more like a tree traversal problem or a graph-like problem. So joins are maybe the wrong tool for the job.