Anthony Giniers
@antogyn
@aginiers
GraphQL at Scale
https://slides.com/antogyn/graphql-at-scale
Staff Engineer @ Swan
ou GraphQL dans la vraie vie
GraphQL est un langage de requête pour des données de type graphe
Alternative à REST
Une requête GraphQL est une liste hiérarchique de champs
{
user(id: 123) {
id
name
account {
IBAN
}
}
}
{
"user": {
"id": 123,
"name": "Anthony Giniers",
"account": {
"IBAN": "NL93RABO6684756000"
}
}
}
→ Les données ont la même forme que la requête
→ Elles sont accessibles en une seule requête
type Query {
user(id: Int): User
}
type User {
id: Int!
name: String
account: Account
}
type Account {
IBAN: String
}
{
user(id: 123) {
id
name
account {
IBAN
}
}
}
Les spécifications sont encodées dans le client
{
user(id: 123) {
id
name
account {
IBAN
}
}
}
{
"__schema": {
"types": [
{
"name": "User",
"fields": [
{
"name": "id",
"type": {
"name": "Int"
}
},
{
"name": "name",
"type": {
"name": "String"
}
},
{
"name": "account",
"type": {
"name": "Account"
}
}
]
}
...
]
}
}
type User {
id: Int
name: String
account: Account
}
{
__schema {
types {
name
fields {
name
type {
name
}
}
}
}
}
https://principledgraphql.com/
1. One Graph
REST
Service
GraphQL Gateway
GraphQL
Service
MAJ
Double travail
Complexité des releases
Ownership: qui met quoi à jour ?
User
Service
GraphQL Gateway
Account
Service
User
Service
GraphQL Gateway
Account
Service
User
Service
GraphQL Gateway
Account
Service
type User {
id: String
name: String
}
User
Service
GraphQL Gateway
Account
Service
type Account {
id: String
balance: Number
user: User
}
type User {
id: String
name: String
}
User
Service
GraphQL Gateway
Account
Service
type Account {
id: String
balance: Number
user: User
}
type User {
id: String
name: String
}
User
Service
GraphQL Gateway
Account
Service
type Account {
id: String
balance: Number
user: User
}
extend type User {
accounts: [Account]
}
type User {
id: String
name: String
}
User
Service
GraphQL Gateway
Account
Service
type Account {
id: String
balance: Number
user: User
}
type User {
id: String
name: String
}
extend type User {
accounts: [Account]
}
User
Service
GraphQL Gateway
Account
Service
type Account {
id: String
balance: Number
user: User
}
extend type User {
accounts: [Account]
}
type User {
id: String
name: String
}
User
Service
GraphQL Gateway
Account
Service
+ GraphQL Stitching
https://principledgraphql.com/
2. Federated implementation
User Subgraph
Payment Subgraph
Account
Subgraph
Federation Gateway
User Subgraph
Payment Subgraph
Account
Subgraph
No code
Federation Gateway
Federation Gateway
User Subgraph
Payment Subgraph
Account
Subgraph
Respectent une spec
Federation Gateway
User Subgraph
Payment Subgraph
Account
Subgraph
Respectent une spec
Sont "composables"
type Account {
id: String
balance: Number
user: User
}
type User {
id: String
name: String
}
User Subgraph
Account
Subgraph
type Account @key(fields: "id") {
id: String
balance: Number
user: User
}
type User @key(fields: "id") {
id: String
name: String
}
User Subgraph
Account
Subgraph
Federation Gateway
query {
user {
id
account {
balance
}
}
}
QueryPlan {
Sequence {
Fetch(service: "User") {
{
user {
id
}
}
},
Flatten(path: "user") {
Fetch(service: "Account") {
{
... on User {
id
}
} =>
{
... on User {
account {
balance
}
}
}
},
},
},
}
The industry standard platform for GraphQL federation
Federated architecture for any API service
The Open-Source GraphQL Federation Solution
The GraphQL Federation platform
https://principledgraphql.com/
3. Track the Schema in a Registry
GraphQL Gateway
User Subgraph
Account
Subgraph
type Account {
user: User
}
type User {
id: String
}
User ?
GraphQL Gateway
User.id ?
User Subgraph
Account
Subgraph
type User {
id: Int
}
type User {
id: String
}
User Subgraph
Payment Subgraph
Account
Subgraph
GraphQL Registry
GraphQL Gateway
Composition
User Subgraph
Payment Subgraph
Account
Subgraph
Git
GraphQL Registry
GraphQL Gateway
https://principledgraphql.com/
4. Abstract, Demand-Oriented Schema
User Subgraph
type User {
first_name: String
}
Account
Subgraph
type Account {
legalRepresentative: User
}
query {
account {
legalRepresentative {
first_name
}
}
}
https://principledgraphql.com/
5. Use an Agile Approach to Schema Development
{
user {
id
name
}
}
type User {
id: Int
name: String @deprecated
identity: Identity
}
type Identity {
name: String
}
{
user {
id
identity {
name
}
}
}
type User {
id: Int
name: String
}
https://principledgraphql.com/
6. Iteratively Improve Performance
Federation Gateway
query {
user {
id
account {
balance
}
}
}
QueryPlan {
Sequence {
Fetch(service: "User") {
{
user {
id
}
}
},
Flatten(path: "user") {
Fetch(service: "Account") {
{
... on User {
id
}
} =>
{
... on User {
account {
balance
}
}
}
},
},
},
}
Apollo Gateway => Apollo Router
Réécrit en Rust
Mesh => Conductor
Cosmo (Wundergraph)
Réécrit en Rust
Ecrit en Go
Query plan caching
type Query {
accounts: [Account]
}
type Account {
lastPayment: Payment
}
type Payment {
amount: Int!
}
query {
accounts { # N accounts => 1 requête
lastPayment { # 1 payment => 1 + N requêtes
amount
}
}
}
type Query {
accounts: [Account]
}
type Account {
lastPayment: Payment
}
type Payment {
amount: Int!
}
query {
accounts { # N accounts => 1 requête
lastPayment { # 1 payment => 1 + N requêtes
amount
}
}
}
type Query {
accounts: [Account]
}
type Account {
lastPayment: Payment
}
type Payment {
amount: Int!
}
query {
accounts { # N accounts => 1 requête
lastPayment { # 1 payment => 1 + N requêtes
amount
}
}
}
type Query {
accounts: [Account]
}
type Account {
lastPayment: Payment
}
type Payment {
amount: Int!
}
query {
accounts { # N accounts => 1 requête
lastPayment { # 1 payment => 1 + N requêtes
amount
}
}
}
Solutions: spécifiques à chaque language/platforme
graphql/dataloader
pour Node.js java-dataloader
pour Java
Mais est-ce vraiment un problème ?
type Query {
accounts: [Account]
}
type Account {
lastPayment: Payment
}
type Payment {
amount: Int!
}
... et on a N+1 requêtes
En REST:
query {
accounts { # N accounts => 1 requête
lastPayment { # 1 payment => 1 + N requêtes
amount
}
}
}
type User {
id: String
name: String
accounts: [Account]
}
Implémentation naïve: systématiquement récupérer les accounts
d'un User
query {
user {
id
name
}
}
query {
user {
id
name
firstName
language
birthDate
accounts {
id
name
iban
balance
virtualIbans
memberships {
permissions
user {
id
name
firstName
}
}
cards {
id
}
transactions {
id
amount {
value
currency
}
reference
type
debtor {
id
name
firstName
}
creditor {
id
name
firstName
accountNumber
}
}
}
}
}
Federation Gateway
query {
user {
id
name
firstName
language
birthDate
accounts {
id
name
iban
balance
virtualIbans
memberships {
permissions
...
1
query
Federation Gateway
SHA256(query)
2
query
Federation Gateway
query {
user {
id
name
firstName
language
birthDate
accounts {
id
name
iban
balance
virtualIbans
memberships {
permissions
...
1
register
Federation Gateway
2
ok
id
Federation Gateway
3
query
id
https://principledgraphql.com/
7. Use GraphQL Metadata to Empower Developers
@deprecated
Avoir des outils qui profitent le plus possible des informations du graphehttps://principledgraphql.com/
8. Access and Demand Control
En amont, dans une autre API Gateway
Dans la GraphQL Federation Gateway
Dans chaque subgraph
type Query {
users: [User] @requiresScopes(scopes: [["read:users"]])
}
type Mutation {
updateUser(input: UpdateUserInput!): User @authenticated
}
Au niveau du schéma (+ Federation Gateway)
Au niveau des subgraphs
type Query {
user: User
}
type User {
id: String
friends(limit: Int!): [User]
}
query {
user {
friends(limit: 100) {
friends(limit: 100) {
friends(limit: 100) {
friends(limit: 100) {
friends(limit: 100) {
...
}
}
}
}
}
}
}
query {
user {
friends(limit: 100) {
friends(limit: 100) {
friends(limit: 100) {
friends(limit: 100) {
friends(limit: 100) {
...
}
}
}
}
}
}
}
😭
query {
user {
friends(limit: 100) {
friends(limit: 100) {
friends(limit: 100) {
id
}
}
}
friends(limit: 100) {
friends(limit: 100) {
friends(limit: 100) {
id
}
}
}
friends(limit: 100) {
friends(limit: 100) {
friends(limit: 100) {
id
}
}
}
friends(limit: 100) {
friends(limit: 100) {
friends(limit: 100) {
id
}
}
}
...
}
}
type Query {
user: User @cost(complexity: 5)
}
type User {
id: String
friends(limit: Int!): [User] @cost(complexity: 3, multipliers: ["limit"])
}
type Query {
user: User @cost(complexity: 5)
}
type User {
id: String
friends(limit: Int!): [User] @cost(complexity: 3, multipliers: ["limit"])
}
query {
user { # 5
friends(limit: 10) { # 35 = 5 + 3*10
friends(limit: 10) { # 335 = 5 + 3*10 + 3*10*10
friends(limit: 10) { # 3335 = 5 + 3*10 + 3*10*10 + 3*10*10*10
id
}
}
}
...
}
}
type Query {
user: User @cost(complexity: 5)
}
type User {
id: String
friends(limit: Int!): [User] @cost(complexity: 3, multipliers: ["limit"])
}
query {
user { # 5
friends(limit: 10) { # 35 = 5 + 3*10
friends(limit: 10) { # 335 = 5 + 3*10 + 3*10*10
friends(limit: 10) { # 3335 = 5 + 3*10 + 3*10*10 + 3*10*10*10
id
}
}
}
...
}
}
type Query {
user: User @cost(complexity: 5)
}
type User {
id: String
friends(limit: Int!): [User] @cost(complexity: 3, multipliers: ["limit"])
}
query {
user { # 5
friends(limit: 10) { # 35 = 5 + 3*10
friends(limit: 10) { # 335 = 5 + 3*10 + 3*10*10
friends(limit: 10) { # 3335 = 5 + 3*10 + 3*10*10 + 3*10*10*10
id
}
}
}
...
}
}
type Query {
user: User @cost(complexity: 5)
}
type User {
id: String
friends(limit: Int!): [User] @cost(complexity: 3, multipliers: ["limit"])
}
query {
user { # 5
friends(limit: 10) { # 35 = 5 + 3*10
friends(limit: 10) { # 335 = 5 + 3*10 + 3*10*10
friends(limit: 10) { # 3335 = 5 + 3*10 + 3*10*10 + 3*10*10*10
id
}
}
}
...
}
}
type Query {
user: User @cost(complexity: 5)
}
type User {
id: String
friends(limit: Int!): [User] @cost(complexity: 3, multipliers: ["limit"])
}
query {
user { # 5
friends(limit: 10) { # 35 = 5 + 3*10
friends(limit: 10) { # 335 = 5 + 3*10 + 3*10*10
friends(limit: 10) { # 3335 = 5 + 3*10 + 3*10*10 + 3*10*10*10
id
}
}
}
...
}
}
type Query {
user: User @cost(complexity: 5)
}
type User {
id: String
friends(limit: Int!): [User] @cost(complexity: 3, multipliers: ["limit"])
}
query {
user { # 5
friends(limit: 10) { # 35 = 5 + 3*10
friends(limit: 10) { # 335 = 5 + 3*10 + 3*10*10
friends(limit: 10) { # 3335 = 5 + 3*10 + 3*10*10 + 3*10*10*10
id
}
}
}
...
}
}
😭
query {
user {
friends(limit: 15) {
friends(limit: 10) {
id
}
}
}
}
... en boucle
X req/s
X coût de complexité/min
https://principledgraphql.com/
9. Structured Logging
95% du travail en une métrique :
temps de réponse + error / resolver
Lier les métriques aux traces
Rien à faire sur la Federation Gateway
Bien avoir la propagation sur les subgraphs
Graphql Gateway
Subgraph 1
Subgraph 2
Lier les logs aux traces
Facultatif: 1 log / resolver
https://principledgraphql.com/
10. Separate the GraphQL Layer from the Service Layer
🚀