1 of 49

From Python To Go:�How Khan Academy is saving time and money

With your host, Steve Coffman

2 of 49

What is Khan Academy?

3 of 49

4 of 49

Who am I?

(not a brief philosophical interlude)

5 of 49

I love my job.�I love my co-workers.

I love what we do together.

6 of 49

Where did we start? �GCP App Engine �Python 2 Monolith

7 of 49

Where did we start? �GCP App Engine �Python 2 Monolith

This didn’t change. It’s awesome!

8 of 49

Where are we going?

GCP App Engine

Multiple Go Services (27)

GraphQL Between

9 of 49

Where are we going?

GCP App Engine

Multiple Go Services (27)

GraphQL Between

This didn’t change. It’s awesome!

10 of 49

EOL for Python 2 forced us to choose...

11 of 49

We Chose Go

  • Fast (which translates to cheaper)
  • Rich Ecosystem (and standard lib)
  • Simple and Productive
  • Maintaining someone else's code is a pleasure in Golang

12 of 49

What platform should we run on?

13 of 49

We Chose AppEngine (again)

  • Avoid undifferentiated heavy lifting
  • Scales Great! (e.g. covid-19)
  • Reliable as heck (all-volunteer pager duty)

14 of 49

Stick with Monolith or switch to services?

15 of 49

We Chose Services (27)

  • Human Scaling as We Grew
  • Independent Deployability
  • Granular Cost Tracking Aligned to Value

16 of 49

How do services communicate between each other?

17 of 49

We Chose GraphQL

  • Great Success with React + GraphQL on Frontend
  • AppEngine ≠ HTTP/2 (gRPC) (yet)
  • Ubiquitous Language Across Go/JavaScript Divide
  • Schema Driven, Code Generated

18 of 49

How should we divide up into services?

19 of 49

Meet (some of) The 27 Services

user

districts

users

assignments

coaches

progress

render-server

analytics

content-library

content

Cloud Storage

graphql-gateway

20 of 49

How have we learned and transitioned to Go?

21 of 49

Learning Go at Khan

22 of 49

Ways to Safely Transition

  • Traffic Shadowing
  • Github Scientist
  • Side-by-Side Testing
    • Using GraphQL Federation!

23 of 49

Side-by-Side Transition at Khan

  1. Python monolith
  2. Develop in a Go service
  3. Python primary, Go secondary
  4. Go primary, Python secondary
  5. Go only

24 of 49

Side-by-Side

  • Python monolith
  • Develop in a Go service
  • Python primary, Go secondary
  • Go primary, Python secondary
  • Go only

25 of 49

Side-by-Side

  • Python monolith
  • Develop in a Go service
  • Python primary, Go secondary
  • Go primary, Python secondary
  • Go only

26 of 49

Side-by-Side

  • Python monolith
  • Develop in a Go service
  • Python primary, Go secondary
  • Go primary, Python secondary
  • Go only

27 of 49

Side-by-Side

  • Python monolith
  • Develop in a Go service
  • Python primary, Go secondary
  • Go primary, Python secondary
  • Go only

28 of 49

Side-by-Side

  • Python monolith
  • Develop in a Go service
  • Python primary, Go secondary
  • Go primary, Python secondary
  • Go only

29 of 49

It’s code time!

30 of 49

GraphQL Schema Changes

Step 1: Python and beginning of Go

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time�}

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time @migrate(from: "python", state: "manual")

}

31 of 49

GraphQL Schema Changes

Step 2: Python and Go Side-by-Side

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time @migrate(from: "python", state: "manual")

}

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time @migrate(from: "python", state: "side-by-side")

}

32 of 49

Side-by-Side Differences

33 of 49

Side-by-Side Differences

34 of 49

Side-by-Side Differences

35 of 49

GraphQL Schema Changes

Step 3: Go serving, Python ignored

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time @migrate(from: "python", state: "side-by-side")

}

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time @migrate(from: "python", state: "migrated")

}

36 of 49

Side-by-Side GraphQL Schema Changes

Step 4: Go only, Python deleted

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time @migrate(from: "python", state: "migrated")

}

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time

}

37 of 49

Side-by-Side Transition at Khan

  • Python monolith
  • Develop in a Go service
  • Python primary, Go secondary
  • Go primary, Python secondary
  • Go only

38 of 49

What about Mutations?

  • Altering persistent data Side-by-side testing is a poor fit
  • We need a safe, gradual traffic shift Ideally, internal traffic first

39 of 49

Blue/Green

Release a new version alongside the old version, then switch traffic.

40 of 49

Side-By-Side (Traffic Shadowing)

Release a new version alongside the old version.

Incoming traffic is mirrored to the new version and doesn't impact the response.

41 of 49

Canary

Release a new version to a subset of users, then proceed to a full rollout.

42 of 49

Canaries and You

43 of 49

Canaries and YOU

  • Selective release of a feature to a random subset of requests, for testing purposes
  • Gradually increase traffic from 0 to 100%
  • More than 0%, all ‘internal’ users will get the canary treatment

44 of 49

GraphQL Schema Changes

Step 1: Python and beginning of Go

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time�}

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time @migrate(from: "python", state: "canary", color: "red")

}

45 of 49

Canary GraphQL Schema Changes

Step 2: Go only, Python deleted

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time @migrate(from: "python", state: "canary", color: "red")

}

extend type Assignment @key(fields: "id") {

id: ID! @external

createdDate: Time

}

46 of 49

Sooo…

  • Engineers are happy and productive
  • Efficiency went up
  • Costs bending down

47 of 49

What’s Next

  • 65% of production traffic served by Go
  • Open Source by default
  • Sharing our onboarding process

48 of 49

49 of 49

49

Thanks! Any questions?