Implementing Multitenancy Using Namespaces

The Namespaces API allows you to easily enable multitenancy in your application, simply by selecting a namespace string for each tenant using the appengine.Namespace function.

Setting the current namespace

You can set namespaces using appengine.Namespace. This returns a new context that will automatically cause the given namespace to be used by namespace-enabled APIs.

Most App Engine developers will use their Google Apps domain as the current namespace. Google Apps lets you deploy your app to any domain that you own, so you can easily use this mechanism to configure different namespaces for different domains. Then, you can use those separate namespaces to segregate data across the domains. For more information about setting multiple domains in the Google Apps dashboard, see Deploying Your Application on Your Google Apps URL.

If you do not specify a value for namespace, the namespace is set to an empty string. The namespace string is arbitrary, but also limited to a maximum of 100 alphanumeric characters, periods, underscores, and hyphens. More explicitly, namespace strings must match the regular expression [0-9A-Za-z._-]{0,100}.

By convention, all namespaces starting with "_" (underscore) are reserved for system use. This system namespace rule is not enforced, but you could easily encounter undefined negative consequences if you do not follow it.

Avoiding data leaks

One of the risks commonly associated with multitenant apps is the danger that data will leak across namespaces. Unintended data leaks can arise from many sources, including:

  • Using namespaces with App Engine APIs that do not yet support namespaces. For example, Blobstore does not support namespaces. If you use Namespaces with Blobstore, you need to avoid using Blobstore queries for end user requests, or Blobstore keys from untrusted sources.
  • Using an external storage medium (instead of memcache and datastore), via URL Fetch or some other mechanism, without providing a compartmentalization scheme for namespaces.
  • Setting a namespace based on a user's email domain. In most cases, you don't want all email addresses of a domain to access a namespace. Using the email domain also prevents your application from using a namespace until the user is logged in.

Deploying namespaces

The following sections describe how to deploy namespaces with other App Engine tools and APIs.

Creating namespaces on a per user basis

Some applications need to create namespaces on a per-user basis. If you want to compartmentalize data at the user level for logged-in users, consider using user.Current(c).ID, which returns a unique, permanent ID for the user. The following code sample demonstrates how to use the Users API for this purpose:

import (
	"golang.org/x/net/context"

	"google.golang.org/appengine"
	"google.golang.org/appengine/user"
)

func namespace(ctx context.Context) context.Context {
	// assumes the user is logged in.
	ctx, err := appengine.Namespace(ctx, user.Current(ctx).ID)
	if err != nil {
		// ...
	}
	return ctx
}

Typically, apps that create namespaces on a per-user basis also provide specific landing pages to different users. In these cases, the application needs to provide a URL scheme dictating which landing page to display to a user.

Using namespaces with the Datastore

By default, the datastore uses the current namespace for datastore requests. The API applies this current namespace to datastore.Key objects when they are created. Therefore, you need to be careful if an application stores Key objects in serialized forms, since the namespace is preserved in those serializations.

If you are using deserialized Key objects, make sure that they behave as intended. Most simple applications that use datastore (put/query/get) without using other storage mechanisms will work as expected by setting the current namespace before calling any datastore API.

The following code example shows the SomeRequest request handler for incrementing the count for the current namespace and the arbitrarily named -global- namespace in a Counter datastore entity.
import (
	"io"
	"net/http"

	"golang.org/x/net/context"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
)

type Counter struct {
	Count int64
}

func incrementCounter(ctx context.Context, name string) error {
	key := datastore.NewKey(ctx, "Counter", name, 0, nil)
	return datastore.RunInTransaction(ctx, func(ctx context.Context) error {
		var ctr Counter
		err := datastore.Get(ctx, key, &ctr)
		if err != nil && err != datastore.ErrNoSuchEntity {
			return err
		}
		ctr.Count++
		_, err = datastore.Put(ctx, key, &ctr)
		return err
	}, nil)
}

func someHandler(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	err := incrementCounter(ctx, "SomeRequest")
	if err != nil {
		// ... handle err
	}

	// temporarily use a new namespace
	{
		ctx, err := appengine.Namespace(ctx, "-global-")
		if err != nil {
			// ... handle err
		}
		err = incrementCounter(ctx, "SomeRequest")
		if err != nil {
			// ... handle err
		}
	}

	io.WriteString(w, "Updated counters.\n")
}

func init() {
	http.HandleFunc("/some_url", someHandler)
}

Using namespaces with the Memcache

Memcache uses the namespace from the appengine.Context used for memcache API calls.

Using namespaces with the Task Queue

Push queues use the namespace as set in the appengine.Context at the time the task is added.

Task names are shared across all namespaces. You cannot create two tasks of the same name, even if they use different namespaces. If you wish to use the same task name for many namespaces, you can simply append each namespace to the task name.

There are some unique instances where it is appropriate to explicitly set a namespace for a task that works across all namespaces. For example, you might create a task that aggregates usage statistics across all namespaces. You could then explicitly set the namespace of the task. The following code sample demonstrates how to explicitly set namespaces with the task queue.

import (
	"io"
	"net/http"

	"golang.org/x/net/context"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/taskqueue"
)

type Counter struct {
	Count int64
}

func incrementCounter(ctx context.Context, name string) error {
	key := datastore.NewKey(ctx, "Counter", name, 0, nil)
	return datastore.RunInTransaction(ctx, func(ctx context.Context) error {
		var ctr Counter
		err := datastore.Get(ctx, key, &ctr)
		if err != nil && err != datastore.ErrNoSuchEntity {
			return err
		}
		ctr.Count++
		_, err = datastore.Put(ctx, key, &ctr)
		return err
	}, nil)
}

// taskQueueHandler serves /_ah/counter.
func taskQueueHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "POST only", http.StatusMethodNotAllowed)
		return
	}
	ctx := appengine.NewContext(r)
	err := incrementCounter(ctx, r.FormValue("counter_name"))
	if err != nil {
		// ... handle err
	}
}

func someRequest(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)

	// Perform asynchronous requests to update counter.
	// (missing error handling here.)
	t := taskqueue.NewPOSTTask("/_ah/counter", map[string][]string{
		"counter_name": {"someRequest"},
	})

	taskqueue.Add(ctx, t, "")

	// temporarily use a new namespace
	{
		ctx, err := appengine.Namespace(ctx, "-global-")
		if err != nil {
			// ... handle err
		}
		taskqueue.Add(ctx, t, "")
	}

	io.WriteString(w, "Counters will be updated.\n")
}

func init() {
	http.HandleFunc("/_ah/counter", taskQueueHandler)
	http.HandleFunc("/some_request", someRequest)
}

Using namespaces with the Blobstore

The Blobstore is not segmented by namespace. To preserve a namespace in Blobstore, you need to access Blobstore via a storage medium that is aware of the namespace (currently only memcache, datastore, and task queue). For example, if a blob's Key is stored in a datastore entity, you can access it with a datastore Key that is aware of the namespace.

If the application is accessing Blobstore via keys stored in namespace-aware storage, the Blobstore itself does not need to be segmented by namespace. Applications must avoid blob leaks between namespaces by:

  • Not using BlobInfo for end-user requests. You can use BlobInfo queries for administrative requests (such as generating reports about all the applications blobs), but using it for end-user requests may result in data leaks because all BlobInfo records are not compartmentalized by namespace.
  • Not using Blobstore keys from untrusted sources.

Setting namespaces for Datastore Queries

In the Google Cloud Platform Console, you can set the namespace for Datastore queries.

If you don't want to use the default, select the namespace you want to use from the drop-down.

Using namespaces with the Bulk Loader

The bulk loader supports a --namespace=NAMESPACE flag that allows you to specify the namespace to use. Each namespace is handled separately and, if you want to access all namespaces, you will need to iterate through them.

Search operations use the namespace associated with a context passed to them when they are called.

To perform an operation in a namespace, wrap a context with appengine.Namespace, and use that context when calling methods on an index. In the example below, the context is wrapped in the "aSpace" namespace:

// Open an index
index, err := search.Open("index")

// Wrap the context into a namespace
c = appengine.Namespace(c, "aSpace")

// The Search operation occurs in the namespace "aSpace"
it := index.Search(c, "hello", nil)

Send feedback about...

App Engine standard environment for Go