Programming Google App Engine with Python (2015)

Chapter 5. Using Modules

You can build large web applications using just App Engine’s automatically scaling instances, and many have. Automatic scaling is well suited to large scale user traffic and can accommodate real-world traffic spikes with ease. But it isn’t long before you want to do more computational tasks with App Engine besides serving user traffic and residual tasks. Other parts of a mature app’s architecture, such as batch jobs, long-running computing tasks, and special purpose always-on backend services, don’t quite fit the same mold. Performance tuning that’s suited to one kind of computation doesn’t suit another. Your web traffic may work well with small instance classes and aggressive pending queues, while your nightly data crawl needs more memory on a single instance. And you probably don’t want your user traffic instance pool saturated by a batch job.

App Engine lets you define sets of instances for different purposes, called modules. Each module can be configured and tuned separately using all of the options we’ve discussed so far, such as instance classes and automatic scaling parameters. Each module gets its own URL, so it can be addressed individually. You can make a module’s URL publicly accessible or just call it internally from other modules. Each module can scale its own instances according to its purpose-specific traffic patterns.

For even greater flexibility, App Engine offers two additional scaling patterns beyond automatic scaling: manual scaling and basic scaling. With manual scaling, you start and stop instances directly using the Cloud SDK or within the app by calling an API. Basic scaling has the same features as manual scaling, with the addition of a simple configurable scheduler that can start new instances in response to requests and stop idle instances after a period of time. Each module can be configured to use any of the three available scaling strategies.

Modules can also be deployed separately from each other, at separate times and with separate versions. It’s easiest to use the same code base for all modules, but this gives you more options as to when software versions are deployed or rolled back.

With modules, you can design an architecture that meets your application’s needs, and develop and tune each component separately.

An Example Layout

Let’s consider a simple modular architecture for our hypothetical multiplayer game. Refer to Figure 5-1.

Figure 5-1. An example of an application architecture using modules

In this example, a “web frontend” module handles all of the user traffic coming from browsers, using the automatic scaling strategy. This is the default module for the website for the game, and serves all traffic for the main domain name. All apps have at least one module, and typically the default module uses automatic scaling. The example apps we’ve discussed so far have one module using this default configuration.

This multiplayer game has a mobile app. The mobile app communicates with the App Engine app via an API hosted in the “mobile frontend” module, with its own URL. Just like the web frontend, this API traffic comes from users, so we use automatic scaling for this module as well. Making it a separate module from the web traffic isolates it for the purposes of performance tuning and makes it easier to deploy changes to the web and mobile experiences separately. We may want to coordinate API support with mobile app store releases, or provision additional resident instances in anticipation of a scheduled mobile launch.

Both user-facing frontend modules use small instance classes and run code tailored to produce fast responses to user client requests. They communicate with the built-in App Engine services as usual, such as the datastore, memcache, and task queue services. We use two additional modules to provide custom services of our own.

The “battle simulation backend” service performs heavy-duty computation for updating the game world in response to major events. We configure this module to use a large instance class to take advantage of the faster CPU. We use the basic scaling strategy for this so we can provision more instances automatically, up to a fixed maximum number of instances. If we eventually discover that we need more battle simulation instances, we can evaluate the costs and benefits, and adjust the configuration accordingly. We might use the “pull queue” feature of the task queue service to send work to this backend, or we can call it directly from the frontends using an API of our own design. (For more information on task queues, see Chapter 16.)

The “world cache backend” maintains a copy of the data that describes the game world. This module is configured to use an instance class with a large amount of RAM and a fixed number of always-on resident instances. Of course, we must persist the game world to the datastore on a regular basis or risk losing data. So (in this hypothetical example) we use a write-behind cache that updates RAM first then periodically flushes to the datastore with minimal risk of loss. We use manual scaling because we want this special cache to stay resident as long as possible, and the amount of memory we need is proportional to the size of the world, not to the number of active users.

This example is simplified for illustration purposes; a real multiplayer game architecture might be more sophisticated. This provides a basic idea of how separate modules with distinct configurations allow you to optimize costs and performance.

Configuring Modules

You already know how to configure the default module using the app.yaml file. The file selects the runtime, specifies whether the code is threadsafe, defines handlers mapped to URL paths, and so forth. By default, the default module uses automatic scaling, and you can tune its parameters with the automatic_scaling options we discussed earlier.

To define a module other than the default module, create another file whose name ends in .yaml and isn’t one of the other .yaml filenames recognized by App Engine. Give the module an ID using the module: parameter. It’s typical to use the module ID for the filename as well, but it’s not required. The module ID follows the same rules as the app ID: lowercase letters, numbers, hyphens, and the first character must be a letter.

Continuing the previous example, we can configure the mobile frontend module (mobile-fe) using a file named mobile-fe.yaml, like so:

module: mobile-fe

application: app-id

version: 1

runtime: python27

api_version: 1

threadsafe: true

instance_class: F2

automatic_scaling:

  min_idle_instances: 3

handlers:

# ...

The version parameter is specific to the given module, and does not have to be unique across all modules. The limit of 60 versions applies to the total count of versions across all modules.

The app.yaml file for the default module is just a module configuration file. App Engine knows it describes the default module because it does not contain a module ID parameter. You can also use the special module ID default to declare that a configuration file is for the default module.

Manual and Basic Scaling

To declare that the module uses a manual scaling strategy, include a manual_scaling section in the configuration (and do not include an automatic_scaling section). Our game’s world data cache is scaled manually with five instances to start, so world-cache.yaml looks like this:

module: world-cache

application: app-id

version: 1

runtime: python27

api_version: 1

threadsafe: true

instance_class: B4_1G

manual_scaling:

  instances: 5

handlers:

# ...

The instances parameter sets the number of instances started when the configuration is deployed for the first time. You can start and stop instances after deployment by hand with the Cloud Console or programmatically with the modules API (described in “The Modules API”).

To declare that the module uses basic scaling, include a basic_scaling section. Our game’s battle simulation infrastructure is only needed on demand, so battle-sim.yaml looks like this:

module: battle-sim

application: app-id

version: 1

runtime: python27

api_version: 1

threadsafe: true

instance_class: B8

basic_scaling:

  max_instances: 10

  idle_timeout: 5m

handlers:

# ...

With basic scaling, max_instances is the maximum number of instances to start in response to requests to the module. If a request arrives when all instances are busy and the number of active instances is less than the maximum, a new instance is started. idle_timeout is the amount of time an instance must be idle before it is shut down automatically, specified as a number and a unit (m for minutes).

Modules with manual or basic scaling use a different set of instance classes than modules with automatic scaling: B1, B2, B4, B4_1G, and B8. These are similar to the corresponding F* classes, with the addition of B8, which has 1 GB of memory and a 4.8 GHz CPU. If not specified, the default instance class for manual or basic scaling is B2.

If the module’s configuration file does not specify the scaling strategy, or it includes an automatic_scaling section, then automatic scaling is used for the module.

Manual Scaling and Versions

You can create multiple versions of a module by changing the version: parameter in its configuration file, then deploying the module. Exactly one version is the default version for the module, and you can change the default version using the Cloud Console or the appcfg.py command. You can delete unused versions from the Cloud Console or the appcfg.py command as well.

With manual scaling, App Engine starts the requested number of instances when the module is deployed. If you have multiple versions of a manually scaled module, App Engine may be running the requested number of instances for each version of the module. Be sure to delete unused versions or shut down their instances to avoid burning through your quota or your budget.

This is different from automatic scaling and resident (minimum idle) instances: resident instances are only started and maintained for the default version. Manual scaling instances are started for all versions when they are deployed, and stay running until they are shut down via the Cloud Console, the appcfg.py command, or an API call.

Startup Requests

An instance does not run any code until it receives a request. This is reasonable for modules that respond to requests, such as a service that waits for an API call before doing any work. In many cases, you probably want the instance to start doing some work right away, either to kick off a long-running process or to prepare the instance for responding to future events.

When App Engine starts an instance in a module with manual or basic scaling, it issues a “start” request at the following URL path:

/_ah/start

You can map a request handler to this path in the handlers: section of the module’s configuration. This handler is called on each instance that is started.

If the start handler returns, it must return an HTTP status code indicating success (200–299). A 404 status code is also considered “success,” in case there is no handler mapped to the URL path. If the handler returns a server error, App Engine considers the instance startup to have failed and terminates the instance. It then starts a new instance, if needed.

An instance will not respond to other requests until the start handler has returned. If the module does not need to respond to requests, then you can put all of the module’s work in the start handler. The start handler can even be a continuously running process: modules with manual scaling do not have a request deadline. If the module must do both background work and respond to requests, a better option is for the start handler to create a background thread, then exit immediately. We’ll look at background threads in a moment.

Startup requests for modules with manual and basic scaling are similar to warmup requests for automatic scaling, but you set them up in different ways and use them for different purposes. (Refer back to “Warmup Requests”.)

Shutdown Hooks

There are two ways that application code can know when an instance is being shut down. One is to call the is_shutting_down() function from the google.appengine.api.runtime module periodically, and react accordingly if the method returns True:

import webapp2

from google.appengine.api import runtime

class MainHandler(webapp2.RequestHandler):

    def get(self):

        # Initialization.

        # ...

        while notruntime.is_shutting_down():

            # Do a bit of work...

        # Clean up.

        # ...

app = webapp2.WSGIApplication([('/_ah/start', MainHandler)])

Alternatively, the app can register a shutdown hook with the runtime environment. This hook is a Python function that App Engine calls in a new thread when it’s about to shut down the instance.

If the function raises an exception, this exception will propagate to all active request handlers and threads. This makes it easy for the shutdown hook to interrupt all work in progress and coordinate cleanup efforts:

import webapp2

from google.appengine.api import runtime

class Shutdown(Exception):

    pass

def shutdown():

    # Clean up.

    # ...

    raise Shutdown()

class MainHandler(webapp2.RequestHandler):

    def get(self):

        # Set the shutdown hook.

        runtime.set_shutdown_hook(shutdown)

        # Initialization.

        # ...

        try:

            while True:

                # Do work...

        except Shutdown:

            # Additional task-specific cleanup.

            # ...

app = webapp2.WSGIApplication([('/_ah/start', MainHandler)])

Once App Engine has decided to shut down an instance, the app has 30 seconds after the shutdown hook is called to finish what it is doing. At the end of the 30 seconds, the instance goes away.

Neither the shutdown hook nor the 30-second runway are guaranteed. Under rare circumstances, App Engine may need to kill an instance before the shutdown hook can be called or completed. Long-running processes should persist their state periodically and be able to recover from a sudden interruption.

TIP

App Engine logs a request to the URL path /_ah/stop when it initiates shutdown on an instance. You can’t map a request handler to this URL: it uses a built-in handler that invokes the shutdown hook. You can look for this request in the logs when you’re troubleshooting issues with shutdown logic.

Background Threads

During a request handler, an app can invoke regular Python threads to perform concurrent work. Once the handler returns a response, App Engine assumes that any unfinished threads are no longer needed and terminates them. Regular threads cannot outlast the request handler. This isn’t a problem for modules that do request handling with automatic scaling, as the request handler exits once it has a response and can use other means (such as task queues) to defer work. Nor is this a problem for a simple always-on instance with manual scaling, which can perform a large or continuously running job in its start handler and manage threads inside that process without terminating.

We need another solution for the case where a module with manual scaling needs to perform work in the background and also accept requests. Putting the background job in the start handler is insufficient because the instance can’t accept requests until the start handler finishes. Instead, we need a way to initiate work from the start handler, then exit from the handler while the work continues in the background.

For this purpose, App Engine provides background threads, a special kind of thread that detaches from the request handler and continues running after the handler returns. Only instances with manual or basic scaling can use background threads. In Python, you use the BackgroundThreadclass from the google.appengine.api.background_thread module as you would the threading.Thread class from the Python standard library:

from google.appengine.api import background_thread

def work():

    # ...

# ...

        thread = background_thread.BackgroundThread(

            target=work)

        thread.start()

Log messages for background tasks appear in your logs under a virtual request for the URL /_ah/background. The virtual request information includes the ID of the instance on which it is running, and how much time has elapsed since it was started.

Modules and the Development Server

The development server can run multiple modules simultaneously on your local machine. Each module gets its own port number on localhost, chosen automatically from the available ports.

To start the development server with modules, run the dev_appserver.py command, and provide the path to the configuration file of each module you wish to run, including app.yaml:

dev_appserver.py app.yaml battle-sim.yaml mobile-fe.yaml world-cache.yaml

The development server prints the selected port numbers for each module to the console:

INFO ... Starting module "default" running at: http://localhost:8080

INFO ... Starting module "battle-sim" running at: http://localhost:8081

INFO ... Starting module "mobile-fe" running at: http://localhost:8082

INFO ... Starting module "world-cache" running at: http://localhost:8083

You can use these ports to send test traffic to modules directly. Traffic to the module ports is distributed to instances according to the scaling policy for the module.

The individual instances are also assigned ports. You can see the current state of the instances with statistics and links to all of them in the development server console at http://localhost:8000.

The development server simulates automatic scaling and manual scaling. For modules configured with basic scaling, the development server initializes the maximum number of instances, then issues the /_ah/start request for each instance when the instance receives its first request.

Unfortunately, there is no way to shut down a simulated instance in the development server. To test your shutdown hook, you must deploy the app to App Engine. When running on App Engine, you can shut down instances manually from the command line or the Cloud Console.

TIP

When calling one module from another, you do not need to hard-code the localhost URLs into your app for testing. Instead, you can determine the URL for a module by calling a function. See “Addressing Modules with URLs”.

Deploying Modules

When we introduced deploying an application with the appcfg.py update command, we used the application root directory as its argument:

appcfg.py update myapp

When given the application root directory like this, the appcfg.py update command locates the app.yaml file in this directory, then deploys the app to the module described by that file. In previous examples, app.yaml described the default module with automatic scaling, so this did what we wanted. (This is the only way the filename app.yaml is special.)

The appcfg.py update command can also accept module configuration file names as arguments. When given a module configuration file, appcfg.py update deploys the app and all of its files to that module. This works with app.yaml or any other module configuration file:

appcfg.py update myapp/world-cache.yaml

You can list multiple module configuration files in one command to deploy them all in sequence:

appcfg.py update myapp/app.yaml myapp/mobile-fe.yaml \

                 myapp/battle-sim.yaml myapp/world-cache.yaml

The appcfg.py update command assumes that the directory containing the configuration file is the application root directory for the purposes of the module, and it deploys all of the files it finds to the module. It’s a good practice to use the same application files for all modules, and simply use different handlers: in each configuration file to control their individual behavior.

Alternatively, you can store each module’s configuration file and code in a separate directory. As long as the application IDs are the same, they will all be deployed to the same application.

Deploying a module with manual or basic scaling causes all of its active instances to be stopped and started. If you know a module hasn’t changed and you do not want its instances to be restarted when you deploy, be sure to exclude that module’s configuration file from the appcfg.py update command.

TIP

So if the runtime environment is selected by a field in the module’s configuration file, and each module can be deployed separately with different code, does that mean one app can use different runtime environments for different modules? Can you have one module implemented in Python and another module implemented in Java?

The answer is yes (!), with a proviso. You can write a module in Java and deploy it to an app that already has another module (possibly the default module) in Python, using the same application ID, different module IDs, and different runtime configurations. The proviso is that you can’t easily test interactions between the two modules in a development server. The Python app and the Java app must run in separate development servers from their corresponding SDKs, and these development servers know nothing about each other. They can’t share a simulated datastore or other data services, and they don’t know each other’s development URLs automatically. At the very least, you’d have to implement a layer that detects whether the app is in a development server (via the SERVER_SOFTWARE environment variable) and stub out the service calls.

This is still a useful scenario if you’re willing to develop and test the modules separately, which is a reasonable thing to do anyway. And you can always deploy nondefault versions to App Engine for integration testing.

Addressing Modules with URLs

We’ve already seen how you can access a deployed application using its appspot.com URL, with the application ID as the leftmost subdomain:

  http://app-id.appspot.com/

We now know that this URL accesses the default version of the default module. The default version is the version selected as the default in the Versions panel of the Cloud Console. The default module is the module whose configuration file omits the module: parameter or sets it todefault, typically app.yaml.

If the app has multiple modules, each module gets its own domain name. This domain is formed by taking the appspot.com URL for the app and prefixing the module ID:

  http://module-id.app-id.appspot.com/

This URL accesses the default version of the specified module. If the module has multiple instances, App Engine routes a web request for this URL to an instance selected by its load-balancing algorithm. With automatic scaling, and with basic scaling where the number of active instances is below the configured maximum, App Engine may start a new instance if all active instances are busy, though this doesn’t have an immediate impact on where the current request is routed.

You may recall that it’s possible to access a specific version of an app, with a URL like this:

  http://version-id.app-id.appspot.com/

More specifically, this accesses a specific version of the default module. You’ve probably already guessed that this means there’s a potential for version IDs on the default module and module IDs to conflict, and you’re right. You can’t use the same ID for a module and one of the versions of the default module.

You’ve also probably guessed that it’s possible to access a specific version of a specific module, and you’re right again:

  http://version-id.module-id.app-id.appspot.com/

If a module with manual or basic scaling has multiple instances, you can address the instance directly with one more piece of the domain:

  http://instance-id.module-id.app-id.appspot.com/

In this case, the instance ID must be a running instance of the default version for the module. You can also access an instance of a nondefault version of a module using the longest possible URL, containing all of the IDs:

  http://instance-id.version-id.module-id.app-id.appspot.com/

Instance IDs are determined at runtime, so you need to call an API to figure out what they are. We’ll see how in “The Modules API”.

Calling Modules from Other Modules

One of the most common uses of modules is to build backend services for other modules to access via endpoints. You can send a request from one module to another using the URL Fetch service, an App Engine service that manages outgoing HTTP requests. (See Chapter 13 for a complete introduction to this service.) The request can go to the module’s URL, like any other:

from google.appengine.api import urlfetch

# ...

        module_url = 'http://module-id.app-id.appspot.com/api/list'

        result = urlfetch.fetch(module_url)

        if result.status_code == 200:

            # ...

Adding the module URL directly to the code like this is not a good practice. Better would be to calculate the module URL from environmental factors, including whether the app is running in the development server and what the current app’s ID is. Helpfully, App Engine has an API for this:

from google.appengine.api import modules

from google.appengine.api import urlfetch

# ...

        module_url = 'http://{}/api/list'.format(

            modules.get_hostname(module='module-id'))

        result = urlfetch.fetch(module_url)

        if result.status_code == 200:

            # ...

The get_hostname() function in the google.appengine.api.modules module returns the appropriate module-specific domain name for the environment. In a development server, this is the localhost hostname with the appropriate port number added. On App Engine, this is calculated from the module ID and the current app ID. The get_hostname() function accepts optional version and instance parameters to select a specific version or instance, and you can omit any of module, version, or instance to select the module, version, and instance currently running the code (or the default version or instance when selecting a different module).

Module endpoints that are called by the app itself usually should not be called by outsiders that happen to know the URL. App Engine makes it easy to secure these endpoints so only calls from within the app and authenticated requests from the app’s developers can access it. To set this up, configure the handler for the endpoint in the module’s configuration file so that its login: property is admin:

module: module-id

# ...

handlers:

- url: /api/.*

  script: backend.app

  login: admin

We saw this administrator-only configuration in “Authorization with Google Accounts”. login: admin lets through URL Fetch requests coming from the app itself. We’ll see this again later for other kinds of self-calling, such as in Chapter 16.

Module URLs and Secure Connections

Back in “Configuring Secure Connections”, we mentioned that appspot.com URLs with multiple parts need special treatment when using HTTPS. The same applies to all module-specific appspot.com URLs as well.

As a reminder, to access a module-specific URL with HTTPS, replace all of the dots (.) to the left of the app ID with -dot- (hyphen, the word “dot,” hyphen), like so:

  https://module-id-dot-app-id.appspot.com/

  https://version-id-dot-module-id-dot-app-id.appspot.com/

Module URLs and Custom Domains

The appspot.com URLs are sufficient for internal calls. If you want to accept external requests directly to a specific module and you don’t mind sharing your app ID with the world, you can advertise the appspot.com URL. There’s nothing inherently wrong with exposing an app ID, but you might prefer to avoid it for aesthetic reasons.

If you set up a custom domain using the procedure discussed in “Domain Names”, you can use subdomains of your custom domain to access modules. For this to work, you must update the DNS record with your DNS hosting service so that the appropriate subdomains go to Google. You can make this a “wildcard subdomain,” so all subdomains for your custom domain will work, and you don’t have to update DNS records as you make changes to module names. Wildcard subdomains are required if you want to address individual instances directly, because instance IDs are determined dynamically.

With the appropriate DNS configuration in place, you can access a module using the module ID as a subdomain, like so:

http://module-id.example.com/

Similarly, you can access instances like so:

http://instance-id.module-id.example.com/

Subdomains of custom domains always use the default version for the module.

TIP

You cannot access modules via subdomains of a custom domain set up as a Google Apps domain. Unfortunately, at this time, this means that you cannot access modules with custom domains via HTTPS. If you need HTTPS, the only option is the appspot.com domain, using the -dot- notation.

Dispatching Requests to Modules

Sometimes you want requests to reach nondefault modules, but you don’t want to expose URLs on subdomains. Instead, you want to map URL paths on the main domain to modules. For example, instead of using http://mobile-fe.example.com/ for your mobile REST API, you’d prefer to use http://www.example.com/api/

App Engine lets you do this using yet another configuration file, called dispatch.yaml. It looks like this:

dispatch:

- url: "*/static/*"

  module: default

- url: "*/api/*"

  module: mobile-fe

- url: "saucy-boomerang-123.appspot.com/test/*"

  module: test

By necessity, this file is very simple: each dispatch rule is a directive for Google’s high-performance frontends to override its own decision about how to route a request. The file can contain up to 10 rules. Each rule consists of a url pattern, and the name of the module that should handle the request, where default is the name of the default module.

Unlike an app.yaml file, the URL pattern matches both the domain of the request and the URL path. The first slash (/) in the pattern always matches the boundary between the domain and the path, and so must be included. You can use a wildcard (*) character at the beginning, at the end, or both, to match zero or more characters. Fancier pattern matching is not supported.

Consider this url value:

- url: "*/api/*"

  module: mobile-fe

The beginning wildcard appears before the first slash, and so this rule applies to all domains, including the custom domain, the appspot.com domain, and all subdomains. The pattern after the first slash matches all URL paths that begin with /api/, followed by zero or more characters. All requests whose domain and path match this pattern are routed to the mobile-fe module.

If none of the rules in the dispatch.yaml file match the request, then the usual dispatch logic takes over.

As with other app-wide configuration files, the dispatch.yaml file is deployed with the rest of the application during appcfg.py update. You can also deploy just this file to the app by running appcfg.py update_dispatch.

Starting and Stopping Modules

When you deploy a module with manual scaling, App Engine ensures that the number of instances requested in the configuration file’s instances parameter have started, starting new ones if necessary. From that point on, the number of instances in the module can be adjusted via the Cloud Console in the Instances panel, via the appcfg.py command, or programmatically by calling the modules API from the app.

Instances in a module with basic scaling can also be started and stopped in these ways. The main difference with basic scaling is that instances are also started and stopped in response to incoming requests and idle timeouts, respectively. Basic instances are stopped during a deployment so they can be restarted to pick up the new software.

To shut down instances from the Cloud Console, go to the Instances panel under Compute, App Engine, then select the appropriate module and version from the drop-down menus at the top. (The module drop-down only appears if multiple modules are deployed.) The panel includes performance graphs specific to the module and version, as well as a list of active instances. Click the Shutdown button to stop an instance.

There is no way to increase the number of instances in a module from the Cloud Console. The expectation with manual scaling is that the app will start instances as it needs them via the API. Or you can use the command-line tool.

From the command line, you can stop a module, and start a module. Stopping a module shuts down all of its instances, and starting a stopped module activates all of the instances configured for the module. The command takes the module’s .yaml file as a parameter, or you can just set the --application=…, --module=…, and --version=… arguments directly:

appcfg.py stop_module_version world-cache.yaml

appcfg.py start_module_version world-cache.yaml

appcfg.py stop_module_version --application=saucy-boomerang-123 \

  --module=world-cache --version=alpha

Finally, you can use the modules API to stop and start modules, and also adjust the number of instances for a module with manual scaling without deploying a configuration change. We’ll see how to do that in “The Modules API”.

Managing and Deleting Modules and Versions

When you deploy a module, App Engine checks the version: parameter in the configuration file and either creates a new version of the module with that ID if it does not exist, or replaces the version with that ID if it does.

When you deploy a module for the first time, the first version created becomes the default version. Otherwise, the default version doesn’t change unless you use the Cloud Console or the appcfg.py command to change it. To change the default version from the Cloud Console, go to the Versions panel under Compute, App Engine. Click the checkbox next to the version you want to make the default, then click the “Make default” button.

Changing the default version for a module with manual or basic scaling changes the destination of requests to the module URL, and otherwise has no immediate effect on the module’s instances. Each version’s instances continue to run according to their scaling policies.

When you’re working with automatic scaling, nondefault versions are mostly inconsequential, because versions that don’t receive traffic don’t consume resources. They either serve their purpose (as testing versions, for example), or they sit around waiting to be cleaned up when a developer notices that the app has hit its version count limit. With manual scaling, cleaning up old versions is much more important. Versions of modules with manual scaling keep their instances running until they are explicitly shut down or the version is deleted. It’s worth keeping careful track of which versions have active instances that are not being used, and either shutting them down or deleting them as part of your deployment process.

To delete a version from the Cloud Console, go to the Versions panel as before. Click the checkboxes next to the versions to delete, then click the Delete button.

Notice that the default module does not have a checkbox next to it. This is a safety catch to prevent you from deleting the version that is serving the main traffic for the module. To delete the default version, you must first either make another version the default, or you must delete all of the other versions. Deleting all versions (by deleting all nondefault versions, then deleting the default version) deletes the module.

To delete a version from the command line, use the appcfg.py delete_version command, passing the app ID, module ID, and version ID as the -A, -M, and -V arguments, respectively. The -M argument can be omitted to delete a version from the default module, or you can specify -M default:

appcfg.py delete_version -A saucy-boomerang-123 -M world-cache -V alpha

This command will fail if you attempt to delete the default version and other versions exist. It won’t try to pick a new default version automatically. You can delete the default version if it is the last version in a module.

The Modules API

App Engine gives you programmatic access to information about the modules and versions of the running app, as well as the identifiers of the module, version, and instance running the code. You can also adjust the number of instances for a module with manual scaling, and start and stop module versions just as you can from the command line. All of these functions are provided by the google.appengine.api.modules module.

We already looked at get_hostname(). This is essential for calculating the URL the app should use when calling one of its own modules via the URL Fetch service. This is the only method that will return localhost URLs when running in the development server.

You can get the IDs for the module, version, and instance running the current code using the get_current_module_name(), get_current_version_name(), and get_current_instance_id() functions. They take no arguments.

The get_modules() function returns a complete list of the app’s module names, including default if it exists. (It is technically possible to deploy only named modules to an app, and therefore have no default module.)

The get_versions() function returns a list of version IDs. Without an argument, it returns the versions of the current module. When given a module name as its module argument, it returns the versions for that module, or raises an exception if the given module does not exist.

The related function get_default_version() returns the version ID of the default version. It too takes an optional module name argument, and uses the current module if this is omitted.

There are four methods you can use to manipulate the instances of modules with manual or basic scaling. You can use get_num_instances() and set_num_instances(count) to manipulate the number of instances for the module. These functions take optional module and versionarguments that default to the module or version running the code. Deploying the module resets the number of instances to the number specified in the configuration file.

Finally, you can start and stop module versions with start_version(module, version) and stop_version(module,version). The arguments to the stop_version() function are optional, and default to the current module and version. Calling stop_version() without arguments is a good way for a module to stop itself.

TIP

Remember that stopping an instance will invoke its shutdown hook, if any, and wait 30 seconds for it to complete.

An Always-On Example

The following simple example illustrates how the start handler, background threads, and the shutdown hook work together on an always-on instance. The start handler registers the shutdown hook and kicks off the background thread, then returns. The background thread increments a global counter once per second. You can inspect the current value of the counter in a browser with a web request for the "/" URL path.

Here is the Python code. Let’s call this file counter.py:

import logging

import time

import webapp2

from google.appengine.api import runtime

from google.appengine.api import background_thread

GLOBAL_COUNTER = 1

class Shutdown(Exception):

    pass

def shutdown():

    logging.info('Shutdown hook called')

    raise Shutdown()

def counter_loop():

    global GLOBAL_COUNTER

    try:

        while GLOBAL_COUNTER < 600:

            GLOBAL_COUNTER += 1

            time.sleep(1)

        logging.info('Shutting down counter after 600 cycles')

    except Shutdown:

        logging.info('Counter loop saw shutdown')

class MainPage(webapp2.RequestHandler):

    def get(self):

        self.response.out.write('GLOBAL_COUNTER=%d' % GLOBAL_COUNTER)

class StartHandler(webapp2.RequestHandler):

    def get(self):

        runtime.set_shutdown_hook(shutdown)

        thread = background_thread.BackgroundThread(

            target=counter_loop)

        thread.start()

app = webapp2.WSGIApplication([('/', MainPage),

                               ('/_ah/start', StartHandler)], debug=True)

The configuration file for this module specifies manual scaling and one instance. We can put the configuration for the counter module in a file named counter.yaml:

module: counter

application: module-demo

version: 1

runtime: python27

api_version: 1

threadsafe: true

instance_class: B1

manual_scaling:

  instances: 1

handlers:

- url: .*

  script: counter.app

We can deploy this module with this command:

appcfg.py update counter.yaml

And access the interactive display with this URL (given the application and module IDs shown):

http://counter.module-demo.appspot.com/

If you want to try deploying this example, remember that with manual scaling, all of the requested instances are started as soon as you deploy. Instance running time consumes the “backend instance hours” quota, and apps without a budget set only get so many free hours a day. You’ll want to keep close tabs on the running instance, and shut it down (or delete the module) when you’re done experimenting.

You can reduce the risk of this example burning through your backend instance hours quota by changing it to use basic scaling. With basic scaling, the instance won’t start until you load the interactive display. While the instance is running, the background thread will increment the counter once per second, up to 600 (the condition on the while loop). After 600 seconds, the instance will go idle, then shut down after the idle timeout you set in the configuration.