Hacker News new | ask | show | jobs
by KirinDave 3159 days ago
Sure, but you're asking your server to support a full query language. Even with data-fetch on a js-impl (or ... a hell of a lot of by-hand tooling in Python with Graphene), this overhead really hurts your qps.

To be honest, I sort of wonder if the right place to implement the GQL interpretation and scheduling layer is in a service-worker.

That way, the client devs who love this complex query language can also support the queries they need and ensure their calling conventions don't violate performance boundaries in the gql server implementation.

2 comments

Facebook’s Relay solves the throughput problem by effectively precompiling queries. Their production apps only make a subset of the requests of the full graphql language - and those are frozen at deploy/compile time. In some sense this makes their production graphql more like “auto-generated REST” from the server’s perspective.

That said, thinking of this in terms of client vs server devs isn’t useful - it’s not like anyone on the team want to see the servers burn. The beauty of graphql is it’s descriptiveness - you could create a query performance score and fail CI if your main page queries are hitting too high of a cost. A meet up in SF last year had some FB engineers discussing this - they do something along these lines before shipping their android/iOS apps.

> Facebook’s Relay solves the throughput problem by effectively precompiling queries. Their production apps only make a subset of the requests of the full graphql language

I am aware of this, and it seems ridiculous to me. It discards the value of having a graph query language. I'm open to being wrong about this assessment.

Why am I using a new query language if it's going to end up being exactly the same as the old rest query environment?

> That said, thinking of this in terms of client vs server devs isn’t useful

Considering it from a typical labor division standpoint or by technical dependency and requirements isn't useful? I disagree.

> The beauty of graphql is it’s descriptiveness

Which you have said the "performant" way to handle is to discard and turn into a formulaic series of bytes that is indistinguishable form a new term encoding on restful POST bodies.

> you could create a query performance score and fail CI if your main page queries are hitting too high of a cost.

This is exactly how everyone already tools RESTful endpoints. Whole industries are built around scoring endpoints this way.

Inevitably my concern about GraphQL being hard to implement server side is inevitably met with Facebook's outer and inner solution. The outer solution: Just pretend it's graphql, really it's postbodies and we won't let you write custom queries except for column filtering (which these solutions WILL fetch).

The inner solution, it has been intimated to me, is they have custom GQL libraries written in Haskell using Haxl that they don't share because the industry is too busy being afraid of it. Sadly, these don't appear to be open source (and even if they were, a lot of shops would not be equipped to use them).

> Why am I using a new query language if it's going to end up being exactly the same as the old rest query environment?

the client controls the APIs without you touching them. It is required that the client syncs up with the server but you don't have to touch backend code to change the set of fields you want (this works for internal clients, not open APIs, clearly).

If you're thinking "but I can just allow the client to select fields in my JSONAPI compliant endpoint", yes, you can, and when you add includes for nested fields, and nested selections, you basically get 90% of graphql :)

> yes, you can, and when you add includes for nested fields, and nested selections, you basically get 90% of graphql :)

True, but until someone provides a great software kit for doing that work, I'm not going to allow arbitrary extensions.

It's really not that hard to build a relatively optimal GraphQL server. Obviously it requires paying careful attention to performance, and to take steps to mitigate abuse (query whitelists, complexity caps etc). If you were to move the implementation off the server and into the client you'd use a lot of the benefits of GraphQL in the first place (performance being a major one).
Obviously it requires paying careful attention to performance, and to take steps to mitigate abuse (query whitelists, complexity caps etc)

Are these also "not that hard?"

"Query whitelists" sounds like sending to the server something like `{"query_id": 4, "variables": ...}` instead of `{"query": ..., "variables": ...}` which are straightforward to implement using any kind of server side key-value store and a middleware that maps the `query_id` back to the corresponding `query`, a tool that can help you with this is Apollo's PersistGraphQL [1]

I have no idea how I would go about implementing complexity caps though, but I guess I would do something like what GitHub has done for their own GraphQL API [2], which they explain better than I can.

[1]: https://github.com/apollographql/persistgraphql [2]: https://developer.github.com/v4/guides/resource-limitations/

Another simple option for limiting complexity (I've considered implementing this in my GraphBrainz project): in the `context` provided to the GraphQL query resolver, increment a counter whenever a resolver requires fetching from an external API/database/etc. (whatever "too much of" would constitute abuse or just take a long time). Fail if the counter reaches some threshold. This would be really easy.

Also, instead of multiplying node counts like GitHub does (which is pretty clever!), another simple option would be to look at the depth of the query (how many levels down is the deepest leaf), and fail if it's over some maximum. This is also very easy to do as you get the query AST in the `info` field of the resolver. (This one is less effective than the one above since depth doesn't totally match up with resource usage, fields can be aliased, etc. but you get the idea.)

> Another simple option for limiting complexity

Okay but... I guess my question is: why are you denying a client the right to make a complex query? Is it because all your queries are kinda slow and so you must hand-optimize them, leading to a combinatoric explosion of codepaths?

Or is it because your clients cannot judge how complex the queries they're making are? If so, isn't this actually a gap in the GQL spec? Lots of other query language implementations offer query description and estimation commands in their code.

Your proposed solution seems to me like it's brutal for your consumers. There's minimal indication of how quickly your complexity metric will rise in the query. You'd need to add ad-hoc per query&mutation arguments to push that query complexity cap up for legitimate uses.

> Okay but... I guess my question is: why are you denying a client the right to make a complex query? Is it because all your queries are kinda slow and so you must hand-optimize them, leading to a combinatoric explosion of codepaths?

No, it's because:

(1) This is a feature of literally every API, most of them just use the extremely blunt instrument of rate limiting (even if requesting the same simple scalar value field over and over again does not add any strain on the server, you'll be rate limited just the same). Why aren't you asking this same question about REST queries?

and

(2) The 'Graph' part of 'GraphQL' means that queries can theoretically request connected nested objects of nearly infinite depth. This doesn't require that anything about the query code be slow or needs to be hand-optimized, or that there be any complex codepaths, just that MORE JOINS == MORE WORK and MORE PAYLOAD, no matter how perfectly optimized it is. Why aren't you asking "why does REST deny clients the right to make as deeply nested queries as they need?"

Query whitelist: Not trivial to do (properly) from scratch, but seems to be well-supported in library form.

Complexity caps: Depends on the server implementation. Sangria (Scala implementation) has it built in. For others, i'm not sure. It would be easier to add a depth cap, but a complexity cap is more useful. I think whitelisting and.or rate-limiting is the way to go if you're actually concerned about your GraphQL server being abused though.

Query whitelist: Takes the query language out of the game and makes the system indistinguishable from old pre-restful .cgi endpoints.

Complexity caps: In the absence of good query scheduling this is a defense mechanism by GQL server providers. Great. But why are we doing it in the first place. This is still more complex with nearly identical outcomes to restful endpoints.

A query white list is functionality identical to hand-written endpoints to serve each use case, except there's no hand-writing involved.

As long as you have a half-decent build pipeline and embrace static patterns on the client, you get to keep all of the benefits.

To be honest, you seem like someone who hasn't done a lot of client-side work, especially with component systems. The benefits to development speed and maintainability are significant, and nearly all the downsides can be mitigated.

If you're only approaching this from the perspective of a backend developer, i'd fully expect GraphQL to be underwhelming to you.

> A query white list is functionality identical to hand-written endpoints to serve each use case, except there's no hand-writing involved.

This isn't really graphQL though, is it? This is an ORM. The ones I work with (in Blu(cough)Python) the binding layer I use works off standard ORM stuff, and could route object retrieval out of a restful API with minimal developer work as well (AuthZ, AuthN).

> As long as you have a half-decent build pipeline and embrace static patterns on the client, you get to keep all of the benefits.

I guess I simply have no idea what the benefits are. I keep asking, people keep giving me "technical" benefits that I'm challenging and I've yet to really see any refutation to those challenges.

It keeps coming back to:

> The benefits to development speed and maintainability are significant

And okay, a query DSL is useful. I totally buy into this. In fact, I have made many query DSLs. I advocate for linguistic metaprogramming every chance I get.

But what I'm saying is that the way it's implemented (server side) is, in many ways, regressive. I think you'd get most of the same benefits from a service worker doing that arbitration and dispatch. Your query whitelist updates wouldn't require infrastructure pushes AND client pushes. You'd have better caching story (because SW would make it easier to cache both the intermediate queries and prevent refetch on the top level call).

My primary objection to GraphQL is just how immature the tooling is. And I know how to dive in and provide SDKs for solving some of these hard problems, but then we end up with the 2017 equivalent of a .cgi script and I'm just... I don't get why any architect would agree to that.

> i'd fully expect GraphQL to be underwhelming to you.

Then why push it down to me as a requirement? And why recommend outrageous "solutions" like "oh well we'll just query whitelist so you lose all your flexibility." "We implement internal complexity caps you have no way to perceive from the query language but don't worry we'll 400 your request so you know."

These seem, to me, to make your frontend experience objectively worse than restful calls.

> It's really not that hard to build a relatively optimal GraphQL server. Obviously it requires paying careful attention to performance, and to take steps to mitigate abuse (query whitelists, complexity capsi

I'm sorry, but I categorically reject the value of a query language for APIs if you turn it into something indistinguishable from a POST-body driven web framework with less cachability.

What is the point of a query language that may act across multiple sub-domains if your provider then says, "But only these queries are allowed."

AFAICT, it's a waste of time. You lose so much in the migration from a RESTful postbody API in exchange for what amounts to... well... a DSL for writing API calls that any javascripter worth their salt could have made already.

You do realize that "relatively optimal" is an oxymoron?
It is no more an oxymoron than "locally optimal". There is always a scope for claims of optimal in practice.

Perhaps it was "superfluous" to say. But then, so was your post.

No I don't agree.

To me, it's like saying "somewhat unique".

The phrase "relatively optimal" doesn't make sense to me. Do you have a server that's optimal relative to some other server? That's just "optimal", no? And if it's sub-optimal relative to some other server, that's just "sub-optimal"?

Locally optimal is a known thing (it even has it's own wikipedia entry https://en.wikipedia.org/wiki/Local_optimum), but "relatively optimal" is just sloppy thinking in my view.

If you want to know what I meant, I can tell you.

In practice I was able to have a GraphQL server which produced optimal downstream request waterfalls (i.e. to internal APIs) for every query that our app made in practice, and nearly every contrived query I could come up with.

So what I mean is that if you'd hand-written the code to handle those data requirements, in nearly all scenarios you wouldn't have been able to do better.

Does "optimal, nearly all the time" make you feel better?