Números de generación y condiciones previas

Los números de generación de objetos permiten a los usuarios identificar de forma única los recursos de datos y aplicar condiciones previas para prevenir daños en los datos o condiciones de carrera.

Generaciones

Incluso sin el control de versiones de los objetos habilitado, todos los objetos de Cloud Storage tienen números de generación y de metageneración. El número de generación cambia cada vez que se reemplaza el objeto y el de metageneración cambia cada vez que se actualizan los metadatos del objeto.

Dado que los números de metageneración de objetos se restablecen en uno para cada generación de objetos nueva, son significativos solo cuando se sincronizan con un número de generación.

Los bucket s también conservan una cantidad de metageneraciones que permite a los usuarios identificar de forma única el estado de los metadatos de un bucket. Dado que los buckets no tienen datos de carga útil y, por lo tanto, tampoco números de generación, sus números de metageneración son significativos por sí mismos.

Para ver un ejemplo de cómo obtener los metadatos de un objeto, incluido el número de generación, consulta Visualiza y edita metadatos de objetos.

Ejemplo: carga en paralelo

En las cargas en paralelo, divides un objeto en varios fragmentos, subes los fragmentos en una ubicación temporal de forma simultánea y usas compose en el objeto original a partir de estos fragmentos temporales. Si un proceso independiente usa de forma involuntaria el mismo nombre que uno o más fragmentos temporales que subiste, luego, cuando intentes usar compose en el objeto, se usarán componentes incorrectos y el objeto se dañará.

Mediante el uso de los números de generación, se evita que suceda este daño. Si incluyes el número de generación de cada fragmento subido cuando realizas tu solicitud compose, compose ocurre con los fragmentos correctos o la solicitud falla con una respuesta 404 Not Found.

Preconditions

Las condiciones previas indican a Cloud Storage que solo realice una solicitud si el número de generación o metageneración del objeto afectado cumple con tus criterios de condición previa. Estas verificaciones de los números de generación y metageneración garantizan que el objeto se encuentre en el estado esperado, lo que te permite realizar actualizaciones seguras de lectura, modificación y escritura y operaciones condicionales en los objetos.

Cuando una condición previa Match usa un número de generación o metageneración específico, el objeto de Cloud Storage al que la solicitud aplica debe tener el mismo número de generación y metageneración. Si lo hace, la solicitud se ejecuta de forma correcta. Si no lo hace, la solicitud falla y se muestra un mensaje de error 412 Precondition Failed.

Cuando una condición previa Match usa el valor 0 en lugar de un número de generación, la solicitud solo se realiza de forma correcta si no hay objetos activos en el bucket de Cloud Storage con el nombre especificado en la solicitud. La condición previa Match=0 se puede usar para las solicitudes GET, pero incluso si pasa la condición previa, la solicitud general puede fallar si no es válida. Por ejemplo, si intentas descargar un objeto que no existe mientras usas Match=0, se genera una respuesta 404.

Cuando una condición previa NotMatch usa un número de generación o metageneración que coincide con el valor de un objeto de Cloud Storage existente, se muestra una respuesta 304.

Las condiciones previas, a menudo, se usan en las solicitudes cambiantes, cargas, borrado, copias o actualizaciones de metadatos, para evitar las condiciones de carrera. Las condiciones de carrera pueden surgir cuando la misma solicitud se envía varias veces o cuando los procesos independientes interfieren entre sí. Por ejemplo, los reintentos de varias solicitudes después de una interrupción en la red, o los usuarios que realizan una operación de lectura-modificación-escritura en el mismo objeto pueden crear condiciones de carrera.

Además de las condiciones previas que usan los números de generación y metageneración, también existen condiciones previas disponibles que usan ETag. Las ETag de la API de XML para objetos no compuestos solo cambian cuando cambia el contenido, mientras que las ETag de los objetos compuestos y los recursos de la API de JSON cambian siempre que el contenido o los metadatos cambien. Para obtener más información sobre las ETag, consulta Prácticas recomendadas para ETags y Hash.

Costo de condiciones previas

Las condiciones previas tienen un costo de rendimiento y facturación: para cada operación cambiante, también envías una solicitud de metadatos GET facturable a fin de determinar el número de generación y metageneración del objeto. Como una consideración de rendimiento, las condiciones previas pueden duplicar de manera potencial la parte de la red de la latencia general de la operación si agregas un recorrido de ida y vuelta adicional, que puede ser un factor importante en las operaciones sensibles a la latencia. Como consideración sobre los precios, la solicitud de metadatos GET que te permite usar las condiciones previas se factura según una tarifa de $0.004 por 10,000 operaciones.

Según tu aplicación, existen maneras de evitar los costos de rendimiento y facturación asociados con el uso de condiciones previas, como las que se detallan a continuación:

  • Almacenar los números de generación y metageneración de tus objetos de forma local a fin de que conozcas los números correctos para usar en tu condición previa.
  • Usar un esquema de nombres que evite más de un cambio del nombre del mismo objeto para que no tengas que usar las condiciones previas
  • Tener conocimiento de la aplicación sobre qué objetos se crearon recientemente, a fin de que sepas cuándo usar la condición previa if-generation-match:0
  • Recordar los resultados de las llamadas GET realizadas antes de los cambios

Condiciones previas en la API de XML

En la API de XML, los números de generación y metageneración se exponen mediante los encabezados de las respuestas x-goog-generation y x-goog-metageneration. Estos encabezados se muestran en la respuesta de una HEAD para un objeto.

Consulta la Referencia de encabezados HTTP si deseas obtener una lista completa de encabezados de solicitudes de condiciones previas que puedes usar para hacer que la solicitud sea condicional al estado del objeto solicitado. Por ejemplo, puede hacer lo siguiente:

  • Usar las condiciones previas x-goog-if-generation-match para ejecutar una solicitud solo si el número de metageneración en el encabezado coincide con el número de metageneración del objeto solicitado. Si usas 0 en lugar de un número de generación, la solicitud solo se realiza de forma correcta si no existe un objeto activo en tu bucket que coincida con el objeto nombrado en la solicitud.

  • Usa las condiciones previas x-goog-if-metageneration-match para ejecutar una solicitud solo si el número de metageneración en el encabezado coincide con el número de metageneración del objeto solicitado.

  • Usa la condición previa If-Modified-Since con solicitudes GET o HEAD. Estas solicitudes se ejecutan solo si el momento de creación para la generación del objeto más reciente, es decir, la última modificación del objeto, sucedió con anterioridad al tiempo especificado en la condición previa.

  • Usa ETags y las condiciones previas If-Match o If-None-Match con solicitudes GET o HEAD. Estas solicitudes se ejecutan solo si el objeto solicitado coincide o no con la ETag especificada en la condición previa.

  • Usa varias condiciones previas en la misma solicitud. Por ejemplo, si usas x-goog-if-generation-match, también puedes usar x-goog-if-metageneration-match.

Condiciones previas en la API de JSON

En la API de JSON, puedes obtener los números de generación y de metageneración mediante las propiedades generation y metageneration de una respuesta que contiene un recurso de objeto o de bucket . Un recurso de objeto o de bucket se muestra en el cuerpo de la respuesta de una solicitud GET para el objeto o el bucket.

Los siguientes ejemplos muestran cómo recuperar la información de un objeto mediante solicitudes a la API de JSON con parámetros de consulta que actúan como condiciones previas. Puedes usar los parámetros de consulta ifGenerationMatch, ifGenerationNotMatch, ifMetagenerationMatch y ifMetagenerationNotMatch para operaciones como compose, insert o rewrite. Para obtener más información, consulta la página de referencia de objetos de la API de JSON.

ifGenerationMatch

Esta solicitud la API de JSON usa la condición previa ifGenerationMatch. La API completa esta solicitud solo para el objeto con el número de generación que proporcionaste:

  1. Obtén un token de autorización de acceso de OAuth 2.0 Playground. Configura Playground para usar tus credenciales de OAuth.
  2. Usa cURL para llamar a la API de JSON con una solicitud de objeto GET:

      curl \
      'https://storage.googleapis.com/storage/v1/b/BUCKET_NAME/o/OBJECT_NAME?ifGenerationMatch=GENERATION' \
      --header 'Authorization: Bearer OAUTH2_TOKEN' \
      --header 'Accept: application/json' \
      --compressed
    

    Aquí:

    • BUCKET_NAME es el nombre del bucket en el que se encuentra el objeto. Por ejemplo, my-bucket.
    • OBJECT_NAME es el nombre del objeto para el que deseas recuperar información. Por ejemplo, dog.png.
    • OAUTH2_TOKEN es el token de acceso que generaste en el paso 1.
    • GENERATION es el número de generación del objeto para el que deseas recuperar la información. Por ejemplo, 1122334455667788.

      Si no se encuentra ningún objeto que coincida con la condición previa dada, en este caso el número de generación se mostrará con el siguiente mensaje de error:

      "message": "Precondition Failed"

ifMetagenerationNotMatch

Esta es una solicitud que usa la condición previa ifMetagenerationNotMatch, un parámetro de búsqueda que hace que el éxito de la solicitud dependa de que el número de metageneración de un objeto sea diferente al número especificado en la condición previa. Este parámetro te permite excluir una versión específica del objeto de la consulta:

  1. Obtén un token de autorización de acceso de OAuth 2.0 Playground. Configura Playground para usar tus credenciales de OAuth.
  2. Usa cURL para llamar a la API de JSON con una solicitud de objeto GET:

      curl \
      'https://storage.googleapis.com/storage/v1/b/BUCKET_NAME/o/OBJECT_NAME?ifMetagenerationNotMatch=METAGENERATION' \
      --header 'Authorization: Bearer OAUTH2_TOKEN' \
      --header 'Accept: application/json' \
      --compressed
    

    Aquí:

    • BUCKET_NAME es el nombre del bucket en el que se encuentra el objeto. Por ejemplo, my-bucket.
    • OBJECT_NAME es el nombre del objeto para el que deseas recuperar información. Por ejemplo, dog.png.
    • OAUTH2_TOKEN es el token de acceso que generaste en el paso 1.
    • METAGENERATION es el número de metageneración de un objeto. Cuando el objeto de Cloud Storage especificado tiene este número de metageneración, la solicitud falla. Por ejemplo, 5.

      Si el número de metageneración proporcionado coincide con el número de metageneración del objeto de Cloud Storage especificado, se muestra una respuesta 304.

Limitaciones

Ten en cuenta que no se aceptan las condiciones previas de generación y metageneración para las operaciones de la LCA; en su lugar, usa el recurso de entrada de control de acceso de ETag. Puedes encontrarlo dentro de los recursos de entrada de control de acceso, a los que también se puede acceder desde el objeto los contiene o el recurso del bucket.

ETags HTTP 1.1

La API de JSON también es compatible con las ETag HTTP 1.1 y los encabezados HTTP If-Match y también If-None-Match correspondientes para todos los recursos, incluidos los buckets, objetos y LCA. Se muestra una ETag como parte del encabezado de respuesta cuando se muestra un recurso, como también se incluye en el mismo recurso.

Usa ETags y las condiciones previas If-Match o If-None-Match como encabezados en las solicitudes. Estas solicitudes se ejecutan solo si el objeto solicitado coincide o no con la ETag especificada en la condición previa.

Ejemplos de condiciones de carrera

En la siguiente sección, se exploran las condiciones de carrera que debes considerar.

Lectura, modificación y escritura en simultáneo

Un patrón común para actualizar los metadatos del objeto o del bucket implica la lectura del estado actual, la aplicación de modificaciones de forma local y el envío de metadatos modificados a Cloud Storage para su escritura. Esto puede ser precario si dos o más procesos independientes intentan ejecutar la secuencia al mismo tiempo.

Considera el ejemplo siguiente: deseas agregar una entrada de LCA para un colaborador a fin de que pueda acceder a tu bucket. Al mismo tiempo, un compañero de trabajo desea quitar a un colaborador diferente que ya no necesita acceder al bucket.

Para ello, tú y tu compañero de trabajo leen el mismo estado inicial de los metadatos del bucket, y cada uno realiza la modificación deseada en las entradas de LCA del bucket. Cuando escribes las modificaciones en Cloud Storage, se actualizan los metadatos de forma correcta. Desafortunadamente, tus cambios se perderán en cuanto tu compañero de trabajo suba sus modificaciones, ya que no tenía forma de tener en cuenta tu actualización. Como resultado, se pierde la entrada de la LCA para tu colaborador. Ya no podrá acceder a tu bucket, y nadie estará al tanto de lo que suceda (sin volver y observar las entradas de la LCA).

Prevención de la condición de carrera

Tú y tu compañero de trabajo pueden evitar esta condición de carrera si agregan una condición previa if-metageneration-match a cada una de las operaciones de escritura. En la condición previa, ambos usan el número de metageneración del bucket, que es parte de los metadatos que recibiste en la operación de lectura inicial.

Cuando tus modificaciones se agregan a la entrada de la LCA, el número de metageneración del depósito cambia. Ahora que se usan las condiciones previas, cuando tu compañero de trabajo escribe su versión de las entradas de la LCA, el número de metageneración del bucket no coincidirá con el número en la condición previa y se le informará de la actualización con errores con un código de respuesta 412 Precondition Failed. Tras recibir este código de respuesta, tu compañero de trabajo puede reaccionar en consecuencia, por ejemplo, puede realizar un ciclo nuevo de lectura, modificación y escritura con los metadatos actualizados.

Reintentos de solicitudes múltiples

Cloud Storage es un sistema distribuido. Debido a que las solicitudes pueden fallar por las condiciones de la red o del servicio, Google recomienda reintentar los errores con una retirada exponencial. Sin embargo, debido a la naturaleza de los sistemas distribuidos, en ocasiones, estos reintentos pueden generar un comportamiento inesperado.

Considera el ejemplo siguiente: deseas borrar un archivo, file.txt, almacenado en Cloud Storage. Después, deseas agregar un archivo nuevo con el mismo nombre a Cloud Storage.

A fin de lograrlo, debes enviar una solicitud de borrado para el objeto. Sin embargo, una condición de red, como un enrutador intermedio que pierde conectividad de manera temporal, evita que la solicitud llegue a Cloud Storage, y tú no recibes respuesta alguna.

Debido a que no recibiste una respuesta a la primera solicitud, envías una segunda solicitud de borrado para el objeto, que se realiza de forma correcta y recibes una respuesta de confirmación. Un minuto después, decides subir un archivo file.txt nuevo, y la carga se realiza de forma correcta.

Una condición de carrera surge si el enrutador que perdió conectividad vuelve a conectarse y envía la solicitud de eliminación original, que aparentemente se había perdido, a Cloud Storage. Cuando la solicitud llega a Cloud Storage, se ejecuta de forma correcta porque hay un file.txt nuevo. Cloud Storage envía una respuesta que no recibes porque tu cliente dejó de escucharla. El archivo nuevo se borra, al contrario de tu intención, y no sabes que se produjo la segunda operación de eliminación.

En el diagrama siguiente, se muestra lo que sucedió:

Prevención de la condición de carrera

A fin de evitar que suceda la anterior situación descrita, debes comenzar con la obtención de los metadatos de file.txt para determinar su generación actual. Luego, debes enviar la solicitud de borrado con una condición previa if-generation-match que use el número de generación. El uso de la condición previa garantiza que solo el objeto con el número de generación específico se borre, sin importar cuándo llega la solicitud de borrado a Cloud Storage o cuántas veces se envía con la condición previa. Con la condición previa if-generation-match, cualquier intento involuntario por cambiar una generación diferente de file.txt falla con el código de respuesta 412 Precondition Failed.

Dado que las interrupciones de la red similares pueden ocasionar condiciones de carrera para la solicitud de carga que siguió a tu solicitud de borrado, puedes evitar muchas de estas con una condición if-generation-match:0 aplicada a la solicitud de carga. El uso de esta condición previa garantiza que los reintentos de la carga no escriban dos veces el objeto de forma accidental, porque la condición previa permite que la solicitud se realice de forma correcta, solo si no existen generaciones actuales del objeto.

Con estas condiciones previas implementadas, proteges tus datos a fin de evitar que se pierdan de forma accidental cuando se realizan las solicitudes de borrado y carga. Esto puede verse en el diagrama siguiente:

Limitaciones de if-generation-match:0

if-generation-match:0 no puede evitar que la creación del objeto se produzca dos veces si el primer objeto se borra, debido a que la ausencia del objeto no se identifica de forma única. Considera el caso siguiente, en el que ningún dato se pierde, pero obtienes un archivo que no esperabas:

  1. Comienzas con una solicitud GET para que los metadatos de file.txt encuentren su número de generación. En la respuesta, te das cuenta de que file.txt no existe.

  2. Ante esto, realizas una solicitud para subir file.txt con la condición previa if-generation-match:0, pero el tiempo de espera de la solicitud se agota cuando un enrutador intermedio pierde conectividad de forma temporal.

  3. Si fallas la primera vez, vuelve a realizar la solicitud de carga, otra vez con la condición previa if-generation-match:0. Esta vez, la solicitud se realiza de forma correcta.

  4. Poco después, envía una solicitud para borrar file.txt, que se realiza de forma correcta.

  5. Si el enrutador que perdió conectividad ahora se conecta de nuevo y envía tu primera solicitud de carga a Cloud Storage, la condición previa que acompañó a la solicitud sigue siendo una coincidencia, por lo que se volverá a crear file.txt. Con o sin la condición previa, file.txt se sube de manera inesperada una segunda vez.