1 of 24

GraphQL as a high-performance�cross-database query language

Predrag Gruevski �

2 of 24

Outline

  • Common GraphQL patterns
    • What works and what doesn’t

  • Where and why common patterns fail at scale
    • How breaking the GraphQL rules can help

  • Demo of the GraphQL compiler
    • GraphQL macros
    • Cross-database queries

3 of 24

Outline

  • Common GraphQL patterns
    • What works and what doesn’t

  • Why common patterns fail at scale
    • How breaking the GraphQL rules can help

  • Demo of the GraphQL compiler
    • GraphQL macros
    • Cross-database queries

4 of 24

GraphQL as most commonly used

  • GraphQL + SQL
    • Apollo + Sequelize
    • Graphene + SQLAlchemy

Product backend

SQL �query

GraphQL

Browser

Relational database

  • Pros:
    • Simple, easy to set up
  • Cons:
    • Needs lots of custom resolvers
    • Limited to simple (shallow) queries due to N+1 queries problem

5 of 24

N+1 queries problem

for data_id in get_ids_from_database():

get_data_from_database(data_id)

N + 1 = 1 query to look up N identifiers� + N queries to look up their data

6 of 24

N+1 queries solution: compile your GraphQL

for data_id in get_ids_from_database():

get_data_from_database(data_id)

N + 1 = 1 query to look up N identifiers� + N queries to look up their data

7 of 24

Avoiding the N+1 problem

  • GraphQL as the ORM
    • Hasura, Prisma, Postgraphile, Warthog, …

Product backend

SQL �query

GraphQL

Browser

Relational database

GraphQL data access layer

GraphQL

  • Pros:
    • Deeper GraphQL queries work
    • Autogenerated resolvers
  • Cons:
    • Limited database options (Postgres)
    • Schema bloat due to complex autogenerated input GraphQL types

8 of 24

Avoiding the N+1 problem

  • GraphQL as the ORM
    • Hasura, Prisma, Postgraphile, Warthog, …
  • Pros:
    • Deeper GraphQL queries work
    • Autogenerated resolvers
  • Cons:
    • Limited database options (Postgres)
    • Schema bloat due to complex autogenerated input GraphQL types

https://bit.ly/2kaaOQe

Operations on “createdAt”

Operations on “updatedAt”

Operations on “deletedAt”

Operations on “firstName”

9 of 24

Outline

  • Common GraphQL patterns
    • What works and what doesn’t

  • Why common patterns fail at scale
    • How breaking GraphQL rules can help

  • Demo of the GraphQL compiler
    • GraphQL macros
    • Cross-database queries

10 of 24

If your company’s architecture looks like this

Product backend

SQL �query

GraphQL

Browser

Relational database

then you have truly found paradise, enjoy it!

11 of 24

Paradise Lost: before long, it’ll turn into this

Product backends

Product backends

Graph databases

Timeseries databases

Document stores

Relational databases

ML model training

Analytics

Product backends

various graph �query languages

SQL

various timeseries �query languages

document store APIs

12 of 24

Paradise Regained: can we have this instead?

Product backends

Product backends

???

Graph databases

Timeseries databases

Document stores

Relational databases

ML model training

Analytics

Product backends

various graph �query languages

SQL

various timeseries �query languages

document store APIs

13 of 24

Paradise Regained: can we have this instead?

Product backends

Product backends

???

Graph databases

Timeseries databases

Document stores

Relational databases

ML model training

Analytics

Product backends

various graph �query languages

SQL

various timeseries �query languages

document store APIs

common �query interface

14 of 24

Paradise Regained: can we have this instead?

Product backends

Product backends

???

Graph databases

Timeseries databases

Document stores

Relational databases

ML model training

Analytics

Product backends

various graph �query languages

SQL

various timeseries �query languages

document store APIs

millions of lines of stitched schema

15 of 24

Paradise Regained: can we have this instead?

Product backends

Product backends

???

Graph databases

Timeseries databases

Document stores

Relational databases

ML model training

Analytics

Product backends

various graph �query languages

SQL

various timeseries �query languages

document store APIs

Product�domain

Infrastructure�domain

16 of 24

Paradise Regained: everyone is happy

  • Product / ML engineers focus on business logic

  • Infrastructure engineers handle underlying implementation

17 of 24

Paradise Regained: everyone is happy

  • Product / ML engineers focus on business logic
    • Database-agnostic: switching MySQL => Postgres has no visible impact
    • Fully declarative: ignorant of data locations, indexes, join order…
    • “Free” optimizations: no need to rewrite business logic as infra changes

  • Infrastructure engineers handle underlying implementation

18 of 24

Paradise Regained: everyone is happy

  • Product / ML engineers focus on business logic
    • Database-agnostic: switching MySQL => Postgres has no visible impact
    • Fully declarative: ignorant of data locations, indexes, join order…
    • “Free” optimizations: no need to rewrite business logic as infra changes

  • Infrastructure engineers handle underlying implementation
    • Free to choose the best database(s) for the job
    • Free to move and reshape data as necessary
    • Able to use advanced database features to speed up everyone’s workloads

19 of 24

Paradise Regained: GraphQL compiler

Product backends

Product backends

GraphQL compiler

Graph databases

Timeseries databases

Document stores

Relational databases

ML model training

Analytics

Product backends

various graph �query languages

SQL

various timeseries �query languages

document store APIs

GraphQL syntax, with more powerful semantics

millions of lines of auto-generated cross-db schema

20 of 24

Rules we have to break

  • Nested output format => rows and columns
    • Nesting: convenient for React, inconvenient for ML and analytics
    • Most databases output rows and columns – even graph databases!
    • GraphQL compiler returns rows and columns

21 of 24

Rules we have to break

  • Nested output format => rows and columns
    • Nesting: convenient for React, inconvenient for ML and analytics
    • Most databases output rows and columns – even graph databases!
    • GraphQL compiler returns rows and columns

  • Naming fields != outputting fields
    • Vanilla GraphQL: naming a field is asking for that field to be returned
    • GraphQL compiler: fields have to be explicitly marked for output,�allowing them to be operated on by directives without outputting them

22 of 24

Outline

  • Common GraphQL patterns
    • What works and what doesn’t

  • Why common patterns fail at scale
    • How breaking the GraphQL rules can help

  • Demo of the GraphQL compiler
    • GraphQL macros
    • Cross-database queries

23 of 24

interface GeographicArea {

name: String

uuid: ID

in_GeographicArea_SubArea: [GeographicArea]

out_GeographicArea_SubArea: [GeographicArea]

}

type Country implements GeographicArea {

alpha2: String

alpha3: String

name: String

uuid: ID

in_GeographicArea_SubArea: [GeographicArea]

out_GeographicArea_SubArea: [GeographicArea]

in_Airport_BasedIn: [Airport]

in_Airline_RegisteredIn: [Airline]

}

type Region implements GeographicArea {

name: String

uuid: ID

in_GeographicArea_SubArea: [GeographicArea]

out_GeographicArea_SubArea: [GeographicArea]

}

24 of 24

interface GeographicArea {

name: String

uuid: ID

in_GeographicArea_SubArea: [GeographicArea]

out_GeographicArea_SubArea: [GeographicArea]

}

type Country implements GeographicArea {

alpha2: String

alpha3: String

name: String

uuid: ID

in_GeographicArea_SubArea: [GeographicArea]

out_GeographicArea_SubArea: [GeographicArea]

in_Airport_BasedIn: [Airport]

in_Airline_RegisteredIn: [Airline]

}

type Region implements GeographicArea {

name: String

uuid: ID

in_GeographicArea_SubArea: [GeographicArea]

out_GeographicArea_SubArea: [GeographicArea]

}

type Airline {

alpha2_country: String

callsign: String

iata_code: String

icao_code: String

id: Int

name: String

in_FlightRoute_OperatingAirline: [FlightRoute]

out_Airline_RegisteredIn: [Country]

}

type Airport {

alpha2_country: String

city_served: String

elevation_ft: Int

iata_code: String

icao_code: String

id: Int

name: String

in_FlightRoute_FromAirport: [FlightRoute]

in_FlightRoute_ToAirport: [FlightRoute]

out_Airport_BasedIn: [Country]

}

type FlightRoute {

id: Int

stops: Int

out_FlightRoute_FromAirport: [Airport]

out_FlightRoute_OperatingAirline: [Airline]

out_FlightRoute_ToAirport: [Airport]

}