Differences between Go 1.11 and Go 1.12+

Migrating to the Go 1.12+ runtime allows you to use up-to-date language features and build apps that are more portable, with idiomatic code.

Changes in the App Engine Go 1.12+ runtime

If you are considering a migration to the Go 1.12+ runtime, you should be aware of the following differences between Go 1.12+ and earlier runtimes on App Engine standard environment:

  • To reduce runtime migration effort and complexity, the App Engine standard environment allows you to access many of the legacy bundled services and APIs in the Go 1.12+ runtime, such as Memcache. Your Go 1.12+ app can call the bundled services APIs through the App Engine SDK for Go, and access most of the same functionality as on the Go 1.11 runtime.

    You also have the option to use Google Cloud products that offer similar functionality as the legacy bundled services. These Google Cloud products provide idiomatic Cloud Client Libraries for Go. For the bundled services that are not available as separate products in Google Cloud, such as image processing, search, and messaging, you can use third-party providers or other workarounds.

    To learn more about migrating to unbundled services, see Migrating from bundled services.

  • The behavior of some elements in the app.yaml configuration file has been modified. For more information, see Changes to the app.yaml file.

  • Logging in the Go 1.12+ runtime follows the logging standard in Cloud Logging. In the Go 1.12+ runtime, app logs are no longer bundled with the request logs but are separated in different records. To learn more about reading and writing logs in the Go 1.12+ runtime, see the logging guide.

Memory usage differences

Second-generation runtimes see a higher baseline of memory usage compared to first-generation runtimes. This is due to multiple factors, such as different base image versions, and differences in how the two generations calculate memory usage.

Second-generation runtimes calculate instance memory usage as the sum of what an application process uses, and the number of application files dynamically cached in memory. To avoid memory-intensive applications from experiencing instance shutdowns due to exceeding memory limits, upgrade to a larger instance class with more memory.

CPU usage differences

Second-generation runtimes can see a higher baseline of CPU usage upon instance cold-start. Depending on an application's scaling configuration, this might have unintended side effects, such as, a higher instance count than anticipated if an application is configured to scale based on CPU utilization. To avoid this issue, review and test application scaling configurations to ensure the number of instances are acceptable.

Request header differences

First-generation runtimes allow request headers with underscores (e.g. X-Test-Foo_bar) to be forwarded to the application. Second-generation runtimes introduces Nginx into the host architecture. As a result of this change, second-generation runtimes are configured to automatically remove headers with underscores (_). To prevent application issues, avoid using underscores in application request headers.

Changes to the app.yaml file

The behavior of some elements in the app.yaml configuration file has been modified:

Element Change type Description
app_engine_apis Applicable to Go 1.12+ only Must be set to true if you want to access the legacy bundled services for Go 1.12+.
login Supported if app_engine_apis is true If you are not using the legacy bundled services for Go 1.12+, use these alternative methods to authenticate users.
runtime Modified Change the runtime element to specify Go 1.12+.

For more information, see the app.yaml reference.

Creating a main package

Your service must include a package main statement in at least one source file. Alternatively, if your service is using the google.golang.org/appengine package, include a call to appengine.Main().

Writing a main package

If your service doesn't already contain a main package, add the package main statement and write a main() function. At a minimum, the main() function should:

  • Read the PORT environment variable and call the http.ListenAndServe() function:

    port := os.Getenv("PORT")
    if port == "" {
    	port = "8080"
    	log.Printf("Defaulting to port %s", port)
    }
    
    log.Printf("Listening on port %s", port)
    if err := http.ListenAndServe(":"+port, nil); err != nil {
    	log.Fatal(err)
    }

Registering your HTTP handlers

You can register your HTTP handlers by choosing one of the following options:

  • The preferred method is to manually move all http.HandleFunc() calls from your packages to your main() function in your main package.
  • Alternatively, import your application's packages into your main package, ensuring each init() function that contains calls to http.HandleFunc() gets run on startup.

    You can find all packages which use the http.HandleFunc() call with the following bash script, and copy the output into your main package's import block:

    gp=$(go env GOPATH) && p=$(pwd) && pkg=${p#"$gp/src/"} && find . -name "*.go" | xargs grep "http.HandleFunc" --files-with-matches | grep -v vendor/ | grep -v '/main.go' | sed "s#\./\(.*\)/[^/]\+\.go#\t_ \"$pkg/\1\"#" | sort | uniq
    

Structuring your files

Go requires each package has its own directory. You can tell App Engine where your main package is by using main: in your project's app.yaml file. For example, if your app's file structure looked like this:

myapp/
├── app.yaml
├── foo.go
├── bar.go
└── web/
    └── main.go

Your app.yaml file would have:

main: ./web # Relative filepath to the directory containing your main package.

For more information about the main flag, see the app.yaml reference.

Moving files to your GOPATH

Find your GOPATH by using the following command:

go env GOPATH

Move all relevant files and imports to your GOPATH. If using relative imports, such as import ./guestbook, update your imports to use the full path: import github.com/example/myapp/guestbook.