For ex:
I have:
{
"status": "running",
"ts": "1590560955"
}
If current system ts >= above "ts", then:
{
"status": "completed",
"ts": "1590560955"
}
Is this even possible? Does Lambda function help here? If yes, how?
Yes, it is possible. It is called a "conditional update" - see for example this documentation. You can update one attribute based on the value of a different attribute - if those belong to the same item.
Here is an example (in Python, using Amazon's boto3 library, but you can translate it to whichever language you prefer):
table.update_item(
Key={'p': p},
UpdateExpression='SET status = :completedl',
ConditionExpression='ts > :currentts',
ExpressionAttributeValues={':completed': 'completed', ':currenttts': currentts})
This conditionally updates the item with key p: If ConditionExpression is fullfilled, i.e., the item's ts value is higher than "currentts" (which you'll supply), then the update described in UpdateExpression takes place: This item's status attribute is set to the string 'completed'.
Related
I am new to AWS in general, I am building a relatively simple application with Amplify, but I've used Google Firebase before. My question is: Is there a way to set a constrain for a field to be non-negative? I have an application that does transactions and I don't want my balance to be negative. I just need a simple error/exception. Is it possible to set a field constraint in DynamoDB that says "This field should be >= 0"?.
I also checked if it was possible to do it in the VTL amplify generated resolver of my graphql mutation, and indeed it is possible to set some constraints, But somehow it allows the operation and crashes on the next one (when the balance on the DB is already < 0, like if it checks it before the update). I tried saying something like "current_balance - transaction >= 0" but I couldn't get it to work.
So it seems that the only way is to create a custom lambda resolver that does the various checks before submitting the mutation to DynamoDB. I haven't tried it yet but I don't understand how I can do a check on the current balance (stored in the DB) without doing a query.
More in general is it even possible to validate fields (even with simple assertions like non-negative) on amplify/dynamoDB? Moving to another DB like Aurora would help?
Thanks for you help
DynamoDb supports conditional updates which allow an update to be applied when the given condition is met. You can set the condition current_balance >= cost for your update.
However, the negative balance is not the main problem. What you should address is how to prevent other requests from updating the same current_balance at the same time, or in short, race conditions on current_balance. In order to deal with that, you also need a conditional update whose condition is "current_balance = initial_balance". The initial_balance is, I guess, what you get from DynamoDB at the very beginning of the purchase process.
Sample VTL code
#set( $remaining_balance = $initial_balance - $transaction_cost )
#if( $remaining_balance < 0 )
$util.error("Insufficient balance")
#end
{
"version" : "2018-05-29",
"operation" : "UpdateItem",
"key": { <your-dynamodb-key> },
"update" : {
"expression" : "SET current_balance = :remaining_balance",
"expressionValues" : {
":remaining_balance" : $util.dynamodb.toNumberJson($remaining_balance)
}
},
"condition": {
"expression": "current_balance = :initial_balance",
"expressionValues" : {
":initial_balance" : $util.dynamodb.toNumberJson($initial_balance)
}
}
}
I'm trying to read an item with ID of X from DynamoDB (Using Appsync graphql) and I want it to create a default item if there is none.
This seems like it should be a normal use case. But the solutions I've tried have all been pretty bad:
I tried to create a Pipeline resolver that would first get the item, then in a second function create an item if there was no item in the result from the previous function. This had with returning the read item.
I tried making a PutAction with the condition that an item with this ID doesn't work. This does what I need it to, but I can't change the response from an error warning, no matter what I do to the response mapping template.
So how does one efficiently create a "read - or create if it does not exist" resolver for DynamoDb?
It turns out that I was close to the solution.
According to this documentation: https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-dynamodb.html#aws-appsync-resolver-mapping-template-reference-dynamodb-condition-handling
Create a putItem resolver that conditionally checks if there is an item with the same unique identifier (in DynamoDB that's usually a primary key and a sort key combination)
If the resolver determines the read object to not be different from the intended new object a warning will not be sent. So we can simply remove ALL fields from the comparison.
Example:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"id" : { "S" : "${ctx.args.id}" }
},
"condition" : {
"expression" : "attribute_not_exists(id)",
"equalsIgnore": [ "__typename", "_version", "_lastChangedAt", "_createdAt", "name", "owner"]
},
"attributeValues": {
"name": { "S" : "User Username" }
}
}
I have a table called Products, whose Key is a Range : orgzviceid + productid. It has a map attribute called "checkout" and a quantity storing attribute called "prod_stk_qty_i_i".
Say initially, for a product with Product ID 34, total available quantity is 10. As soon as a Cart checkout happens, assuming the Checkout ID is 5, and it has checked 2 quantities out a product id 34, then the product's (for productid 34) "checkout" map entry and "prod_stk_qty_i_i" in DynamoDB would be something like this:
"checkout" : { "5" : 2 },
"prod_stk_qty_i_i" : 8
If another checkout happens for the same product (say 1 quantity), and if that checkout ID is 7, then the checkout ooks like this:
"checkout" : { "5" : 2, "7" : 1 },
"prod_stk_qty_i_i" : 7
If payment is made, the checkout entry is removed, and quantity is increased.
Now, my requirement is to periodically after some timeout (30 minutes), release the Product Quantities which have been checked out, but not released. I do this by
Increasing the Quantity by "checkout."'s value
Removing the checkout. map entry
It is important that this operation not fail even if this operation is attempted multiple times, (idempotent), so its necessary that it only update if the checkout.checkoutID field exists. If not, it should simply ignore.
I tried the following:
[
"UpdateItem",
[
{
"TableName": "Products",
"Key": {
"orgzviceid": {
"N": "3000161710"
},
"productid": {
"N": "11"
}
},
"UpdateExpression": "REMOVE #checkout.#checkoutID SET #prod_stk_qty_i_i = #prod_stk_qty_i_i + #checkout.#checkoutID",
"ExpressionAttributeNames": {
"#checkout": "checkout",
"#checkoutID": "235",
"#prod_stk_qty_i_i": "prod_stk_qty_i_i"
},
"ConditionExpression": "attribute_exists(#checkout.#checkoutID)",
"ReturnValues": "ALL_NEW"
}
]
]
However, it gives me an error in case the checkout entry is not found for checkout id 235. Note that I've written ConditionExpression to do the update only if attribute "condition.235" exists.
Error Logs:
com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException","message":"The
conditional request failed ..."
So, how do I write a query such that if the map entry exist, then do the above operation, other wise not fail?
Obviously, one bad hack is to first check in a GetItem query if the checkout entry exists for the provided CheckoutID, and then only do this, however, it just does not seem right
I believe your using conditional expressions incorrectly. The point of the conditional is to fail if certain criteria is not met. WHy do you have the conditional at all? Without the conditional it would just execute the update expression and if the item does not exist I would not expect you to get an error. Like querying for an item that does not exist. You should simply get an empty set back. Not an error.
Your approach will not work because you are mixing "Attribute" and "AttributeValue" together in your conditional expression. Let me explain:
"ConditionExpression": "attribute_exists(#checkout.#checkoutID)"
In your table, checkout is an attribute in dynamo db, whereas checkoutID is in no way related to the table schema. So for dynamo DB, checkoutID is part of the attribute's value and not the attribute itself.
Therefore, to having the condition that you do will not work.
A conditional expression for your use case would be something which says attribute checkout exists and it's value is . However, in order to do that, you'd need to pass the expected map which boils down to reading the record before updating.
I do think that reading the record, updating the value and persisting it should be the way to go ahead in this case (and is not necessarily a bad idea)
Do consider using some kind of optimistic locking in this case to prevent against dirty reads and writes.
looking for an answer to this question if possible, not looking for a refactoring advice or redesign, i just need to understand what else I am missing here :
I have an existing item in dynamodb:
{
"CartId": 321,
"UserId": usr555,
"CartItems": [
{
"ProductId":59999,
"Quantity": 1
},
{
"ProductId": 58888,
"Quantity": 2
}
]
}
in my code I want to insert another nested attribute into the array CartItems in the item above. i can't find a way of doing this without retrieving the entire item and then inserting, which could amount to KBs in size.
all I want to do is insert a single array element in an existing nested attribute without having to retrieve the item.
the language I am using is nodejs and the function is DynamoDB.put.
UpdateExpression attribute supports SET action and SET action supports list_append function. Since list_append function operands must be list, enclose the new value within a list as below.
response = dynamodb_client.update_item(TableName='your_table',
Key={'CartId':'321'},
UpdateExpression='SET CartItems = list_append(CartItems, :val1)',
ExpressionAttributeValues = {':val1':[{'ProductId':12345, 'Quantity': 100}]})
Hope this helps
if I was to have a DynamoDB table with a UserID as a key and a number as a value can I increment that number/value in a single operation? or do I need to read it out, increment and write it back in?
thx
As others have stated, you can increment with one operation, however "action: ADD" is now deprecated. The current way to achieve this is with UpdateExpression. I am also assuming the OP meant that there was a numeric [attribute] whose value needs incrementing. I am calling the attribute loginCount:
dynamoDB.updateItem({
TableName: "Users",
Key: { "UserId": { S: "c6af9ac6-7b61" } },
ExpressionAttributeValues: { ":inc": {N: "1"} },
UpdateExpression: "ADD loginCount :inc"
})
DynamoDB supports the concept of atomic counters. You would simply call update_item with action: "ADD" to automatically increment the value for an item.
If you are looking for something like MySQL's Auto Increment functionality, then No, that is not possible in DynamoDB.