Object Versioning and Concurrency Control

Object Versioning and Concurrency Control

Overview

Versioning-enabled buckets maintain an archive of objects, providing a way to
un-delete data that you accidentally deleted, or to retrieve older versions of
your data. You can turn versioning on or off for a bucket at any time. Turning
versioning off leaves existing object versions in place, and simply causes the
bucket to stop accumulating new object versions. In this case, if you upload
to an existing object the current version is overwritten instead of creating
a new version.

Regardless of whether you have enabled versioning on a bucket, every object
has two associated positive integer fields:

the generation, which is updated when the content of an object is
overwritten.

the metageneration, which identifies the metadata generation. It starts
at 1; is updated every time the metadata (e.g., ACL or Content-Type) for a
given content generation is updated; and gets reset when the generation
number changes.

Of these two integers, only the generation is used when working with versioned
data. Both generation and metageneration can be used with concurrency control
(discussed in a later section).

To work with object versioning in gsutil, you can use a flavor of storage URLs
that embed the object generation, which we refer to as version-specific URLs.
For example, the version-less object URL:

To see all object versions in a versioning-enabled bucket along with
their generation.metageneration information, use gsutil ls -a:

gsutills-ags://bucket

You can also specify particular objects for which you want to find the
version-specific URL(s), or you can use wildcards:

gsutills-ags://bucket/object1gs://bucket/images/*.jpg

The generation values form a monotonically increasing sequence as you create
additional object versions. Because of this, the latest object version is
always the last one listed in the gsutil ls output for a particular object.
For example, if a bucket contains these three versions of gs://bucket/object:

then gs://bucket/object#1360102216114000 is the latest version and
gs://bucket/object#1360035307075000 is the oldest available version.

If you specify version-less URLs with gsutil, you will operate only on the
live version of an object, for example:

gsutilcpgs://bucket/object./dir

or:

gsutilrmgs://bucket/object

The same is true when using wildcards like * and **. These will operate only
on the live version of matching objects. For example, this
command will remove the live version and create an archived version for each
object in a bucket:

gsutilrmgs://bucket/**

To operate on a specific object version, use a version-specific URL.
For example, suppose the output of the above gsutil ls -a command is:

Note that version-specific URLs cannot be the target of the gsutil cp
command (trying to do so will result in an error), because writing to a
versioned object always creates a new version.

Note also that some shells treat "#" as a special character (e.g., zsh with
the extendedglob option enabled). If you're using a shell that treats "#" as a
special character you will need to surround the argument with quotes, such as:

gsutilcp'gs://bucket/object#1360035307075000'./dir

If an object has been deleted, it will not show up in a normal gsutil ls
listing (i.e., ls without the -a option). You can restore a deleted object by
running gsutil ls -a to find the available versions, and then copying one of
the version-specific URLs to the version-less URL, for example:

gsutilcpgs://bucket/object#1360101007329000 gs://bucket/object

Note that when you do this it creates a new object version, which will incur
additional charges. You can get rid of the extra copy by deleting the older
version-specfic object:

gsutilrmgs://bucket/object#1360101007329000

Or you can combine the two steps by using the gsutil mv command:

gsutilmvgs://bucket/object#1360101007329000 gs://bucket/object

If you remove the live version of an object in a versioning-enabled bucket,
an archived version will be preserved:

gsutilrmgs://bucket/object

If you remove a version-specific URL for an object (even if it is the live
version), that version will be deleted permanently:

gsutilrmgs://bucket/object#1360101007329000

If you want to remove all versions of an object, use the gsutil rm -a option:

gsutilrm-ags://bucket/object

If you want to remove all versions of all objects in a bucket (and the bucket
itself), use the rm -r option (-r implies the -a option):

gsutilrm-rgs://bucket

Note that there is no limit to the number of older versions of an object you
will create if you continue to upload to the same object in a versioning-
enabled bucket. It is your responsibility to delete versions beyond the ones
you want to retain.

Copying Versioned Buckets

You can copy data between two versioned buckets, using a command like:

gsutilcp-r-Ags://bucket1/*gs://bucket2

When run using versioned buckets, this command will cause every object version
to be copied. The copies made in gs://bucket2 will have different generation
numbers (since a new generation is assigned when the object copy is made),
but the object sort order will remain consistent. For example, gs://bucket1
might contain:

Note that the object versions are in the same order (as can be seen by the
same sequence of sizes in both listings), but the generation numbers (and
timestamps) are newer in gs://bucket2.

Concurrency Control

If you are building an application using Cloud Storage, you may need to
be careful about concurrency control. Normally gsutil itself isn't used for
this purpose, but it's possible to write scripts around gsutil that perform
concurrency control.

For example, suppose you want to implement a "rolling update" system using
gsutil, where a periodic job computes some data and uploads it to the cloud.
On each run, the job starts with the data that it computed from last run, and
computes a new value. To make this system robust, you need to have multiple
machines on which the job can run, which raises the possibility that two
simultaneous runs could attempt to update an object at the same time. This
leads to the following potential race condition:

job 1 computes the new value to be written

job 2 computes the new value to be written

job 2 writes the new value

job 1 writes the new value

In this case, the value that job 1 read is no longer current by the time
it goes to write the updated object, and writing at this point would result
in stale (or, depending on the application, corrupt) data.

To prevent this, you can find the version-specific name of the object that was
created, and then use the information contained in that URL to specify an
x-goog-if-generation-match header on a subsequent gsutil cp command. You can
do this in two steps. First, use the gsutil cp -v option at upload time to get
the version-specific name of the object that was created, for example:

gsutilcp-vfilegs://bucket/object

might output:

Created:gs://bucket/object#1360432179236000

You can extract the generation value from this object and then construct a
subsequent gsutil command like this:

Given this information, you could use the following command to request setting
the ACL on the older version of the object, such that the command will fail
unless that is the current version of the data+metadata:

Without adding these headers, the update would simply overwrite the existing
ACL. Note that in contrast, the "gsutil acl ch" command uses these headers
automatically, because it performs a read-modify-write cycle in order to edit
ACLs.

If you want to experiment with how generations and metagenerations work, try
the following. First, upload an object; then use gsutil ls -l -a to list all
versions of the object, along with each version's metageneration; then re-
upload the object and repeat the gsutil ls -l -a. You should see two object
versions, each with metageneration=1. Now try setting the ACL, and rerun the
gsutil ls -l -a. You should see the most recent object generation now has
metageneration=2.