{"id":14465,"date":"2020-08-07T07:45:58","date_gmt":"2020-08-07T05:45:58","guid":{"rendered":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/"},"modified":"2020-08-07T07:45:58","modified_gmt":"2020-08-07T05:45:58","slug":"aws-dynamodb-local","status":"publish","type":"post","link":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/","title":{"rendered":"Amazon DynamoDB Local: running NoSQL on SQLite"},"content":{"rendered":"<h2>By Franck Pachot<\/h2>\n<p>.<br \/>\nDynamoDB is a cloud-native, managed, key-value proprietary database designed by AWS to handle massive throughput for large volume and high concurrency with a simple API.<\/p>\n<ul>\n<li>simple API: Get, Put, Query, Scan on a table without joins, optimizer, transparent indexes,&#8230;<\/li>\n<li>high concurrency: queries are directed to one shard with a hash function<\/li>\n<li>massive throughput: you can just add partitions to increase the IOPS<\/li>\n<li>large volume: it is a shared-nothing architecture where all tables are hash partitioned<\/li>\n<li>key-value: you access to any data with a key that is hashed to go to the right partition and the right range within it<\/li>\n<li>managed: you have zero administration to do. Just define the key and the throughput you desire and use it<\/li>\n<li>cloud-native: it was designed from the beginning to run in the AWS cloud<\/li>\n<\/ul>\n<p>One problem with cloud-native solution is that you need to access the service during the development of your application. This is not a major cost issue because DynamoDB is available on the Free Tier (with limited throughput, but that&#8217;s sufficient for development). But users may want to develop offline, on their laptop, without a reliable internet connection. And this is possible because Amazon provides a downloadable version of this database: DynamoDB Local.<\/p>\n<h3>Difference<\/h3>\n<p>The most important thing is that the API is the same as with the cloud version. For sure, all the partitioning stuff is missing in the local version. And I have no idea if the underlying data format is similar or not:<br \/>\n<a href=\"https:\/\/twitter.com\/FranckPachot\/status\/1291313397171138561?s=20\" rel=\"noopener noreferrer\" target=\"_blank\">https:\/\/twitter.com\/FranckPachot\/status\/1291313397171138561?s=20<\/a>However, this is more for curiosity. The local version just needs a compatible API. You will not measure the performance there. And from what I&#8217;ve discovered below, I&#8217;m sure the storage is completely different in the cloud version.<\/p>\n<h3>Install<\/h3>\n<pre><code>\n[oracle@cloud DynamoDBLocal]$ cat \/etc\/oracle-release\nOracle Linux Server release 7.7\n[oracle@cloud DynamoDBLocal]$ cat \/etc\/redhat-release\nRed Hat Enterprise Linux Server release 7.7 (Maipo)\n<\/code><\/pre>\n<p>I am doing this installation on OEL 7.7 which is similar to RHEL 7.7 or CentOS 7.7<\/p>\n<pre><code>\n[oracle@cloud DynamoDBLocal]$ java -version\njava version \"1.8.0_231\"\nJava(TM) SE Runtime Environment (build 1.8.0_231-b11)\nJava HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)\n<\/code><\/pre>\n<p>I have a JRE installed<\/p>\n<pre><code>\nmkdir -p \/var\/tmp\/DynamoDBLocal &amp;&amp; cd $_\n<\/code><\/pre>\n<p>I&#8217;m installing everything in a local temporary directory.<\/p>\n<p>All is documented in: <a href=\"https:\/\/docs.aws.amazon.com\/amazondynamodb\/latest\/developerguide\/DynamoDBLocal.DownloadingAndRunning.html#download-locally\" rel=\"noopener noreferrer\" target=\"_blank\">https:\/\/docs.aws.amazon.com\/amazondynamodb\/latest\/developerguide\/DynamoDBLocal.DownloadingAndRunning.html#download-locally<\/a><\/p>\n<pre><code>\ncurl https:\/\/s3.eu-central-1.amazonaws.com\/dynamodb-local-frankfurt\/dynamodb_local_latest.tar.gz | tar -xvzf -\n<\/code><\/pre>\n<p>This simply downloads and extract the DynamoDB local distribution<\/p>\n<h3>Run<\/h3>\n<pre><code>\njava -Djava.library.path=\/var\/tmp\/DynamoDBLocal\/DynamoDBLocal_lib -jar \/var\/tmp\/DynamoDBLocal\/DynamoDBLocal.jar -sharedDb -dbPath \/var\/tmp\/DynamoDBLocal &amp;\n<\/code><\/pre>\n<p>This will use a persistent file (you can run it in memory only with -inMemory instead of it) in the directory mentioned by -dbPath and -sharedDb will use the following file name:<\/p>\n<pre><code>\n[oracle@cloud ~]$ ls -l \/var\/tmp\/DynamoDBLocal\/shared-local-instance.db\n-rw-r--r-- 1 oracle oinstall 12346368 Aug  6 12:20 \/var\/tmp\/DynamoDBLocal\/shared-local-instance.db\n<\/code><\/pre>\n<p>I&#8217;ll tell you more about this file later.<\/p>\n<p>so, when started it displays on which port it listens:<\/p>\n<pre><code>\n\n[oracle@cloud ~]$ pkill -f -- '-jar DynamoDBLocal.jar -sharedDb'\n\n[oracle@cloud ~]$ java -Djava.library.path=\/var\/tmp\/DynamoDBLocal\/DynamoDBLocal_lib -jar \/var\/tmp\/DynamoDBLocal\/DynamoDBLocal.jar -sharedDb -dbPath \/var\/tmp\/DynamoDBLocal &amp;\n[1] 33294\n[oracle@cloud ~]$ Initializing DynamoDB Local with the following configuration:\nPort:   8000\nInMemory:       false\nDbPath: \/var\/tmp\/DynamoDBLocal\nSharedDb:       true\nshouldDelayTransientStatuses:   false\nCorsParams:     *\n\n<\/code><\/pre>\n<p>Another port can be defined with -port<\/p>\n<h3>AWS CLI<\/h3>\n<p>I use the AWS commandline interface, here is how to install it:<\/p>\n<pre><code>\nwget --continue https:\/\/awscli.amazonaws.com\/awscli-exe-linux-x86_64.zip\nunzip -oq awscli-exe-linux-x86_64.zip\nsudo .\/aws\/install\naws configure\n<\/code><\/pre>\n<p>For the configuration, as you are local, you can put anything you want for the access key and region:<\/p>\n<pre><code>\n[oracle@cloud ~]$ aws configure\nAWS Access Key ID [****************chot]: @FranckPachot\nAWS Secret Access Key [****************chot]: @FranckPachot\nDefault region name [Lausanne]: Lausanne\nDefault output format [table]:\n<\/code><\/pre>\n<p>Because this information is not used, I&#8217;ll need to define the endpoint &#8220;&#8211;endpoint-url http:\/\/localhost:8000&#8221; with each call.<\/p>\n<h3>Create table<\/h3>\n<pre><code>\naws dynamodb --endpoint-url http:\/\/localhost:8000 create-table \\\n --attribute-definitions \\\n  AttributeName=MyKeyPart,AttributeType=S \\\n  AttributeName=MyKeySort,AttributeType=S \\\n --key-schema \\\n  AttributeName=MyKeyPart,KeyType=HASH \\\n  AttributeName=MyKeySort,KeyType=RANGE \\\n --billing-mode PROVISIONED \\\n --provisioned-throughput ReadCapacityUnits=25,WriteCapacityUnits=25 \\\n --table-name Demo\n<\/code><\/pre>\n<p>I mentioned some provisioned capacity ready for my test on the Free Tier but they are actually ignored by DynamoDB local.<\/p>\n<pre><code>\n[oracle@cloud ~]$ aws dynamodb --endpoint-url http:\/\/localhost:8000 create-table \\\n&gt;  --attribute-definitions \\\n&gt;   AttributeName=MyKeyPart,AttributeType=S \\\n&gt;   AttributeName=MyKeySort,AttributeType=S \\\n&gt;  --key-schema \\\n&gt;   AttributeName=MyKeyPart,KeyType=HASH \\\n&gt;   AttributeName=MyKeySort,KeyType=RANGE \\\n&gt;  --billing-mode PROVISIONED \\\n&gt;  --provisioned-throughput ReadCapacityUnits=25,WriteCapacityUnits=25 \\\n&gt;  --table-name Demo\n--------------------------------------------------------------------------------------------------------------------------------------------------------\n|                                                                      CreateTable                                                                     |\n+------------------------------------------------------------------------------------------------------------------------------------------------------+\n||                                                                  TableDescription                                                                  ||\n|+----------------------------------+------------+-----------------------------------------------------+------------+-----------------+---------------+|\n||         CreationDateTime         | ItemCount  |                      TableArn                       | TableName  | TableSizeBytes  |  TableStatus  ||\n|+----------------------------------+------------+-----------------------------------------------------+------------+-----------------+---------------+|\n||  2020-08-06T12:42:23.669000+00:00|  0         |  arn:aws:dynamodb:ddblocal:000000000000:table\/Demo  |  Demo      |  0              |  ACTIVE       ||\n|+----------------------------------+------------+-----------------------------------------------------+------------+-----------------+---------------+|\n|||                                                               AttributeDefinitions                                                               |||\n||+------------------------------------------------------------------------+-------------------------------------------------------------------------+||\n|||                              AttributeName                             |                              AttributeType                              |||\n||+------------------------------------------------------------------------+-------------------------------------------------------------------------+||\n|||  MyKeyPart                                                             |  S                                                                      |||\n|||  MyKeySort                                                             |  S                                                                      |||\n||+------------------------------------------------------------------------+-------------------------------------------------------------------------+||\n|||                                                                     KeySchema                                                                    |||\n||+----------------------------------------------------------------------------------------+---------------------------------------------------------+||\n|||                                      AttributeName                                     |                         KeyType                         |||\n||+----------------------------------------------------------------------------------------+---------------------------------------------------------+||\n|||  MyKeyPart                                                                             |  HASH                                                   |||\n|||  MyKeySort                                                                             |  RANGE                                                  |||\n||+----------------------------------------------------------------------------------------+---------------------------------------------------------+||\n|||                                                               ProvisionedThroughput                                                              |||\n||+--------------------------------+---------------------------------+-----------------------------+-----------------------+-------------------------+||\n|||      LastDecreaseDateTime      |      LastIncreaseDateTime       |   NumberOfDecreasesToday    |   ReadCapacityUnits   |   WriteCapacityUnits    |||\n||+--------------------------------+---------------------------------+-----------------------------+-----------------------+-------------------------+||\n|||  1970-01-01T00:00:00+00:00     |  1970-01-01T00:00:00+00:00      |  0                          |  25                   |  25                     |||\n||+--------------------------------+---------------------------------+-----------------------------+-----------------------+-------------------------+||\n<\/code><\/pre>\n<p>Another difference with the cloud version is that this command returns immediately (no &#8220;CREATING&#8221; status).<\/p>\n<h3>Python<\/h3>\n<p>I&#8217;ll put some items with Python, thus installing it.<\/p>\n<pre><code>\nyum install -y python3\npip3 install boto3\n<\/code><\/pre>\n<p>boto3 is the AWS SDK for Python<\/p>\n<h3>Insert some items<\/h3>\n<p>Here is my demo.py program:<\/p>\n<pre><code>\nimport boto3, time, datetime\nfrom botocore.config import Config\ndynamodb = boto3.resource('dynamodb',config=Config(retries={'mode':'adaptive','total_max_attempts': 10}),endpoint_url='http:\/\/localhost:8000')\nn=0 ; t1=time.time()\ntry:\n for k in range(0,10):\n  for s in range(1,k+1):\n     r=dynamodb.Table('Demo').put_item(Item={'MyKeyPart':f\"K-{k:08}\",'MyKeySort':f\"S-{s:08}\",'seconds':int(time.time()-t1),'timestamp':datetime.datetime.now().isoformat()})\n     time.sleep(0.05);\n     n=n+1\nexcept Exception as e:\n print(str(e))\nt2=time.time()\nprint(f\"Last: %s\\n\\n===&gt; Total: %d seconds, %d keys %d items\/second\\n\"%(r,(t2-t1),k,n\/(t2-t1)))\n<\/code><\/pre>\n<p>I just fill each collection with an increasing number of items.<\/p>\n<pre><code>\n[oracle@cloud DynamoDBLocal]$ python3 demo.py\nLast: {'ResponseMetadata': {'RequestId': '6b23dcd2-dbb0-404e-bf5d-57e7a9426c9b', 'HTTPStatusCode': 200, 'HTTPHeaders': {'content-type': 'application\/x-amz-json-1.0', 'x-amz-crc32': '2745614147', 'x-amzn-requestid': '6b23dcd2-dbb0-404e-bf5d-57e7a9426c9b', 'content-length': '2', 'server': 'Jetty(8.1.12.v20130726)'}, 'RetryAttempts': 0}}\n\n===&gt; Total: 3 seconds, 9 keys 14 items\/second\n\n[oracle@cloud DynamoDBLocal]$\n\n<\/code><\/pre>\n<h3>count items<\/h3>\n<pre><code>\n[oracle@cloud DynamoDBLocal]$ aws dynamodb --endpoint-url http:\/\/localhost:8000 scan --table-name Demo --select=COUNT --return-consumed-capacity TOTAL\n----------------------------------\n|              Scan              |\n+----------+---------------------+\n|   Count  |    ScannedCount     |\n+----------+---------------------+\n|  45      |  45                 |\n+----------+---------------------+\n||       ConsumedCapacity       ||\n|+----------------+-------------+|\n||  CapacityUnits |  TableName  ||\n|+----------------+-------------+|\n||  0.5           |  Demo       ||\n|+----------------+-------------+|\n<\/code><\/pre>\n<p>The nice thing here is that you can see the ConsumedCapacity which gives you an idea about how it scales. Here, I read 45 items that have a size of 81 bytes and this is lower than 4K. Then the cost of it is 0.5 RCU for eventually consistent queries.<\/p>\n<h3>shared-local-instance.db<\/h3>\n<p>You know how I&#8217;m curious. If you want to build a local NoSQL database, which storage engine would you use?<\/p>\n<pre><code>\n[oracle@cloud DynamoDBLocal]$ cd \/var\/tmp\/DynamoDBLocal\n[oracle@cloud DynamoDBLocal]$ file shared-local-instance.db\nshared-local-instance.db: SQLite 3.x database\n<\/code><\/pre>\n<p>Yes, this NoSQL database is actually stored in a SQL database!<\/p>\n<p>They use SQLite for this DynamoDB Local engine, embedded in Java.<\/p>\n<pre><code>\n[oracle@cloud DynamoDBLocal]$ sudo yum install sqlite\nLoaded plugins: ulninfo, versionlock\nExcluding 247 updates due to versionlock (use \"yum versionlock status\" to show them)\nPackage sqlite-3.7.17-8.el7_7.1.x86_64 already installed and latest version\nNothing to do\n<\/code><\/pre>\n<p>I have SQLite installed here and then can look at what is inside with my preferred data API: SQL.<\/p>\n<pre><code>\n[oracle@cloud DynamoDBLocal]$ sqlite3 \/var\/tmp\/DynamoDBLocal\/shared-local-instance.db\nSQLite version 3.7.17 2013-05-20 00:56:22\nEnter \".help\" for instructions\nEnter SQL statements terminated with a \";\"\n\n\nsqlite&gt; .databases\nseq  name             file\n---  ---------------  ----------------------------------------------------------\n0    main             \/var\/tmp\/DynamoDBLocal\/shared-local-instance.db\n\nsqlite&gt; .tables\nDemo  cf    dm    sm    ss    tr    us\n<\/code><\/pre>\n<p>Here is my Demo table accompanied with some internal tables.<br \/>\nLet&#8217;s look at the fixed tables there (which I would call the catalog or dictionary if DynamoDB was not a NoSQL database)<\/p>\n<pre><code>\nsqlite&gt; .headers on\nsqlite&gt; .mode column\nsqlite&gt; select * from cf;\nversion\n----------\nv2.4.0\nsqlite&gt;\n<\/code><\/pre>\n<p>That looks like the version of the database (Config Table)<\/p>\n<pre><code>\nsqlite&gt; select * from dm;\nTableName   CreationDateTime  LastDecreaseDate  LastIncreaseDate  NumberOfDecreasesToday  ReadCapacityUnits  WriteCapacityUnits  TableInfo                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    BillingMode  PayPerRequestDateTime\n----------  ----------------  ----------------  ----------------  ----------------------  -----------------  ------------------  ---------------------------------------------------------------------------------------------                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                -----------  ---------------------\nDemo        1596718271246     0                 0                 0                       25                 25                  {\"Attributes\":[{\"AttributeName\":\"MyKeyPart\",\"AttributeType\":\"S\"},{\"AttributeName\":\"MyKeySort\",\"AttributeType\":\"S\"}],\"GSIList\":[],\"GSIDescList\":[],\"SQLiteIndex\":{\"\":[{\"DynamoDBAttribute\":{\"AttributeName\":\"MyKeyPart\",\"AttributeType\":\"S\"},\"KeyType\":\"HASH\",\"SQLiteColumnName\":\"hashKey\",\"SQLiteDataType\":\"TEXT\"},{\"DynamoDBAttribute\":{\"AttributeName\":\"MyKeySort\",\"AttributeType\":\"S\"},\"KeyType\":\"RANGE\",\"SQLiteColumnName\":\"rangeKey\",\"SQLiteDataType\":\"TEXT\"}]},\"UniqueIndexes\":[{\"DynamoDBAttribute\":{\"AttributeName\":\"MyKeyPart\",\"AttributeType\":\"S\"},\"KeyType\":\"HASH\",\"SQLiteColumnName\":\"hashKey\",\"SQLiteDataType\":\"TEXT\"},{\"DynamoDBAttribute\":{\"AttributeName\":\"MyKeySort\",\"AttributeType\":\"S\"},\"KeyType\":\"RANGE\",\"SQLiteColumnName\":\"rangeKey\",\"SQLiteDataType\":\"TEXT\"}],\"UniqueGSIIndexes\":[]}  0            0\nsqlite&gt;\n<\/code><\/pre>\n<p>Here are the (DynamoDB Metadata) about my table, the DynamoDB ones, like &#8220;AttributeName&#8221;,&#8221;AttributeType&#8221; and their mapping to the SQLite &#8220;SQLiteColumnName&#8221;,&#8221;SQLiteDataType&#8221;,&#8230;<\/p>\n<p>The tables sm, ss, us, and tr are empty and are related with Streams Metadata, Shard Metadata, Streams and Transactions and I may have a look at them for a next post.<\/p>\n<p>Now the most interesting one: my Demo table. For this one, I&#8217;ve opened it in <a href=\"https:\/\/dbeaver.io\/\" rel=\"noopener noreferrer\" target=\"_blank\">DBeaver<\/a>:<\/p>\n<p><a href=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg\" alt=\"\" width=\"1024\" height=\"365\" class=\"aligncenter size-large wp-image-42278\" \/><\/a><br \/>\nI have one SQLite table per DynamoDB table (global secondary indexes are just indexes on the table), one SQLite row per DynamoDB item, the keys (the HASH for partitioning and the RANGE for sorting within the partition) for which I used a string are stored as TEXT in SQLite but containing their ASCII hexadecimal codes (hashKey and rangeKey). And those are the columns for the SQLite primary key. They are also stored in an even larger binary (hashValue,rangeValue where hashValue is indexed), probably a hash function applied to it. And finally, the full item is stored as JSON in a BLOB. The itemSize is interesting because that&#8217;s what counts in Capacity Units (the sum of attribute names and attribute values).<\/p>\n<h3>The power of SQL to verify the NoSQL database<\/h3>\n<p>Actually, there&#8217;s a big advantage to have this NoSQL backed by a SQL database. During the development phase, you don&#8217;t only need a database to run your code. You have to verify the integrity of data, even after some race conditions. For example, I&#8217;ve inserted more items by increasing the &#8216;k&#8217; loop in my demo.py and letting it run for 6 hours:<\/p>\n<pre><code>\n[oracle@cloud aws]$ time aws dynamodb --endpoint-url http:\/\/localhost:8000 scan --table-name Demo --select=COUNT --return-consumed-capacity TOTAL\n----------------------------------\n|              Scan              |\n+-----------+--------------------+\n|   Count   |   ScannedCount     |\n+-----------+--------------------+\n|  338498   |  338498            |\n+-----------+--------------------+\n||       ConsumedCapacity       ||\n|+----------------+-------------+|\n||  CapacityUnits |  TableName  ||\n|+----------------+-------------+|\n||  128.5         |  Demo       ||\n|+----------------+-------------+|\n\nreal    0m50.385s\nuser    0m0.743s\nsys     0m0.092s\n<\/code><\/pre>\n<p>The DynamoDB scan is long here: 1 minute for a small table (300K rows). This API is designed for the cloud where a huge amount of disks can provide high throughput for many concurrent requests. There&#8217;s no optimization when scanning all items, as I described it in a previous post: <a href=\"https:\/\/www.dbi-services.com\/blog\/rdbms-scales-the-algorithm\/\" rel=\"noopener noreferrer\" target=\"_blank\">RDBMS (vs. NoSQL) scales the algorithm before the hardware<\/a>. SQL databases have optimization for full table scans, and the database for those 338498 rows is really small:<\/p>\n<pre><code>\n[oracle@cloud aws]$ du -h \/var\/tmp\/DynamoDBLocal\/shared-local-instance.db\n106M    \/var\/tmp\/DynamoDBLocal\/shared-local-instance.db\n<\/code><\/pre>\n<p>Counting the rows is faster from SQLite directly:<\/p>\n<pre><code>\n[oracle@cloud aws]$ time sqlite3 \/var\/tmp\/DynamoDBLocal\/shared-local-instance.db \"select count(*) from Demo;\"\n338498\n\nreal    0m0.045s\nuser    0m0.015s\nsys     0m0.029s\n\n<\/code><\/pre>\n<p>But be careful: SQLite is not a multi-user database. Query it only when the DynamoDB Local is stopped.<\/p>\n<p>And with the power of SQL it is easy to analyze the data beyond the API provided by DynamoDB:<\/p>\n<pre><code>\n[oracle@cloud aws]$ sqlite3 \/var\/tmp\/DynamoDBLocal\/shared-local-instance.db\nSQLite version 3.32.3 2020-06-18 14:00:33\nEnter \".help\" for instructions\nEnter SQL statements terminated with a \";\"\nsqlite&gt; .mode column\nsqlite&gt; .header on\nsqlite&gt; .timer on\n\nsqlite&gt; select count(distinct hashKey),count(distinct hashKey),count(distinct rangeKey),count(distinct rangeValue) from Demo;\ncount(distinct hashKey)  count(distinct hashKey)  count(distinct rangeKey)  count(distinct rangeValue)\n-----------------------  -----------------------  ------------------------  --------------------------\n823                      823                      822                       822\n\nCPU Time: user 0.570834 sys 0.168966\n\n<\/code><\/pre>\n<p>This simple query confirms that I have as many hash\/range Key as Value.<\/p>\n<pre><code>\nsqlite&gt; select cast(hashKey as varchar),json_extract(ObjectJSON,'$.MyKeyPart')\n   ...&gt; ,count(rangeKey),count(distinct rangeKey)\n   ...&gt; from Demo group by hashKey order by count(rangeKey) desc limit 10;\n\ncast(hashKey as varchar)  json_extract(ObjectJSON,'$.MyKeyPart')  count(rangeKey)  count(distinct rangeKey)\n------------------------  --------------------------------------  ---------------  ------------------------\nK-00000823                {\"S\":\"K-00000823\"}                      245              245\nK-00000822                {\"S\":\"K-00000822\"}                      822              822\nK-00000821                {\"S\":\"K-00000821\"}                      821              821\nK-00000820                {\"S\":\"K-00000820\"}                      820              820\nK-00000819                {\"S\":\"K-00000819\"}                      819              819\nK-00000818                {\"S\":\"K-00000818\"}                      818              818\nK-00000817                {\"S\":\"K-00000817\"}                      817              817\nK-00000816                {\"S\":\"K-00000816\"}                      816              816\nK-00000815                {\"S\":\"K-00000815\"}                      815              815\nK-00000814                {\"S\":\"K-00000814\"}                      814              814\n\nRun Time: real 0.297 user 0.253256 sys 0.042886\n<\/code><\/pre>\n<p>There I checked how many distinct range keys I have for the 10 ones (LIMIT 10) with the highest value (ORDER BY count(rangeKey) DESC), and converted this hexadecimal int a string (CAST) and also compare with what is in the JSON column (JSON_EXTRACT). Yes, many RDBMS database can manipulate easily a semi-structured JSON with SQL.<\/p>\n<pre><code>\nsqlite&gt; select\n   ...&gt;  round(timestamp_as_seconds-lag(timestamp_as_seconds)over(order by timestamp)) seconds\n   ...&gt;  ,MyKeyPart,MyKeySort,MyKeySort_First,MyKeySort_Last,timestamp\n   ...&gt; from (\n   ...&gt;  select\n   ...&gt;    MyKeyPart,MyKeySort\n   ...&gt;   ,first_value(MyKeySort)over(partition by MyKeyPart) MyKeySort_First\n   ...&gt;   ,last_value(MyKeySort)over(partition by MyKeyPart) MyKeySort_Last\n   ...&gt;   ,timestamp,timestamp_as_seconds\n   ...&gt;  from (\n   ...&gt;   select\n   ...&gt;     json_extract(ObjectJSON,'$.MyKeyPart.S') MyKeyPart,json_extract(ObjectJSON,'$.MyKeySort.S') MyKeySort\n   ...&gt;    ,json_extract(ObjectJSON,'$.timestamp.S') timestamp\n   ...&gt;    ,julianday(datetime(json_extract(ObjectJSON,'$.timestamp.S')))*24*60*60 timestamp_as_seconds\n   ...&gt;   from Demo\n   ...&gt;  )\n   ...&gt; )\n   ...&gt; where MyKeySort=MyKeySort_Last\n   ...&gt; order by timestamp desc limit 5\n   ...&gt; ;\n\nseconds     MyKeyPart   MyKeySort   MyKeySort_First  MyKeySort_Last  timestamp\n----------  ----------  ----------  ---------------  --------------  --------------------------\n 16.0       K-00000823  S-00000245  S-00000001       S-00000245      2020-08-07T04:19:55.470202\n 54.0       K-00000822  S-00000822  S-00000001       S-00000822      2020-08-07T04:19:39.388729\n111.0       K-00000821  S-00000821  S-00000001       S-00000821      2020-08-07T04:18:45.306205\n 53.0       K-00000820  S-00000820  S-00000001       S-00000820      2020-08-07T04:16:54.977931\n 54.0       K-00000819  S-00000819  S-00000001       S-00000819      2020-08-07T04:16:01.003016\n\nRun Time: real 3.367 user 2.948707 sys 0.414206\nsqlite&gt;\n<\/code><\/pre>\n<p>Here is how I checked the time take by the insert. My Python code added a timestamp which I convert it to seconds (JULIANDAY) and get the difference with the previous row (LAG). I actually did that only for the last item of each collection (LAST_VALUE). <\/p>\n<p>Those are examples. You can play and improve your SQL skills on your NoSQL data. SQLite is one of the database with the best documentation: <a href=\"https:\/\/www.sqlite.org\/lang.html\" rel=\"noopener noreferrer\" target=\"_blank\">https:\/\/www.sqlite.org\/lang.html<\/a>. And it is not only about learning. During development and UAT you need to verify the quality of data and this often goes beyond the application API (especially when the goal is to verify that the application API is correct).<\/p>\n<p>That&#8217;s all for this post. You know how to run DynamoDB locally, and can even access it with SQL for powerful queries \ud83d\ude09<\/p>\n","protected":false},"excerpt":{"rendered":"<p>By Franck Pachot . DynamoDB is a cloud-native, managed, key-value proprietary database designed by AWS to handle massive throughput for large volume and high concurrency with a simple API. simple API: Get, Put, Query, Scan on a table without joins, optimizer, transparent indexes,&#8230; high concurrency: queries are directed to one shard with a hash function [&hellip;]<\/p>\n","protected":false},"author":89,"featured_media":14466,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1865],"tags":[133,386,1866,2050,2051],"type_dbi":[],"class_list":["post-14465","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-aws","tag-aws","tag-download","tag-dynamodb","tag-local","tag-sqlite"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.2 (Yoast SEO v27.2) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Amazon DynamoDB Local: running NoSQL on SQLite - dbi Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Amazon DynamoDB Local: running NoSQL on SQLite\" \/>\n<meta property=\"og:description\" content=\"By Franck Pachot . DynamoDB is a cloud-native, managed, key-value proprietary database designed by AWS to handle massive throughput for large volume and high concurrency with a simple API. simple API: Get, Put, Query, Scan on a table without joins, optimizer, transparent indexes,&#8230; high concurrency: queries are directed to one shard with a hash function [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/\" \/>\n<meta property=\"og:site_name\" content=\"dbi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2020-08-07T05:45:58+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1496\" \/>\n\t<meta property=\"og:image:height\" content=\"533\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Cloud Team\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Cloud Team\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/\"},\"author\":{\"name\":\"Cloud Team\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/92adc1c969d57f2b2f51e970b15b2f70\"},\"headline\":\"Amazon DynamoDB Local: running NoSQL on SQLite\",\"datePublished\":\"2020-08-07T05:45:58+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/\"},\"wordCount\":1298,\"commentCount\":1,\"image\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg\",\"keywords\":[\"AWS\",\"download\",\"DynamoDB\",\"Local\",\"SQLite\"],\"articleSection\":[\"AWS\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/\",\"name\":\"Amazon DynamoDB Local: running NoSQL on SQLite - dbi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg\",\"datePublished\":\"2020-08-07T05:45:58+00:00\",\"author\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/92adc1c969d57f2b2f51e970b15b2f70\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#primaryimage\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg\",\"contentUrl\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg\",\"width\":1496,\"height\":533},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\/\/www.dbi-services.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Amazon DynamoDB Local: running NoSQL on SQLite\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#website\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/\",\"name\":\"dbi Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/92adc1c969d57f2b2f51e970b15b2f70\",\"name\":\"Cloud Team\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/1fd4abaa3d2f79eb3c430f8ababb33014273b4168e2652ca915d59c6ef5e8cec?s=96&d=mm&r=g\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/1fd4abaa3d2f79eb3c430f8ababb33014273b4168e2652ca915d59c6ef5e8cec?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/1fd4abaa3d2f79eb3c430f8ababb33014273b4168e2652ca915d59c6ef5e8cec?s=96&d=mm&r=g\",\"caption\":\"Cloud Team\"},\"url\":\"https:\/\/www.dbi-services.com\/blog\/author\/cloud\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Amazon DynamoDB Local: running NoSQL on SQLite - dbi Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/","og_locale":"en_US","og_type":"article","og_title":"Amazon DynamoDB Local: running NoSQL on SQLite","og_description":"By Franck Pachot . DynamoDB is a cloud-native, managed, key-value proprietary database designed by AWS to handle massive throughput for large volume and high concurrency with a simple API. simple API: Get, Put, Query, Scan on a table without joins, optimizer, transparent indexes,&#8230; high concurrency: queries are directed to one shard with a hash function [&hellip;]","og_url":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/","og_site_name":"dbi Blog","article_published_time":"2020-08-07T05:45:58+00:00","og_image":[{"width":1496,"height":533,"url":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg","type":"image\/jpeg"}],"author":"Cloud Team","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Cloud Team","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#article","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/"},"author":{"name":"Cloud Team","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/92adc1c969d57f2b2f51e970b15b2f70"},"headline":"Amazon DynamoDB Local: running NoSQL on SQLite","datePublished":"2020-08-07T05:45:58+00:00","mainEntityOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/"},"wordCount":1298,"commentCount":1,"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg","keywords":["AWS","download","DynamoDB","Local","SQLite"],"articleSection":["AWS"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/","url":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/","name":"Amazon DynamoDB Local: running NoSQL on SQLite - dbi Blog","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#primaryimage"},"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg","datePublished":"2020-08-07T05:45:58+00:00","author":{"@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/92adc1c969d57f2b2f51e970b15b2f70"},"breadcrumb":{"@id":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#primaryimage","url":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg","contentUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Annotation-2020-08-06-153013.jpg","width":1496,"height":533},{"@type":"BreadcrumbList","@id":"https:\/\/www.dbi-services.com\/blog\/aws-dynamodb-local\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/www.dbi-services.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Amazon DynamoDB Local: running NoSQL on SQLite"}]},{"@type":"WebSite","@id":"https:\/\/www.dbi-services.com\/blog\/#website","url":"https:\/\/www.dbi-services.com\/blog\/","name":"dbi Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/92adc1c969d57f2b2f51e970b15b2f70","name":"Cloud Team","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/1fd4abaa3d2f79eb3c430f8ababb33014273b4168e2652ca915d59c6ef5e8cec?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/1fd4abaa3d2f79eb3c430f8ababb33014273b4168e2652ca915d59c6ef5e8cec?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/1fd4abaa3d2f79eb3c430f8ababb33014273b4168e2652ca915d59c6ef5e8cec?s=96&d=mm&r=g","caption":"Cloud Team"},"url":"https:\/\/www.dbi-services.com\/blog\/author\/cloud\/"}]}},"_links":{"self":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/14465","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/users\/89"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/comments?post=14465"}],"version-history":[{"count":0,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/14465\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media\/14466"}],"wp:attachment":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media?parent=14465"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/categories?post=14465"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/tags?post=14465"},{"taxonomy":"type","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/type_dbi?post=14465"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}