A realtime GraphQL backend as a compiler in Haskell - - PowerPoint PPT Presentation

a realtime graphql backend as a compiler in haskell
SMART_READER_LITE
LIVE PREVIEW

A realtime GraphQL backend as a compiler in Haskell - - PowerPoint PPT Presentation

A realtime GraphQL backend as a compiler in Haskell http://bit.ly/graphql-haskell Heippa! Tanmai Gopal @tanmaigo http://bit.ly/graphql-haskell Contents 1. The compiler approach 2. Compiler pipeline a. Parse b. GraphQL AST c.


slide-1
SLIDE 1

A realtime GraphQL backend as a compiler in Haskell

http://bit.ly/graphql-haskell

slide-2
SLIDE 2

Tanmai Gopal @tanmaigo Heippa!

http://bit.ly/graphql-haskell

slide-3
SLIDE 3

Contents

1. The compiler approach 2. Compiler pipeline

a. Parse b. GraphQL AST c. Transform to SQL AST <> inject auth d. Optimise SQL AST

3. Realtime (concurrent programming)

http://bit.ly/graphql-haskell

slide-4
SLIDE 4

Building a GraphQL backend

GraphQL queries come from app and need to be translated to data calls (with access control) to the database. Goal:

  • Observability for the team
  • High performance
  • Low footprint

Hasura

App

Database

slide-5
SLIDE 5

Resolver approach

Resolver based approach:

  • Parse GraphQL query to AST
  • Traverse the tree, call resolvers

Resolver can:

  • Make data calls (n+1) where n is the number of results
  • Make calls via dataloader O(n) where n is number of

nodes

If there are N authors with N articles each, you’re making 1 + n + n + n2 queries!

slide-6
SLIDE 6

Compiler approach

query { author { name address { city } articles { title tags { name } } } } SELECT author.name, address.city, articles.title, tag.name FROM author, address, articles, tags WHERE author.id = address.author_id, author.id = articles.author_id, articles.id = tag.article_id

slide-7
SLIDE 7

Approach advantages

  • Observability:
  • 1 SQL query to debug/analyse
  • Performance (on my laptop):
  • 2000 req/s on nested queries ~ 50MB RAM
  • 1500 records in 4ms
  • 400MB data select within 1-5% raw database call

joining tables containing 100M rows each

slide-8
SLIDE 8

Compiler pipeline

query { author { id name } }

ExecutableDefinition (OperationDefinition) OperationType: query SelectionSet: ... SelectionSet: [Selection] Selection: Field: Name: author SelectionSet: [Selection] Selection: Field: Name: id SelectionSet: [] Selection: Field: Name: name SelectionSet: []

SELECT id, name FROM author WHERE id = <session.user-id>

parse AST SQL

slide-9
SLIDE 9

Representing the GraphQL AST with Haskell ADTs

Algebraic Data Types

slide-10
SLIDE 10

Representing the GraphQL AST with Haskell ADTs

slide-11
SLIDE 11

Parsing by hand

query { author { id name } }

1. Lex into “tokens” (words and braces) 2. Read the first word 3. This should be an ExecutableDefinition. 4. Check if this is one of the operation types 5. If yes, the rest of the document is a SelectionSet 6. In a SelectionSet, we need to assemble an array of fields 7. ...

slide-12
SLIDE 12

Parsing with parser combinators

query { author { id name } }

ExecutableDefinition (OperationDefinition)

The Magic Try to parse an operationDefinition or parse a fragmentDefinition.

slide-13
SLIDE 13

Try to parse an operationDefinition by 1. Parsing an operationType 2. Optionally parsing a Name 3. Optionally parsing variables 4. Optionally parsing directives 5. Parsing a selectionSet

Try to parse an operationType by: 1. Parsing a “query” token OR 2. Parsing a “mutation” token OR 3. Parsing a “subscription” token

slide-14
SLIDE 14

Transforming the GraphQL AST to SQL AST

ExecutableDefinition (OperationDefinition) OperationType: query SelectionSet: ...

SELECT id, name FROM author WHERE id = <session.user-id>

SelectionSet: [Selection] Selection: Field: Name: author SelectionSet: [Selection] Selection: Field: Name: id SelectionSet: [] Selection: Field: Name: name SelectionSet: []

slide-15
SLIDE 15

Handling realtime (GraphQL subscriptions)

slide-16
SLIDE 16

Create a connection object + thread for every new connection

Conn1

(+Delivery thread)

Conn2

(+Delivery thread)

Conn3

(+Delivery thread)

...

Event sourcing threads

Thread1 Thread2 Thread3 ...

Database

App App App App App App App App

1. Create a new connection object and attach to the right event-source thread or create a new thread 2. In every thread, take an event and deliver to all relevant connection

  • bjects

3. In case GraphQL schema or auth config changes, close/refresh all connections

slide-17
SLIDE 17

Concurrent programming

slide-18
SLIDE 18

Locks are bad

Define a thread-safe operation:

  • pop from event queue (lock1)

Define a thread-safe operation:

  • push to connection object queue (lock2)

Compose:

  • Pop (lock1)
  • Push (lock2)

The composition is not safe! Option 1: Lock1 && Lock2

  • Pop
  • Push

Release Option 2: Acquire in sequence: Lock1 then Lock2

  • Pop
  • Push

Release Thread1 Conn1

(+Delivery thread)

Conn2

(+Delivery thread)

slide-19
SLIDE 19

Concurrent programming in Haskell with STM

If pop and push are safe actions, then compose them safely atomically $ do pop push Software transactional memory is the idea

  • f database transactions:
  • In memory
  • Across threads
slide-20
SLIDE 20

Summary:

Writing compilers is easy:

  • Parser combinators + ADTs

https://github.com/Gabriel439/post-rfc/blob/master/sotu.md#compilers Doing concurrent programming is easy:

  • Software Transactional Memory (STMs)

https://github.com/Gabriel439/post-rfc/blob/master/sotu.md#single-machine-concurrency

slide-21
SLIDE 21

Hasura

hasura.io github.com/hasura/graphql-engine @HasuraHQ Talk to me about: Haskell Postgres GraphQL Subscriptions Serverless https://3factor.app