How can I implement a transaction of 50 writes in dynamoDB? - amazon-dynamodb

I’m aware there is a hard limit of 25 items per transaction. However, I’m sure there is a way of implementing transactions for more items from scratch. How might I go about it?
I’m thinking something like, keep a version number on every item. Fetch all the items up front, during insert verify version number is the same. Ie optimistic locking. If the condition fails, revert all failed items. Naturally, I can imagine that the revert could fail and I need to do optimistic locking on the revert and end up in a deadlock of reverts.

The solution I found in the end was to implement pessimistic locking. It supports an arbitrary number of writes and reads and guarantees transactional consistency. The catch is if you're not careful, it's easy to run into deadlocks.
The idea is that you
Create a lock table. Each row refers to a specific lock. The primary key of the lock table should be a string which I'll refer to as the lock-key. Often you'll want to lock a specific entity so this is a reasonable format for the lock-key {table_name}#{primary_key} but it might be more arbitrary so any string will do. Rows in the lock table should also auto-delete after a certain time period as per a ttl field ie TimeToLiveSpecification.
Before starting the transaction, acquire the lock. You do this by creating the row with your arbitrary lock-key and with a conditional check that the row doesn't already exist. If it does exist the row creation should fail which means another process has already acquired the lock. You then need to poll, trying to recreate the lock row until the lock has been released.
Once you have acquired the lock you need to keep the lock alive with a heartbeat to prevent other tasks from executing. The hearbeat process should increment a heartbeat property on the lock row which reflects the last-active time of the lock. The ttl of the row should be greater than the heartbeat interval. Normally about double, so that the lock is not auto-purged erroneously. If your process dies, the lock will be naturally released by the auto-deletion of the ttl.
If your task completes successfully it should delete the lock row freeing it up for other tasks.

Related

Do you need to do consistent read after using a DynamoDB transaction to commit a change?

We need strong consistency (insert where not exists, check conditions etc) to keep things in order a fast moving DynamoDb store, however we do far more reads than writes, and would prefer to sent consistentRead = false because it is faster, more stable (when nodes are down) and (most importantly) less costly.
If we use a Transaction write items collection to commit changes, does this wait for all nodes to propagate before returning? If so, surely you don’t need to use a consistent read to query this… is that the case?
No. Transactional writes work like regular reads in that they are acknowledged when they are written to at least 2 of the 3 nodes in the partition. One of those 2 nodes must be the leader node for the partition. The difference in a transaction is that all of the writes in that transaction have to work or none of them work.
If you do an eventually consistent read after the transaction, there is a 33% chance you will get the one node that was not required for the ack. Now then, if all is healthy that third node probably has the write anyhow.
All that said, if your workload needs a strongly consistent read like you indicate, then do it. Don't play around. There should not be a performance hit for a strong consistent read, but like you pointed out, there is a cost implication.

Firestore - Decrease number if greater than zero

Imagine 1 user that can press a button which resets a counter to 0.
In the other side, imagine multiple users (100k, for example) which can increase/decrease the same counter at the same time or whenever they want.
The counter can't never be lower than 0.
What I have thought to do is to run a transaction (read value and then update if necessary), but this seems that, if the counter is updated multiple times before a transaction finishes, it will be repeated again and again, and might ignores some increases if the counter is updated 100k times in a short period and the transaction fails (because of multiple repetitions, maybe I am wrong).
Is the only way to handle this with a transaction?
What you're describing is known as a contention bottleneck, and is a common limit in multi-user systems.
If having 100k concurrent updates to the same data is a realistic scenario in your case, you'll want to look at a different way to solve it.
The first one that comes to mind, and a common solution in general, is to have the users write their increase/decrease to a separate "queue". This can be a collection in Firestore, but the most important thing is that these are append only operations: there is no contention between multiple users writing at the same time.
Then you'd have a Cloud Run instance, or Cloud Functions, process the increase/decrease actions from the users. You can either limit this to at most one concurrent or a few concurrents, leading to either no contention or low contention on updating the final counter.

Is DynamoDb UpdateExpression with ADD to increment a counter transactional?

Do I need to use optimistic locking when updating a counter with ADD updateExpression to make sure that all increments from all the clients will be counted?
https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#API_UpdateItem_RequestSyntax
I'm not sure if you would still call it a transaction if that is the only thing you are doing in DynamoDB, it is a bit confusing the terminology.
IMO it is more correct to say it is Atomic. You can combine the increment with other changes in DynamoDB with a condition that will mean it won't be written unless that condition is true, but if your only change is the increment then other than hitting capacity limits there won't be any other reason (other than an asteroid hitting a datacenter or something of the like) why your increment would fail. (Unless you put a condition on your request which turns out to be false upon writing). If you have two clients incrementing at the same time, DynamoDB will handle this somebody will get in first.
But let's say you are incrementing a values many many times a second, whereby you may indeed be hitting a DynamoDB capacity limit. Consider batching the increments in a Kinesis Stream, whereby you can set the maximum time the stream should wait upon receiving a value that processing should begin. This will enable you to achieve consistency within x seconds in your aggregation.
But other than extremely high traffic situations you should be fine, and in that case the standard way of approaching that problem is using Streams which is very cost effective, saving you capacity units.

SQLITE database WAL file size keeps growing

I am writing continuously into a db file which has PRAGMA journal_mode=WAL, PRAGMA journal_size_limit=0. My C++ program has two threads, one reader(queries at 15 sec intervals) and one writer(inserts at 5 sec intervals).
Every 3 min I am pausing insertion to run a sqlite3_wal_checkpoint_v2() from the writer thread with the mode parameter as SQLITE_CHECKPOINT_RESTART. To ensure that no active read operations are going on at this point, I set a flag that checkpointing is about to take place and wait for reader to complete (the connection is still open) before running checkpoint. After checkpoint completion I again indicate to readers it is okay to resume querying.
sqlite3_wal_checkpoint_v2() returns SQLITE_OK, and pnLog and Ckpt as equal(around 4000), indicating complete wal file has been synced with main db file. So next write should start from beginning according to documentation. However, this does not seem to be happening as the subsequent writes cause the WAL file to grow indefinitely, eventually up to some GBs.
I did some searching and found that that readers can cause checkpoint failure due to open transactions. However, the only reader I'm using is ending its transaction before the checkpoint starts. What else could be preventing the WAL file from not growing?
This is far too late as an answer, but may be useful to other people.
According to the SQLite documentation, your expectations should be correct, but if you read this SO post, problems arise also in case of non-finalized statements. Therefore, if you just sqlite3_reset() your statement, there are chances anyway that the db may look busy or locked for a checkpoint. Note that this may happen also with higher levels of SQLITE_CHECKPOINT_values.
Also, the SQLITE_CHECKPOINT_TRUNCATE value, if checkout is successfully operated, will truncate the -wal file to zero length. That may help you check that all pages have been inserted in the db.
Another discussion in which -wal files grow larger and larger due to unfinalized statements is this.

How to serialize data reliably

Good day, I receive data from a communication channel and display it. Parallel, I serialize it into a SQLite database (using normal SQL INSERT statements). After my application exit I do a .commit on the sqlite object.
What happens if my application is terminated brutally in the middle? Will the latest (reasonably - not say 100 microsec ago, but at least a sec ago) data be safely in the database even without a .commit is made? Or should I have periodic commit? What are best patterns for doing these things?
I tried autocommit on (sqlite's option) and this slows code a lot by a factor ~55 (autocommit vs. just one commit at end). Doing commit every 100 inserts brings performance within 20% of the optimal mode. So autocommit is very slow for me.
My application pumps lots data into DB - what can I do to make it work well?
You should be performing this within a transaction, and consequently performing a commit at appropriate points in the process. A transaction will guarantee that this operation is atomic - that is, it either works or doesn't work.
Atomicity states that database
modifications must follow an “all or
nothing” rule. Each transaction is
said to be “atomic” if when one part
of the transaction fails, the entire
transaction fails. It is critical that
the database management system
maintain the atomic nature of
transactions in spite of any DBMS,
operating system or hardware failure.
If you've not committed, then the inserts won't be visible (and be rolled back) when your process is terminated.
When do you perform these commits ? When your inserts represent something consistent and complete. e.g.. if you have to insert 2 pieces of information for each message, then commit after you've inserted both pieces of info. Don't commit after each one, since your info won't be consistent or complete.
The data is not permanent in the database without a commit. Use an occasional commit to balance the speed of performing many inserts in a transaction (the more frequent the commit, the slower) with the safety of having more frequent commits.
You should do a COMMIT every time you complete a logical change.
One reason for transaction is to prevent uncommitted data from a transaction to be visible from outside. That is important because sometimes a single logical change can translate into multiple INSERT or UPDATE statements. If one of the latter queries of the transaction fails, the transaction can be cancelled with ROLLBACK and no change at all is recorded.
Generally speaking, no change performed in a transaction is recorded in the database until COMMIT succeeds.
does not this slow down considerably my code? – zaharpopov
Frequent commits, might slow down your code, and as an optimization you could try grouping several logical changes in a single transaction. But this is a departure from the correct use of transactions and you should only do this after measuring that this significantly improves performance.

Resources