High Performance Apps with Go on App Engine

Google I/O, May 2013

David Symonds

Software Engineer, Google

Video

This talk was presented at Google I/O in May 2013.

Overview

Why Go + App Engine

History

Turkey doodle (Nov 2011)

golang.org/s/turkey-doodle

Santa Tracker (Dec 2012)

Gopher Mart

Gopher Mart

Gopher Mart

Gopher Mart

Finding performance bottlenecks

Appstats

Performance Techniques

Defer work

Defer work II

Import "appengine/delay" and transform

    sendReceipt(c, user.Current(c).Email, b.String())
func sendReceipt(c appengine.Context, dst, body string) {

into

    sendReceipt.Call(c, user.Current(c).Email, b.String())
var sendReceipt = delay.Func("send-receipt", func(c appengine.Context, dst, body string) {

Batching

    var items []*Item
    for _, key := range keys {
        item := new(Item)
        if err := datastore.Get(c, key, item); err != nil {
            // ...
        }
        items = append(items, item)
    }

Batching II

    items := make([]Item, len(keys))
    if err := datastore.GetMulti(c, keys, items); err != nil {
        // ...
    }

Caching

Small reads
datastore.Get O(20ms)
memcache.Get O(1ms)
RAM O(1┬Ás)

Concurrency

Concurrency II

    var lists []List
    var items []Item
    _, err := datastore.NewQuery("List").GetAll(c, &lists)
    if err != nil { /* ... */ }
    _, err := datastore.NewQuery("Item").GetAll(c, &items)
    if err != nil { /* ... */ }
    // write response

Concurrency III

    var lists []List
    var items []Item
    errc := make(chan error)
    go func() {
        _, err := datastore.NewQuery("List").GetAll(c, &lists)
        errc <- err
    }()
    go func() {
        _, err := datastore.NewQuery("Item").GetAll(c, &items)
        errc <- err
    }()
    err1, err2 := <-errc, <-errc
    if err1 != nil || err2 != nil { /* ... */ }
    // write response

Control variance

Control variance II

"The Tail at Scale", Dean, Barroso; Commun. ACM 56, 2

Control variance III

func myHandler(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    // ...
    // regular request handling
    // ...

    go memcache.Set(c, &memcache.Item{
        Key:   key,
        Value: data,
    })
}

Control variance IV

func myHandler(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    // ...
    // regular request handling
    // ...

    // Save to memcache, but only wait up to 3ms.
    done := make(chan bool, 1) // NB: buffered
    go func() {
        memcache.Set(c, &memcache.Item{
            Key:   key,
            Value: data,
        })
        done <- true
    }()
    select {
    case <-done:
    case <-time.After(3 * time.Millisecond):
    }
}

Before and After

Baseline:

Defer work:

Batching:

Summary

Finally...

More Go things:

Thank you

David Symonds

Software Engineer, Google