Control de versiones de objetos y control de simultaneidad

Descripción general

Los depósitos habilitados para el control de versiones mantienen versiones no actuales de objetos, lo que proporciona una forma de anular la eliminación de los datos que borraste por accidente o recuperar versiones anteriores de tus datos. Puedes activar o desactivar el control de versiones de un depósito en cualquier momento. Desactivar el control de versiones deja las versiones de objetos existentes en su lugar y solo hace que el depósito borre la versión publicada del objeto cada vez que se sube una versión nueva.

Sin tener en cuenta si habilitaste el control de versiones en un depósito, cada objeto tiene dos campos de números enteros positivos asociados:

  • La generación, que se actualiza cuando un objeto nuevo reemplaza un objeto existente con el mismo nombre
  • La metageneración, que identifica la generación de metadatos. Comienza en 1, se actualiza cada vez que los metadatos (p. ej., LCA o Content-Type) de una generación de contenido determinada se actualizan y se restablece cuando cambia el número de generación.

De estos dos números enteros, solo la generación se usa cuando se trabaja con datos con versión. La generación y metageneración se pueden usar con el control de simultaneidad (que se explicará en una sección posterior).

Para trabajar con el control de versiones de objetos en gsutil, puedes usar una variante de URL de almacenamiento que incorpore la generación de objetos, a la que nos referimos como URL específicas de la versión. Por ejemplo, la URL del objeto sin versión:

gs://bucket/object

La URL anterior podría tener dos versiones con estas URL específicas de la versión:

gs://bucket/object#1360383693690000
gs://bucket/object#1360383802725000

En las siguientes secciones, se analiza cómo trabajar con el control de versiones y el control de simultaneidad.

Control de versiones de objetos

Puedes ver, inhabilitar y habilitar el control de versiones de objetos en un depósito mediante los comandos “versioning get” y “versioning set”. Por ejemplo:

gsutil versioning set on gs://bucket

Con el comando anterior, se habilitará el control de versiones para el depósito con nombre. Consulta help y versioning de gsutil para obtener detalles adicionales.

A fin de ver todas las versiones de objetos en un depósito habilitado para el control de versiones junto con su información de generation.metageneration, usa gsutil ls -a:

gsutil ls -a gs://bucket

También puedes especificar objetos específicos para los que deseas encontrar las URL específicas de la versión, o puedes usar comodines:

gsutil ls -a gs://bucket/object1 gs://bucket/images/*.jpg

Los valores de la generación forman una secuencia que aumenta de forma monótona a medida que creas versiones de objetos adicionales. Debido a esto, la versión más reciente del objeto es siempre la última que aparece en el resultado de gsutil ls para un objeto en particular. Por ejemplo, si un depósito contiene estas tres versiones de gs://bucket/object:

gs://bucket/object#1360035307075000
gs://bucket/object#1360101007329000
gs://bucket/object#1360102216114000

entonces gs://bucket/object#1360102216114000 es la versión más reciente y gs://bucket/object#1360035307075000 es la versión disponible más antigua.

Si especificas URL sin versión con gsutil, solo operarás en la versión publicada de un objeto, por ejemplo:

gsutil cp gs://bucket/object ./dir

o:

gsutil rm gs://bucket/object

Lo mismo sucede cuando se usan comodines como * y **. Solo funcionarán en la versión publicada de los objetos que coincidan. Por ejemplo, mediante este comando se quitará la versión publicada y se creará una versión no actual para cada objeto en un depósito:

gsutil rm gs://bucket/**

Para operar en una versión de objeto específica, usa una URL específica de la versión. Por ejemplo, supongamos que el resultado del comando gsutil ls -a anterior es el siguiente:

gs://bucket/object#1360035307075000
gs://bucket/object#1360101007329000

En este caso, el comando:

gsutil cp gs://bucket/object#1360035307075000 ./dir

recuperará la segunda versión más reciente del objeto.

Ten en cuenta que las URL específicas de la versión no pueden ser el destino del comando gsutil cp (intentar hacer eso dará como resultado un error), ya que escribir en un objeto con versión siempre crea una versión nueva.

Además, ten en cuenta que algunas shells tratan “#” como un carácter especial (p. ej., zsh con la opción extendedglob habilitada). Si usas una shell que trata “#” como un carácter especial, deberás encerrar el argumento entre comillas, por ejemplo:

gsutil cp 'gs://bucket/object#1360035307075000' ./dir

Si se borró un objeto, no aparecerá en una lista de gsutil ls normal (es decir, ls sin la opción -a). Puedes restablecer un objeto borrado si ejecutas gsutil ls -a para encontrar las versiones disponibles y, luego, copias una URL específica de la versión en la URL sin versión, por ejemplo:

gsutil cp gs://bucket/object#1360101007329000 gs://bucket/object

Ten en cuenta que cuando haces esto, se crea una versión del objeto nueva, que generará cargos adicionales. Puedes deshacerte de la copia adicional si borras el objeto anterior específico de la versión:

gsutil rm gs://bucket/object#1360101007329000

O puedes combinar los dos pasos mediante el comando gsutil mv:

gsutil mv gs://bucket/object#1360101007329000 gs://bucket/object

Si quitas la versión publicada de un objeto en un depósito habilitado para el control de versiones, se conservará una versión no actual:

gsutil rm gs://bucket/object

Si quitas una URL específica de la versión de un objeto (incluso si es la versión publicada), esa versión se borrará de forma permanente:

gsutil rm gs://bucket/object#1360101007329000

Si deseas quitar todas las versiones de un objeto, usa la opción gsutil rm -a:

gsutil rm -a gs://bucket/object

Si quieres quitar todas las versiones de todos los objetos de un depósito (y también el depósito), usa la opción rm -r (-r implica la opción -a):

gsutil rm -r gs://bucket

Ten en cuenta que no existe un límite de cantidad de versiones anteriores de un objeto que crearás si continúas subiendo contenido al mismo objeto en un depósito habilitado para el control de versiones. Es tu responsabilidad borrar las versiones posteriores a las que deseas conservar.

Copia depósitos con versiones

Puedes copiar datos entre dos depósitos con versiones mediante un comando como el siguiente:

gsutil cp -r -A gs://bucket1/* gs://bucket2

Cuando se ejecuta mediante depósitos con versiones, este comando hará que se copie cada versión de los objetos. Las copias que se hicieron en gs://bucket2 tendrán números de generación diferentes (ya que se asigna una generación nueva cuando se realiza la copia del objeto), pero el orden de clasificación del objeto permanece coherente. Por ejemplo, gs://bucket1 podría contener lo siguiente:

% gsutil ls -la gs://bucket1 10  2013-06-06T02:33:11Z
53  2013-02-02T22:30:57Z  gs://bucket1/file#1359844257574000  metageneration=1
12  2013-02-02T22:30:57Z  gs://bucket1/file#1359844257615000  metageneration=1
97  2013-02-02T22:30:57Z  gs://bucket1/file#1359844257665000  metageneration=1

Luego de la copia, gs://bucket2 podría contener lo siguiente:

% gsutil ls -la gs://bucket2
53  2013-06-06T02:33:11Z  gs://bucket2/file#1370485991580000  metageneration=1
12  2013-06-06T02:33:14Z  gs://bucket2/file#1370485994328000  metageneration=1
97  2013-06-06T02:33:17Z  gs://bucket2/file#1370485997376000  metageneration=1

Ten en cuenta que las versiones de los objetos están en el mismo orden (como se puede ver en la misma secuencia de tamaños en ambas listas), pero los números de generación (y las marcas de tiempo) son más recientes en gs://bucket2.

Control de simultaneidad

Si compilas una aplicación mediante Cloud Storage, es posible que debas tener cuidado con el control de simultaneidad. Por lo general, gsutil no se usa para este fin, pero es posible escribir secuencias de comandos en torno a gsutil que realicen el control de simultaneidad.

Por ejemplo, supongamos que deseas implementar un sistema de “actualización progresiva” mediante gsutil, en el que un trabajo periódico calcula algunos datos y los sube a la nube. En cada ejecución, el trabajo comienza con los datos que procesó en la última ejecución y calcula un valor nuevo. Para hacer que este sistema sea sólido, necesitas tener varias máquinas en las que se pueda ejecutar el trabajo, lo que aumenta la posibilidad de que dos ejecuciones simultáneas intenten actualizar un objeto al mismo tiempo. Esto genera la siguiente condición de carrera probable:

  • El trabajo 1 calcula el valor nuevo que se escribirá.
  • El trabajo 2 calcula el valor nuevo que se escribirá.
  • El trabajo 2 escribe el valor nuevo.
  • El trabajo 1 escribe el valor nuevo.

En este caso, el valor que lee el trabajo 1 ya no es actual en el momento en que escribe el objeto actualizado, y la escritura en este punto dará como resultado datos inactivos (o dañados, según la aplicación).

A fin de evitar esto, puedes encontrar el nombre específico de la versión del objeto creado y, luego, usar la información contenida en esa URL para especificar un encabezado x-goog-if-generation-match en un comando gsutil cp posterior. Puedes hacerlo en dos pasos. Primero, usa la opción gsutil cp -v en el momento de la carga para obtener el nombre específico de la versión del objeto creado, por ejemplo:

gsutil cp -v file gs://bucket/object

El comando anterior podría dar como resultado lo siguiente:

Created: gs://bucket/object#1360432179236000

Puedes extraer el valor de generación de este objeto y, luego, ejecutar un comando de gsutil posterior como el siguiente:

gsutil -h x-goog-if-generation-match:1360432179236000 cp newfile \
    gs://bucket/object

Este comando solicita a Cloud Storage que intente subir el archivo nuevo, pero que falle la solicitud si la generación del archivo nuevo que está publicada en el momento de la carga no coincide con la especificada.

Si el comando que usas actualiza los metadatos de objetos, deberás encontrar la metageneración actual de un objeto. Para ello, usa las opciones ls -a y -l de gsutil. Por ejemplo:

gsutil ls -l -a gs://bucket/object

El comando anterior generará un resultado similar al siguiente:

  64  2013-02-12T19:59:13Z  gs://bucket/object#1360699153986000  metageneration=3
1521  2013-02-13T02:04:08Z  gs://bucket/object#1360721048778000  metageneration=2

Con esta información, puedes usar el siguiente comando para solicitar la configuración de la LCA en la versión anterior del objeto, de modo que el comando fallará, a menos que sea la versión actual de los datos más los metadatos:

gsutil -h x-goog-if-generation-match:1360699153986000 -h \
  x-goog-if-metageneration-match:3 acl set public-read \
  gs://bucket/object#1360699153986000

Sin agregar estos encabezados, la actualización solo reemplazaría la LCA existente. Ten en cuenta que, en cambio, el comando “gsutil acl ch” usa estos encabezados de forma automática, ya que realiza un ciclo de lectura-modificación-escritura para editar las LCA.

Si quieres experimentar con el funcionamiento de las generaciones y las metageneraciones, prueba lo siguiente. Primero, sube un objeto; luego, usa gsutil ls -l -a para enumerar todas las versiones del objeto, junto con la metageneración de cada versión. Una vez que hayas hecho esto, vuelve a subir el objeto y repite el comando gsutil ls -l -a. Deberías ver dos versiones de objeto, cada una con metageneración=1. Ahora intenta configurar la LCA y vuelve a ejecutar gsutil ls -l -a. Deberías ver que la generación de objetos más reciente ahora tiene metageneración=2.

Más información

Para obtener más detalles sobre cómo usar el control de versiones y las condiciones previas, consulta https://cloud.google.com/storage/docs/object-versioning.