Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

205
Vistas
Actualizaciones simultáneas en DynamoDB con expresión condicional en algún momento que pasan

Tengo un problema en el que dos procesos simultáneos actualizan una tabla de DynamoDB con una diferencia de 5 ms entre sí y ambos pasan la expresión condicional cuando espero que uno arroje la excepción ConditionalCheckFailedException . La documentación dice:

DynamoDB admite mecanismos, como las escrituras condicionales, que son necesarios para los bloqueos distribuidos.

https://aws.amazon.com/blogs/database/building-distributed-locks-with-the-dynamodb-lock-client/

El esquema de mi tabla tiene un solo atributo clave llamado "Id":

 AttributeDefinitions: - AttributeName: "Id" AttributeType: "S" KeySchema: - AttributeName: "Id" KeyType: "HASH"

Mi expresión condicional es:

 string conditional = "attribute_not_exists(StartedRefreshingAt)";

Lock Method que incluye la expresión condicional:

 private bool Lock(Configuration config) { string conditional = "attribute_not_exists(StartedRefreshingAt)"; Dictionary<string, AttributeValue> values = new Dictionary<string, AttributeValue>{ {":new_refresh", new AttributeValue(ToDDBDate(DateTime.Now))}}; try { _dynamoDB.UpdateItemAsync(new UpdateItemRequest { TableName = TABLE_NAME, Key = new Dictionary<string, AttributeValue>{{"Id", new AttributeValue(config.Id)}}, UpdateExpression = "set StartedRefreshingAt = :new_refresh", ConditionExpression = conditional, ExpressionAttributeValues = values }).Wait(); return true; } catch (Exception) { return false; } }

Si devuelve verdadero, considero que la tabla está bloqueada ya que ahora existe el atributo StartedRefreshingAt .

Después de completar algunas otras actualizaciones del registro en otros atributos, elimino nuevamente el atributo StartedRefreshingAt , liberando efectivamente el bloqueo. Este es el método que utiliza el método de bloqueo:

 private async Task<Configuration> RefreshAsync(Configuration config) { // concurrent executions may enter here with the same value // for config if (config.AccessTokenExpired()) // Validates the age of the config.AccessToken { _logger.LogInformation($"Refreshing expired access token for {config.Id}"); if (Lock(config)) { // The below code should not be executed concurrently var authPayload = new List<KeyValuePair<string, string>>(); authPayload.Add(new KeyValuePair<string, string>("grant_type", "refresh_token")); authPayload.Add(new KeyValuePair<string, string>("refresh_token", config.RefreshToken)); // 3rd party REST API call // IF execution 1 completed and saved first, the below API // call should fail with an "invlaid grant" response // since it will be using a stale refresh token. JObject authResponse = await GetAuthTokensAsync(authPayload); // Bad practice, but print the auth tokens to logs here // in case we need to recover a RefreshToken. _logger.LogInformation($"Got auth token response for {config.Id}:" + $" {authResponse.ToString(Formatting.None)}"); config.AccessToken = authResponse["access_token"].ToString(); config.AccessTokenCreated = DateTime.Now; // RefreshToken is updated whenever the API call succeeds. config.RefreshToken = authResponse["refresh_token"].ToString(); config.RefreshTokenCreated = config.AccessTokenCreated; config.StartedRefreshingAt = null; // lock attribute await Save(config); // saves to DynamoDB, releasing the lock. } else { _logger.LogInformation($"Someone else is refreshing {config.Id} at same time, so ignoring and returning current value"); // this will return a potentially stale config object, which client code is expected to handle } } return config; }

La mayoría de las veces, cuando se ejecutan dos ejecuciones simultáneas, este código devuelve falso en una de las dos ejecuciones, en promedio, unas 20 veces al día. Eso parece indicar que la expresión se está evaluando correctamente. Pero aproximadamente una vez cada 2 semanas, las ejecuciones simultáneas vuelven a ser verdaderas.

EDITAR : después de revisar la respuesta y proporcionar más contexto de código, lo más probable es que el problema no sea DynamoDB, y la escritura condicional de la ejecución 2 se realiza correctamente DESPUÉS de que la ejecución 1 haya liberado el bloqueo. Podría ser un problema de coherencia con el servidor OAuth de terceros.

Ahora creo que el problema es que la ejecución 2 completa la llamada a la API de OAuth correctamente usando el mismo token de actualización que la ejecución 1. Esto no debería ser posible.

El resultado final es que tengo un RefreshToken guardado en DynamoDB que devuelve perpetuamente "concesión no válida", lo que significa que está obsoleto. Si miro a través de los registros cuando la línea de registro "Obtuve la respuesta del token de autenticación para" fue escrita por dos ejecuciones muy juntas, puedo actualizar la tabla de DynamoDB manualmente con el RefreshToken que se registró primero, se recupera.

El escenario es bastante clásico para una condición de carrera:

  1. la ejecución 1 establece el atributo StartedRefreshingAt y devuelve verdadero, continúa para completar el trabajo adicional
  2. la ejecución 2 establece el atributo StartedRefreshingAt y devuelve verdadero, continúa para completar el trabajo adicional
  3. la ejecución 2 escribe los resultados del trabajo adicional en la tabla
  4. la ejecución 1 vuelve a escribir los resultados del trabajo adicional en la tabla, sobrescribiendo el trabajo más reciente realizado por la ejecución 2.

También es posible que a veces los pasos 3 y 4 se ejecuten a la inversa, pero eso no representa un problema de coherencia para mí, ya que el trabajo más reciente realizado es el resultado final deseado.

over 4 years ago · Santiago Trujillo
1 Respuestas
Responde la pregunta

0

La carrera que está sugiriendo es muy sorprendente, porque es exactamente lo que DynamoDB afirma que evitan sus actualizaciones condicionales. Entonces, Amazon tiene un error grave en su implementación (lo que sería sorprendente, pero no imposible), o la carrera es realmente diferente a la que describiste en tu pregunta.

En su línea de tiempo, no dijo cómo su código restablece "StartedRefreshingAt" a nada. ¿La misma operación UpdateTable que vuelve a escribir los resultados del trabajo en la tabla también elimina el atributo StartedRefreshingAt? Porque si se trata de una escritura separada, es teóricamente posible (incluso si no es común) que las dos escrituras se reordenen. Si StartedRefreshingAt se elimina primero, en ese momento el segundo proceso puede comenzar su propio trabajo, antes de que se escribieran los resultados del primer proceso, por lo que puede ocurrir el problema que describió.

Otra cosa que no dijo es cómo su procesamiento lee el trabajo del elemento. Si accidentalmente usó la consistencia eventual para la lectura, en lugar de la consistencia fuerte, es posible que la ejecución 2 realmente comenzara después de que finalizó la ejecución 1, pero cuando leyó el trabajo que debe hacer, leyó nuevamente el valor anterior y no lo que la ejecución 1 escribió, por lo que la ejecución 2 terminó repitiendo el trabajo de 1 en lugar de hacer un nuevo trabajo.

No sé si alguna de estas conjeturas tiene sentido porque no conozco los detalles de su aplicación, pero creo que la posibilidad de que la coherencia de DynamoDB simplemente no funcione según lo prometido es la última suposición que haría.

over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda