| # From zero to Go: launching on the Google homepage in 24 hours |
| 13 Dec 2011 |
| Tags: appengine, google, guest |
| Summary: How Go helped launch the Google Doodle for Thanksgiving 2011. |
| OldURL: /from-zero-to-go-launching-on-google |
| |
| Reinaldo Aguiar |
| |
| ## Introduction |
| |
| _This article was written by Reinaldo Aguiar, a software engineer from the Search team at Google. He shares his experience developing his first Go program and launching it to an audience of millions - all in one day!_ |
| |
| I was recently given the opportunity to collaborate on a small but highly |
| visible "20% project": |
| the [Thanksgiving 2011 Google Doodle](http://www.google.com/logos/2011/thanksgiving.html). |
| The doodle features a turkey produced by randomly combining different styles of head, |
| wings, feathers and legs. |
| The user can customize it by clicking on the different parts of the turkey. |
| This interactivity is implemented in the browser by a combination of JavaScript, |
| CSS and of course HTML, creating turkeys on the fly. |
| |
| .image turkey-doodle/image00.png |
| |
| Once the user has created a personalized turkey it can be shared with friends |
| and family by posting to Google+. |
| Clicking a "Share" button (not pictured here) creates in the user's Google+ |
| stream a post containing a snapshot of the turkey. |
| The snapshot is a single image that matches the turkey the user created. |
| |
| With 13 alternatives for each of 8 parts of the turkey (heads, |
| pairs of legs, distinct feathers, etc.) there are more than than 800 million |
| possible snapshot images that could be generated. |
| To pre-compute them all is clearly infeasible. |
| Instead, we must generate the snapshots on the fly. |
| Combining that problem with a need for immediate scalability and high availability, |
| the choice of platform is obvious: Google App Engine! |
| |
| The next thing we needed to decide was which App Engine runtime to use. |
| Image manipulation tasks are CPU-bound, so performance is the deciding factor in this case. |
| |
| To make an informed decision we ran a test. |
| We quickly prepared a couple of equivalent demo apps for the new [Python 2.7 runtime](http://code.google.com/appengine/docs/python/python27/newin27.html) |
| (which provides [PIL](http://www.pythonware.com/products/pil/), |
| a C-based imaging library) and the Go runtime. |
| Each app generates an image composed of several small images, |
| encodes the image as a JPEG, and sends the JPEG data as the HTTP response. |
| The Python 2.7 app served requests with a median latency of 65 milliseconds, |
| while the Go app ran with a median latency of just 32 milliseconds. |
| |
| This problem therefore seemed the perfect opportunity to try the experimental Go runtime. |
| |
| I had no previous experience with Go and the timeline was tight: |
| two days to be production ready. |
| This was intimidating, but I saw it as an opportunity to test Go from a different, |
| often overlooked angle: |
| development velocity. |
| How fast can a person with no Go experience pick it up and build something |
| that performs and scales? |
| |
| ## Design |
| |
| The approach was to encode the state of the turkey in the URL, drawing and encoding the snapshot on the fly. |
| |
| The base for every doodle is the background: |
| |
| .image turkey-doodle/image01.jpg |
| |
| A valid request URL might look like this: |
| `http://google-turkey.appspot.com/thumb/20332620][http://google-turkey.appspot.com/thumb/20332620` |
| |
| The alphanumeric string that follows "/thumb/" indicates (in hexadecimal) |
| which choice to draw for each layout element, |
| as illustrated by this image: |
| |
| .image turkey-doodle/image03.png |
| |
| The program's request handler parses the URL to determine which element |
| is selected for each component, |
| draws the appropriate images on top of the background image, |
| and serves the result as a JPEG. |
| |
| If an error occurs, a default image is served. |
| There's no point serving an error page because the user will never see it - |
| the browser is almost certainly loading this URL into an image tag. |
| |
| ## Implementation |
| |
| In the package scope we declare some data structures to describe the elements of the turkey, |
| the location of the corresponding images, |
| and where they should be drawn on the background image. |
| |
| var ( |
| // dirs maps each layout element to its location on disk. |
| dirs = map[string]string{ |
| "h": "img/heads", |
| "b": "img/eyes_beak", |
| "i": "img/index_feathers", |
| "m": "img/middle_feathers", |
| "r": "img/ring_feathers", |
| "p": "img/pinky_feathers", |
| "f": "img/feet", |
| "w": "img/wing", |
| } |
| |
| // urlMap maps each URL character position to |
| // its corresponding layout element. |
| urlMap = [...]string{"b", "h", "i", "m", "r", "p", "f", "w"} |
| |
| // layoutMap maps each layout element to its position |
| // on the background image. |
| layoutMap = map[string]image.Rectangle{ |
| "h": {image.Pt(109, 50), image.Pt(166, 152)}, |
| "i": {image.Pt(136, 21), image.Pt(180, 131)}, |
| "m": {image.Pt(159, 7), image.Pt(201, 126)}, |
| "r": {image.Pt(188, 20), image.Pt(230, 125)}, |
| "p": {image.Pt(216, 48), image.Pt(258, 134)}, |
| "f": {image.Pt(155, 176), image.Pt(243, 213)}, |
| "w": {image.Pt(169, 118), image.Pt(250, 197)}, |
| "b": {image.Pt(105, 104), image.Pt(145, 148)}, |
| } |
| ) |
| |
| The geometry of the points above was calculated by measuring the actual |
| location and size of each layout element within the image. |
| |
| Loading the images from disk on each request would be wasteful repetition, |
| so we load all 106 images (13 \* 8 elements + 1 background + 1 default) into |
| global variables upon receipt of the first request. |
| |
| var ( |
| // elements maps each layout element to its images. |
| elements = make(map[string][]*image.RGBA) |
| |
| // backgroundImage contains the background image data. |
| backgroundImage *image.RGBA |
| |
| // defaultImage is the image that is served if an error occurs. |
| defaultImage *image.RGBA |
| |
| // loadOnce is used to call the load function only on the first request. |
| loadOnce sync.Once |
| ) |
| |
| // load reads the various PNG images from disk and stores them in their |
| // corresponding global variables. |
| func load() { |
| defaultImage = loadPNG(defaultImageFile) |
| backgroundImage = loadPNG(backgroundImageFile) |
| for dirKey, dir := range dirs { |
| paths, err := filepath.Glob(dir + "/*.png") |
| if err != nil { |
| panic(err) |
| } |
| for _, p := range paths { |
| elements[dirKey] = append(elements[dirKey], loadPNG(p)) |
| } |
| } |
| } |
| |
| Requests are handled in a straightforward sequence: |
| |
| - Parse the request URL, decoding the decimal value of each character in the path. |
| |
| - Make a copy of the background image as the base for the final image. |
| |
| - Draw each image element onto the background image using the layoutMap to determine where they should be drawn. |
| |
| - Encode the image as a JPEG |
| |
| - Return the image to user by writing the JPEG directly to the HTTP response writer. |
| |
| Should any error occur, we serve the defaultImage to the user and log the |
| error to the App Engine dashboard for later analysis. |
| |
| Here's the code for the request handler with explanatory comments: |
| |
| func handler(w http.ResponseWriter, r *http.Request) { |
| // [[https://blog.golang.org/2010/08/defer-panic-and-recover.html][Defer]] a function to recover from any panics. |
| // When recovering from a panic, log the error condition to |
| // the App Engine dashboard and send the default image to the user. |
| defer func() { |
| if err := recover(); err != nil { |
| c := appengine.NewContext(r) |
| c.Errorf("%s", err) |
| c.Errorf("%s", "Traceback: %s", r.RawURL) |
| if defaultImage != nil { |
| w.Header().Set("Content-type", "image/jpeg") |
| jpeg.Encode(w, defaultImage, &imageQuality) |
| } |
| } |
| }() |
| |
| // Load images from disk on the first request. |
| loadOnce.Do(load) |
| |
| // Make a copy of the background to draw into. |
| bgRect := backgroundImage.Bounds() |
| m := image.NewRGBA(bgRect.Dx(), bgRect.Dy()) |
| draw.Draw(m, m.Bounds(), backgroundImage, image.ZP, draw.Over) |
| |
| // Process each character of the request string. |
| code := strings.ToLower(r.URL.Path[len(prefix):]) |
| for i, p := range code { |
| // Decode hex character p in place. |
| if p < 'a' { |
| // it's a digit |
| p = p - '0' |
| } else { |
| // it's a letter |
| p = p - 'a' + 10 |
| } |
| |
| t := urlMap[i] // element type by index |
| em := elements[t] // element images by type |
| if p >= len(em) { |
| panic(fmt.Sprintf("element index out of range %s: "+ |
| "%d >= %d", t, p, len(em))) |
| } |
| |
| // Draw the element to m, |
| // using the layoutMap to specify its position. |
| draw.Draw(m, layoutMap[t], em[p], image.ZP, draw.Over) |
| } |
| |
| // Encode JPEG image and write it as the response. |
| w.Header().Set("Content-type", "image/jpeg") |
| w.Header().Set("Cache-control", "public, max-age=259200") |
| jpeg.Encode(w, m, &imageQuality) |
| } |
| |
| For brevity, I've omitted several helper functions from these code listings. |
| See the [source code](http://code.google.com/p/go-thanksgiving/source/browse/) for the full scoop. |
| |
| ## Performance |
| |
| .image turkey-doodle/image02.png |
| |
| This chart - taken directly from the App Engine dashboard - shows average |
| request latency during launch. |
| As you can see, even under load it never exceeds 60 ms, |
| with a median latency of 32 milliseconds. |
| This is wicked fast, considering that our request handler is doing image |
| manipulation and encoding on the fly. |
| |
| ## Conclusions |
| |
| I found Go's syntax to be intuitive, simple and clean. |
| I have worked a lot with interpreted languages in the past, |
| and although Go is instead a statically typed and compiled language, |
| writing this app felt more like working with a dynamic, |
| interpreted language. |
| |
| The development server provided with the [SDK](http://code.google.com/appengine/downloads.html#Google_App_Engine_SDK_for_Go) |
| quickly recompiles the program after any change, |
| so I could iterate as fast as I would with an interpreted language. |
| It's dead simple, too - it took less than a minute to set up my development environment. |
| |
| Go's great documentation also helped me put this together fast. |
| The docs are generated from the source code, |
| so each function's documentation links directly to the associated source code. |
| This not only allows the developer to understand very quickly what a particular |
| function does but also encourages the developer to dig into the package implementation, |
| making it easier to learn good style and conventions. |
| |
| In writing this application I used just three resources: |
| App Engine's [Hello World Go example](http://code.google.com/appengine/docs/go/gettingstarted/helloworld.html), |
| [the Go packages documentation](https://golang.org/pkg/), |
| and [a blog post showcasing the Draw package](https://blog.golang.org/2011/09/go-imagedraw-package.html). |
| Thanks to the rapid iteration made possible by the development server and |
| the language itself, |
| I was able to pick up the language and build a super fast, |
| production ready, doodle generator in less than 24 hours. |
| |
| Download the full app source code (including images) at [the Google Code project](http://code.google.com/p/go-thanksgiving/source/browse/). |
| |
| Special thanks go to Guillermo Real and Ryan Germick who designed the doodle. |