122

I am new to dynamodb. I want to auto increment id value when I use putitem with dynamodb.

Is possible to do that?

so-random-dude
  • 15,277
  • 10
  • 68
  • 113
pranay
  • 1,399
  • 3
  • 10
  • 8
  • Possible duplicate of [How to make a UUID in DynamoDB?](http://stackoverflow.com/questions/11721308/how-to-make-a-uuid-in-dynamodb) – Clarkie May 06 '16 at 13:50
  • or this one http://stackoverflow.com/questions/13264236/dynamodb-auto-incremented-id-server-time-ios-sdk – Clarkie May 06 '16 at 13:51

7 Answers7

109

This is anti-pattern in DynamoDB which is build to scale across many partitions/shards/servers. DynamoDB does not support auto-increment primary keys due to scaling limitations and cannot be guaranteed across multiple servers.

Better option is to assemble primary key from multiple indices. Primary key can be up to 2048 bytes. There are few options:

  1. Use UUID as your key - possibly time based UUID which makes it unique, evenly distributed and carries time value
  2. Use randomly generated number or timestamp + random (possibly bit-shifting) like: ts << 12 + random_number
  3. Use another service or DynamoDB itself to generate incremental unique id (requires extra call)

Following code will auto-increment counter in DynamoDB and then you can use it as primary key.

var documentClient = new AWS.DynamoDB.DocumentClient();
var params = {
  TableName: 'sampletable',
  Key: { HashKey : 'counters' },
  UpdateExpression: 'ADD #a :x',
  ExpressionAttributeNames: {'#a' : "counter_field"},
  ExpressionAttributeValues: {':x' : 1},
  ReturnValues: "UPDATED_NEW" // ensures you get value back
};
documentClient.update(params, function(err, data) {});
// once you get new value, use it as your primary key

My personal favorite is using timestamp + random inspired by Instagram's Sharding ID generation at http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram

Following function will generate id for a specific shard (provided as parameter). This way you can have unique key, which is assembled from timestamp, shard no. and some randomness (0-512).

var CUSTOMEPOCH = 1300000000000; // artificial epoch
function generateRowId(shardId /* range 0-64 for shard/slot */) {
  var ts = new Date().getTime() - CUSTOMEPOCH; // limit to recent
  var randid = Math.floor(Math.random() * 512);
  ts = (ts * 64);   // bit-shift << 6
  ts = ts + shardId;
  return (ts * 512) + randid;
}
var newPrimaryHashKey = "obj_name:" + generateRowId(4);
// output is: "obj_name:8055517407349240"
vladaman
  • 3,741
  • 2
  • 29
  • 26
  • 1
    Can you add more details about your 2nd point and the code at the end? Is `subId` supposed to be a shard ID or something? – andrhamm Oct 03 '17 at 15:34
  • @andrhamm It certainly looks like the shard id though 4? The ref article uses the formula userId % shardTotal (13 bits). – Eli Peters Apr 06 '18 at 22:31
  • 1
    Please could explain the use of bit-shifting? – rangfu Apr 12 '18 at 07:06
  • 2
    @vladaman what is the point of using `var randid = Math.floor(Math.random() * 512); ... randid % 512` it should provide a number between 0 to 511 on the first line. Using modulo 512 for such a number does not change the number. – BennyHilarious Dec 03 '19 at 13:08
  • Have in mind that with this approach you cannot retrieve the timestamp from the id (like the instagram's example), since you don't know the random part. – Mark Hkr Mar 14 '20 at 04:02
  • i know i'm a little late to this, but could someone explain to me why an incremented partition key results in non-uniform distribution (or) bad sharding. From my point of view, it's just a number, unique to the given table due to the sole reason that it's (+1) to any existing id. It shouldn't matter to how it's partitioned right ? – TheAnimatrix Apr 10 '20 at 09:19
  • How can I limit this with n digits? for example i want to create id of 9 digits only. – VK321 Oct 10 '20 at 08:58
  • 1
    @TheAnimatrix the partition key should be used to logically group together data that would be commonly accessed together. Data with the same partition key will typically be stored physically close to other to optimize data retrieval. This is why dynamo supports a partition key and a sort key. The partition key is the same, the sort key increments, together they are unique. I think this answer is misleading since in the linked article, IG are doing their own sharding and not using dynamo. I don't think you'd want to create a key like this using dynamo. – hurlbz Mar 18 '21 at 20:50
80

DynamoDB doesn't provide this out of the box. You can generate something in your application such as UUIDs that "should" be unique enough for most systems.

I noticed you were using Node.js (I removed your tag). Here is a library that provides UUID functionality: node-uuid

Example from README

var uuid = require('node-uuid');
var uuid1 = uuid.v1();
var uuid2 = uuid.v1({node:[0x01,0x23,0x45,0x67,0x89,0xab]});
var uuid3 = uuid.v1({node:[0, 0, 0, 0, 0, 0]})
var uuid4 = uuid.v4();
var uuid5 = uuid.v4();
tier1
  • 6,303
  • 6
  • 44
  • 75
  • 2
    FWIW I use this approach (UUID as the hash key) with Dynamo and it has worked great. – rpmartz May 08 '16 at 11:55
  • 12
    This answer should be marked as the correct answer. It's also worth noting why: you want a uniform distribution of keys, and auto-incrementing would lead to a non-uniform distribution. See this article for more information: https://forums.aws.amazon.com/thread.jspa?messageID=312527 and the AWS docs here: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/BestPractices.html – Lane Rettig Mar 06 '17 at 16:58
  • 2
    Use uuid as node-uuid is deprecated now. – node_saini Dec 19 '19 at 07:29
  • Use KSUID (K-Sortable Globally Unique IDs) instead of UUID in the "sort key". In contrast to UUID, KSUID is sortable. Please refer to: https://github.com/segmentio/ksuid – Ahmad Oct 14 '22 at 16:14
4

You probably can use AtomicCounters.

With AtomicCounters, you can use the UpdateItem operation to implement an atomic counter—a numeric attribute that is incremented, unconditionally, without interfering with other write requests. (All write requests are applied in the order in which they were received.) With an atomic counter, the updates are not idempotent. In other words, the numeric value increments each time you call UpdateItem.

You might use an atomic counter to track the number of visitors to a website. In this case, your application would increment a numeric value, regardless of its current value. If an UpdateItem operation fails, the application could simply retry the operation. This would risk updating the counter twice, but you could probably tolerate a slight overcounting or undercounting of website visitors.

so-random-dude
  • 15,277
  • 10
  • 68
  • 113
  • 1
    This might cause hot key problem as one partition can have at most 3000 IOPS. 1 RCU=1 IOPS . 1 WCU =3 IOPS. Also it will be slow to use AtomicCounter as ID generator because increments are executed in serial. – Guangtong Shen Jul 01 '20 at 01:31
  • 1
    Just to sum up (for myself and to help others): @vladaman s answer is actually showing this AtomicCounter technique. And just as @ guangtongShen has mentioned this technique is NOT scalable! (I only use it on low-intensity operations. Eg. when "creating an item" happens very very rarely. And usually this approach should be avoided in favor of UUIDs (as also mentioned in vladaman s request) – Dimitry K Jul 30 '20 at 13:13
  • The link above mentions: An atomic counter would not be appropriate where overcounting or undercounting can't be tolerated (for example, in a banking application) – Alex Bin Zhao Jul 18 '21 at 00:36
4

Came across a similar issue, where I required auto-incrementing primary key in my table. We could use some randomization techniques to generate a random key and store it using that. But it won't be in a incremental fashion.

If you require something in incremental fashion, you can use Unix Time as your primary key. Not assuring, that you can get a accurate incrementation(one-by-one), but yes every record you put, it would be in incremental fashion, with respect to the difference in how much time each record in inserted in.

Not a complete solution, if you don't want to read the entire table and get it's last id and then increment it.

Following is the code for inserting a record in DynamoDB using NodeJS:

.
.
        const params = {
            TableName: RANDOM_TABLE,
            Item: {
                ip: this.ip,
                id: new Date().getTime()
            }
        }
    
        dynamoDb.put(params, (error, result) => {
            console.log(error, result);
        });
.
.
Trishant Pahwa
  • 2,559
  • 2
  • 14
  • 31
3

If you are using NoSQL Dynamo DB then using Dynamoose, you can easily set default unique id, here is the simple user create example

// User.modal.js

const dynamoose = require("dynamoose");
const { v4: uuidv4 } = require("uuid");

const userSchema = new dynamoose.Schema(
  {
    id: {
      type: String,
      hashKey: true,
    },
    displayName: String,
    firstName: String,
    lastName: String,
  },
  { timestamps: true },
);

const User = dynamoose.model("User", userSchema);

module.exports = User;

// User.controller.js

exports.create = async (req, res) => {
  const user = new User({ id: uuidv4(), ...req.body }); // set unique id
  const [err, response] = await to(user.save());
  if (err) {
    return badRes(res, err);
  }
  return goodRes(res, reponse);
};
Hammad Tariq
  • 344
  • 2
  • 7
3

Update for 2022 :

I was looking for the same issue and came across following research.

DynamoDB still doesn't support auto-increment of primary keys.

https://aws.amazon.com/blogs/database/simulating-amazon-dynamodb-unique-constraints-using-transactions/

Also the package node-uuid is now deprecated. They recommend we use uuid package instead that creates RFC4122 compliant UUID's.

npm install uuid

import { v4 as uuidv4 } from 'uuid';
uuidv4(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
Anil_M
  • 10,893
  • 6
  • 47
  • 74
1

For Java developers, there is the DynamoDBMapper, which is a simple ORM. This supports the DynamoDBAutoGeneratedKey annotation. It doesn't increment a numeric value like a typical "Long id", but rather generates a UUID like other answers here suggest. If you're mapping classes as you would with Hibernate, GORM, etc., this is more natural with less code.

I see no caveats in the docs about scaling issues. And it eliminates the issues with under or over-counting as you have with the auto-incremented numeric values (which the docs do call out).

Philip
  • 697
  • 7
  • 20