github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/blog/content/from-zero-to-go-launching-on-google.article (about) 1 From zero to Go: launching on the Google homepage in 24 hours 2 13 Dec 2011 3 Tags: appengine, google, guest 4 5 Reinaldo Aguiar 6 7 * Introduction 8 9 _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!_ 10 11 I was recently given the opportunity to collaborate on a small but highly visible "20% project": the [[http://www.google.com/logos/2011/thanksgiving.html][Thanksgiving 2011 Google Doodle]]. 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. 12 13 .image from-zero-to-go-launching-on-google_image00.png 14 15 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. 16 17 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! 18 19 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. 20 21 To make an informed decision we ran a test. We quickly prepared a couple of equivalent demo apps for the new [[http://code.google.com/appengine/docs/python/python27/newin27.html][Python 2.7 runtime]] (which provides [[http://www.pythonware.com/products/pil/][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. 22 23 This problem therefore seemed the perfect opportunity to try the experimental Go runtime. 24 25 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? 26 27 * Design 28 29 The approach was to encode the state of the turkey in the URL, drawing and encoding the snapshot on the fly. 30 31 The base for every doodle is the background: 32 33 .image from-zero-to-go-launching-on-google_image01.jpg 34 35 A valid request URL might look like this: [[http://google-turkey.appspot.com/thumb/20332620][http://google-turkey.appspot.com/thumb/20332620]] 36 37 The alphanumeric string that follows "/thumb/" indicates (in hexadecimal) which choice to draw for each layout element, as illustrated by this image: 38 39 .image from-zero-to-go-launching-on-google_image03.png 40 41 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. 42 43 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. 44 45 * Implementation 46 47 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. 48 49 var ( 50 // dirs maps each layout element to its location on disk. 51 dirs = map[string]string{ 52 "h": "img/heads", 53 "b": "img/eyes_beak", 54 "i": "img/index_feathers", 55 "m": "img/middle_feathers", 56 "r": "img/ring_feathers", 57 "p": "img/pinky_feathers", 58 "f": "img/feet", 59 "w": "img/wing", 60 } 61 62 // urlMap maps each URL character position to 63 // its corresponding layout element. 64 urlMap = [...]string{"b", "h", "i", "m", "r", "p", "f", "w"} 65 66 // layoutMap maps each layout element to its position 67 // on the background image. 68 layoutMap = map[string]image.Rectangle{ 69 "h": {image.Pt(109, 50), image.Pt(166, 152)}, 70 "i": {image.Pt(136, 21), image.Pt(180, 131)}, 71 "m": {image.Pt(159, 7), image.Pt(201, 126)}, 72 "r": {image.Pt(188, 20), image.Pt(230, 125)}, 73 "p": {image.Pt(216, 48), image.Pt(258, 134)}, 74 "f": {image.Pt(155, 176), image.Pt(243, 213)}, 75 "w": {image.Pt(169, 118), image.Pt(250, 197)}, 76 "b": {image.Pt(105, 104), image.Pt(145, 148)}, 77 } 78 ) 79 80 The geometry of the points above was calculated by measuring the actual location and size of each layout element within the image. 81 82 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. 83 84 var ( 85 // elements maps each layout element to its images. 86 elements = make(map[string][]*image.RGBA) 87 88 // backgroundImage contains the background image data. 89 backgroundImage *image.RGBA 90 91 // defaultImage is the image that is served if an error occurs. 92 defaultImage *image.RGBA 93 94 // loadOnce is used to call the load function only on the first request. 95 loadOnce [[http://golang.org/pkg/sync/#Once][sync.Once]] 96 ) 97 98 // load reads the various PNG images from disk and stores them in their 99 // corresponding global variables. 100 func load() { 101 defaultImage = loadPNG(defaultImageFile) 102 backgroundImage = loadPNG(backgroundImageFile) 103 for dirKey, dir := range dirs { 104 paths, err := filepath.Glob(dir + "/*.png") 105 if err != nil { 106 panic(err) 107 } 108 for _, p := range paths { 109 elements[dirKey] = append(elements[dirKey], loadPNG(p)) 110 } 111 } 112 } 113 114 Requests are handled in a straightforward sequence: 115 116 - Parse the request URL, decoding the decimal value of each character in the path. 117 118 - Make a copy of the background image as the base for the final image. 119 120 - Draw each image element onto the background image using the layoutMap to determine where they should be drawn. 121 122 - Encode the image as a JPEG 123 124 - Return the image to user by writing the JPEG directly to the HTTP response writer. 125 126 Should any error occur, we serve the defaultImage to the user and log the error to the App Engine dashboard for later analysis. 127 128 Here's the code for the request handler with explanatory comments: 129 130 func handler(w http.ResponseWriter, r *http.Request) { 131 // [[http://blog.golang.org/2010/08/defer-panic-and-recover.html][Defer]] a function to recover from any panics. 132 // When recovering from a panic, log the error condition to 133 // the App Engine dashboard and send the default image to the user. 134 defer func() { 135 if err := recover(); err != nil { 136 c := appengine.NewContext(r) 137 c.Errorf("%s", err) 138 c.Errorf("%s", "Traceback: %s", r.RawURL) 139 if defaultImage != nil { 140 w.Header().Set("Content-type", "image/jpeg") 141 jpeg.Encode(w, defaultImage, &imageQuality) 142 } 143 } 144 }() 145 146 // Load images from disk on the first request. 147 loadOnce.Do(load) 148 149 // Make a copy of the background to draw into. 150 bgRect := backgroundImage.Bounds() 151 m := image.NewRGBA(bgRect.Dx(), bgRect.Dy()) 152 draw.Draw(m, m.Bounds(), backgroundImage, image.ZP, draw.Over) 153 154 // Process each character of the request string. 155 code := strings.ToLower(r.URL.Path[len(prefix):]) 156 for i, p := range code { 157 // Decode hex character p in place. 158 if p < 'a' { 159 // it's a digit 160 p = p - '0' 161 } else { 162 // it's a letter 163 p = p - 'a' + 10 164 } 165 166 t := urlMap[i] // element type by index 167 em := elements[t] // element images by type 168 if p >= len(em) { 169 panic(fmt.Sprintf("element index out of range %s: "+ 170 "%d >= %d", t, p, len(em))) 171 } 172 173 // Draw the element to m, 174 // using the layoutMap to specify its position. 175 draw.Draw(m, layoutMap[t], em[p], image.ZP, draw.Over) 176 } 177 178 // Encode JPEG image and write it as the response. 179 w.Header().Set("Content-type", "image/jpeg") 180 w.Header().Set("Cache-control", "public, max-age=259200") 181 jpeg.Encode(w, m, &imageQuality) 182 } 183 184 For brevity, I've omitted several helper functions from these code listings. See the [[http://code.google.com/p/go-thanksgiving/source/browse/][source code]] for the full scoop. 185 186 * Performance 187 188 [[http://3.bp.blogspot.com/-3dpdQWv1nzQ/TufkFj0bS-I/AAAAAAAAAEg/hONAssh_D9c/s1600/image02.png][.image from-zero-to-go-launching-on-google_image02.png ]] 189 190 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. 191 192 * Conclusions 193 194 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. 195 196 The development server provided with the [[http://code.google.com/appengine/downloads.html#Google_App_Engine_SDK_for_Go][SDK]] 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. 197 198 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. 199 200 In writing this application I used just three resources: App Engine's [[http://code.google.com/appengine/docs/go/gettingstarted/helloworld.html][Hello World Go example]], [[http://golang.org/pkg/][the Go packages documentation]], and [[http://blog.golang.org/2011/09/go-imagedraw-package.html][a blog post showcasing the Draw package]]. 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. 201 202 Download the full app source code (including images) at [[http://code.google.com/p/go-thanksgiving/source/browse/][the Google Code project]]. 203 204 Special thanks go to Guillermo Real and Ryan Germick who designed the doodle.