2012-04-26 17 views
9

Estoy tratando de actualizar un contador de conteos atómicos con Python Boto 2.3.0, pero no puedo encontrar documentación para la operación.Actualizar el Contador Atómico DynamoDB con Python/Boto

Parece que no hay una interfaz directa, así que traté de ir a actualizaciones "en bruto" usando la interfaz de capa1, pero no pude completar ni siquiera una actualización simple.

probé las siguientes variaciones, pero todas sin suerte

dynoConn.update_item(INFLUENCER_DATA_TABLE, 
        {'HashKeyElement': "9f08b4f5-d25a-4950-a948-0381c34aed1c"}, 
        {'new': {'Value': {'N':"1"}, 'Action': "ADD"}})  

dynoConn.update_item('influencer_data', 
        {'HashKeyElement': "9f08b4f5-d25a-4950-a948-0381c34aed1c"}, 
        {'new': {'S' :'hello'}})         

dynoConn.update_item("influencer_data", 
        {"HashKeyElement": "9f08b4f5-d25a-4950-a948-0381c34aed1c"}, 
        {"AttributesToPut" : {"new": {"S" :"hello"}}})  

Todos ellos producen el mismo error:

File "/usr/local/lib/python2.6/dist-packages/boto-2.3.0-py2.6.egg/boto/dynamodb/layer1.py", line 164, in _retry_handler 
    data) 
boto.exception.DynamoDBResponseError: DynamoDBResponseError: 400 Bad Request 
{u'Message': u'Expected null', u'__type': u'com.amazon.coral.service#SerializationException'} 

También investigaron los documentos de la API here pero eran bastante espartano.

He hecho muchas búsquedas y manipulaciones, y lo único que me queda es usar PHP API y bucear en el código para encontrar dónde "formatea" el cuerpo JSON, pero eso es un poco dolor. Por favor, sálvame de ese dolor!

Respuesta

12

Disculpa, no entendí lo que estabas buscando. Puede lograr esto a través de layer2 aunque hay un pequeño error que debe abordarse. Aquí hay un código de capa 2:

>>> import boto 
>>> c = boto.connect_dynamodb() 
>>> t = c.get_table('counter') 
>>> item = t.get_item('counter') 
>>> item 
{u'id': 'counter', u'n': 1} 
>>> item.add_attribute('n', 20) 
>>> item.save() 
{u'ConsumedCapacityUnits': 1.0} 
>>> item # Here's the bug, local Item is not updated 
{u'id': 'counter', u'n': 1} 
>>> item = t.get_item('counter') # Refetch item just to verify change occurred 
>>> item 
{u'id': 'counter', u'n': 21} 

Esto da lugar a la misma petición sobre el alambre que se está realizando en el código de la capa 1, como se muestra en la siguiente salida de depuración.

2012-04-27 04:17:59,170 foo [DEBUG]:StringToSign: 
POST 
/

host:dynamodb.us-east-1.amazonaws.com 
x-amz-date:Fri, 27 Apr 2012 11:17:59 GMT 
x-amz-security- token:<removed> == 
x-amz-target:DynamoDB_20111205.UpdateItem 

{"AttributeUpdates": {"n": {"Action": "ADD", "Value": {"N": "20"}}}, "TableName": "counter", "Key": {"HashKeyElement": {"S": "counter"}}} 

Si se quiere evitar la llamada GetItem inicial, se puede hacer esto en su lugar:

>>> import boto 
>>> c = boto.connect_dynamodb() 
>>> t = c.get_table('counter') 
>>> item = t.new_item('counter') 
>>> item.add_attribute('n', 20) 
>>> item.save() 
{u'ConsumedCapacityUnits': 1.0} 

que actualizará el artículo si ya existe o crearlo si no existe todavía.

+0

gamaat, gracias! Vi estas operaciones pero no pensé que funcionarían de esta manera. ¡Gracias de nuevo! –

+1

Mientras multi-threading realiza esta operación al mismo tiempo, puede no funcionar debido a la opción 'expect' en el método save(). – Zagfai

0

No hay una función de alto nivel en DynamoDB para contadores atómicos. Sin embargo, puede implementar un contador atómico utilizando la función de escritura condicional. Por ejemplo, digamos que una tabla con una clave hash de cadena llamada así.

>>> import boto 
>>> c = boto.connect_dynamodb() 
>>> schema = s.create_schema('id', 's') 
>>> counter_table = c.create_table('counter', schema, 5, 5) 

Ahora escribe un artículo a la tabla que incluye un atributo llamado 'n' cuyo valor es cero.

>>> n = 0 
>>> item = counter_table.new_item('counter', {'n': n}) 
>>> item.put() 

Ahora, si quiero actualizar el valor de mi contador, me gustaría realizar una operación de escritura condicional que se golpee el valor de 'n' a 1 si y sólo si su valor actual de acuerdo con mi idea de su valor actual .

>>> n += 1 
>>> item['n'] = n 
>>> item.put(expected_value={'n': n-1}) 

Esto establecerá el valor de 'n' en el elemento a 1, pero sólo si el valor actual en el DynamoDB es cero. Si el valor ya había sido incrementado por otra persona, la escritura fallaría y, a continuación, necesitaría aumentar el contador local e intentar nuevamente.

Esto es algo complicado, pero todo esto podría incluirse en algún código para que sea mucho más simple de usar. Hice algo similar para SimpleDB que se puede encontrar aquí:

http://www.elastician.com/2010/02/stupid-boto-tricks-2-reliable-counters.html

probablemente debería tratar de actualizar ese ejemplo para usar DynamoDB

+0

Gamaat, muchas gracias por la respuesta, pero esto no es realmente un contador atómico. Esta es una operación de actualización estándar y tiene un costo muy alto asociado. Los costos se pagan al cliente para "asegurar" un atómico donde los contadores atómicos ofrecen esta función para "gratis". –

+0

DynamoDB realmente * tiene * contadores atómicos, solo necesitan acceder a través de la API de nivel inferior. – Mike

+0

(Error grave) [http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.AtomicCounters] – meawoppl

5

Para aquellos que buscan la respuesta que he encontrado. Primera NOTA IMPORTANTE, actualmente estoy al tanto de lo que está pasando, pero por el momento, para obtener una instancia layer1 he tenido que hacer lo siguiente:

import boto 
AWS_ACCESS_KEY=XXXXX 
AWS_SECRET_KEY=YYYYY 
dynoConn = boto.connect_dynamodb(AWS_ACCESS_KEY, AWS_SECRET_KEY) 
dynoConnLayer1 = boto.dynamodb.layer1.Layer1(AWS_ACCESS_KEY, AWS_SECRET_KEY) 

Esencialmente instancias de un capa2 primero y luego una capa 1. Tal vez estoy haciendo algo estúpido, pero en este punto estoy feliz de tenerlo funcionando ... Voy a ordenar los detalles más adelante. ENTONCES...para hacer realidad la atómica llamada actualización:

dynoConnLayer1.update_item("influencer_data", 
        {"HashKeyElement":{"S":"9f08b4f5-d25a-4950-a948-0381c34aed1c"}}, 
        {"direct_influence": 
         {"Action":"ADD","Value":{"N":"20"}} 
        } 
       ); 

Nota en el ejemplo anterior Dynamo se sumará 20 a lo que cada vez el valor actual es y esta operación será significado atómica otras operaciones que suceden en el "mismo tiempo" será correctamente "programado" para que ocurra después de que el nuevo valor se haya establecido como +20 O antes de que se ejecute esta operación. De cualquier forma, se logrará el efecto deseado.

Asegúrese de hacer esto en la instancia de la conexión de capa 1 ya que la capa2 emitirá errores dado que espera un conjunto diferente de tipos de parámetros.

¡Eso es todo! Para que la gente lo sepa, lo descubrí usando el PHP SDK. Toma muy poco tiempo instalar y configurar AND AND THEN cuando realiza una llamada, los datos de depuración realmente le mostrarán el formato del cuerpo de solicitud HTTP para que pueda copiar/modelar sus parámetros layer1 después del ejemplo. Aquí está el código que solía hacer la actualización atómica en PHP:

<?php 
    // Instantiate the class 
    $dynamodb = new AmazonDynamoDB(); 

    $update_response = $dynamodb->update_item(array(
     'TableName' => 'influencer_data', 
      'Key' => array(
       'HashKeyElement' => array(
        AmazonDynamoDB::TYPE_STRING=> '9f08b4f5-d25a-4950-a948-0381c34aed1c' 
       ) 
      ), 
      'AttributeUpdates' => array(
       'direct_influence' => array(
        'Action' => AmazonDynamoDB::ACTION_ADD, 
        'Value' => array(
         AmazonDynamoDB::TYPE_NUMBER => '20' 
        ) 
       ) 
      ) 
    )); 

    // status code 200 indicates success 
    print_r($update_response); 

?> 

Esperamos que esto ayudará al otro hasta que la interfaz de capa 2 Boto se pone al día ... o alguien que simplemente se da cuenta de cómo hacerlo en el nivel 2: -)

0

No estoy seguro de que este sea realmente un contador atómico, ya que cuando incrementa el valor de 1, otra llamada podría incrementar el número en 1, de modo que cuando "obtiene" el valor, no es el valor eso es de esperar

Por ejemplo, poniendo el código de garnaat, que está marcado como la respuesta aceptada, veo que cuando lo pones en un hilo, no funciona:

class ThreadClass(threading.Thread): 
    def run(self): 
     conn = boto.dynamodb.connect_to_region(aws_access_key_id=os.environ['AWS_ACCESS_KEY'], aws_secret_access_key=os.environ['AWS_SECRET_KEY'], region_name='us-east-1') 
     t = conn.get_table('zoo_keeper_ids') 
     item = t.new_item('counter') 
     item.add_attribute('n', 1) 
     r = item.save() #- Item has been atomically updated! 
     # Uh-Oh! The value may have changed by the time "get_item" is called! 
     item = t.get_item('counter') 
     self.counter = item['n'] 
     logging.critical('Thread has counter: ' + str(self.counter)) 

tcount = 3 
threads = [] 
for i in range(tcount): 
    threads.append(ThreadClass()) 

# Start running the threads: 
for t in threads: 
    t.start() 

# Wait for all threads to complete: 
for t in threads: 
    t.join() 

#- Now verify all threads have unique numbers: 
results = set() 
for t in threads: 
    results.add(t.counter) 

print len(results) 
print tcount 
if len(results) != tcount: 
    print '***Error: All threads do not have unique values!' 
else: 
    print 'Success! All threads have unique values!' 

Nota: Si desea que esta para realmente trabajar, cambie el código a este:

def run(self): 
    conn = boto.dynamodb.connect_to_region(aws_access_key_id=os.environ['AWS_ACCESS_KEY'], aws_secret_access_key=os.environ['AWS_SECRET_KEY'], region_name='us-east-1') 
    t = conn.get_table('zoo_keeper_ids') 
    item = t.new_item('counter') 
    item.add_attribute('n', 1) 
    r = item.save(return_values='ALL_NEW') #- Item has been atomically updated, and you have the correct value without having to do a "get"! 
    self.counter = str(r['Attributes']['n']) 
    logging.critical('Thread has counter: ' + str(self.counter)) 

Espero que ayude!

+0

Lo siento, publiqué por qué no funcionará sin publicar una solución. Modifiqué mi publicación original para incluir la solución. – grayaii