30

In my db scheme, I need a autoincrement primary key. How I can realize this feature?

PS For access to DynamoDB, I use dynode, module for Node.js.

NiLL
  • 13,645
  • 14
  • 46
  • 59

11 Answers11

29

Disclaimer: I am the maintainer of the Dynamodb-mapper project

Intuitive workflow of an auto-increment key:

  1. get the last counter position
  2. add 1
  3. use the new number as the index of the object
  4. save the new counter value
  5. save the object

This is just to explain the underlying idea. Never do it this way because it's not atomic. Under certain workload, you may allocate the same ID to 2+ different objects because it's not atomic. This would result in a data loss.

The solution is to use the atomic ADD operation along with ALL_NEW of UpdateItem:

  1. atomically generate an ID
  2. use the new number as the index of the object
  3. save the object

In the worst case scenario, the application crashes before the object is saved but never risk to allocate the same ID twice.

There is one remaining problem: where to store the last ID value ? We chose:

{
    "hash_key"=-1, #0 was judged too risky as it is the default value for integers.
    "__max_hash_key__y"=N
}

Of course, to work reliably, all applications inserting data MUST be aware of this system otherwise you might (again) overwrite data.

the last step is to automate the process. For example:

When hash_key is 0:
    atomically_allocate_ID()
actual_save()

For implementation details (Python, sorry), see https://bitbucket.org/Ludia/dynamodb-mapper/src/8173d0e8b55d/dynamodb_mapper/model.py#cl-67

To tell you the truth, my company does not use it in production because, most of the time it is better to find another key like, for the user, an ID, for a transaction, a datetime, ...

I wrote some examples in dynamodb-mapper's documentation and it can easily be extrapolate to Node.JS

If you have any question, feel free to ask.

yadutaf
  • 6,840
  • 1
  • 37
  • 48
  • 1
    It's nice, but now I'm using a timestamp and random numbers. PS Big thanks for this great answer, and thanks for improvement DynamoDB. – NiLL Aug 20 '12 at 09:34
  • 4
    Another workaround would be to use Redis counter, so you reduce A LOT the stress and operations to DynamoDB. When you do an insert, you ask to Redis the current counter and increase it. If redis does not have the counter, it ask DynamoDB the latest ID, and then store it. – Daniel Aranda Jul 27 '15 at 17:26
  • 1
    The "uuid" npm package usage is a nicer, faster and cheaper solution. Using a counter for a distributed load, even with the "ALL_NEW" flag implies more RCU consumption. – fdaugan Jan 06 '19 at 15:45
15

Another approach is to use a UUID generator for primary keys, as these are highly unlikely to clash.

IMO you are more likely to experience errors consolidating primary key counters across highly available DynamoDB tables than from clashes in generated UUIDs.

For example, in Node:

npm install uuid

var uuid = require('uuid');

// Generate a v1 (time-based) id
uuid.v1(); // -> '6c84fb90-12c4-11e1-840d-7b25c5ee775a'

// Generate a v4 (random) id
uuid.v4(); // -> '110ec58a-a0f2-4ac4-8393-c866d813b8d1'

Taken from SO answer.

James
  • 30,496
  • 19
  • 86
  • 113
  • 1
    UUIDs are the more robust approach for distributed systems – and are now built into Node v15.6 so you can simply use `crypto.randomUUID()` ([docs](https://nodejs.org/api/crypto.html#crypto_crypto_randomuuid_options)) – tenni Jun 09 '21 at 23:47
  • uuid is not unique. uuid is a randomly generated string it's LIKELY to be unique but not necessarily unique so you still have to manually if the generated id already exists then generate a new one if so. – aprilmintacpineda Dec 14 '22 at 02:56
6

If you're okay with gaps in your incrementing id, and you're okay with it only roughly corresponding to the order in which the rows were added, you can roll your own: Create a separate table called NextIdTable, with one primary key (numeric), call it Counter.

Each time you want to generate a new id, you would do the following:

  • Do a GetItem on NextIdTable to read the current value of Counter --> curValue
  • Do a PutItem on NextIdTable to set the value of Counter to curValue + 1. Make this a conditional PutItem so that it will fail if the value of Counter has changed.
  • If that conditional PutItem failed, it means someone else was doing this at the same time as you were. Start over.
  • If it succeeded, then curValue is your new unique ID.

Of course, if your process crashes before actually applying that ID anywhere, you'll "leak" it and have a gap in your sequence of IDs. And if you're doing this concurrently with some other process, one of you will get value 39 and one of you will get value 40, and there are no guarantees about which order they will actually be applied in your data table; the guy who got 40 might write it before the guy who got 39. But it does give you a rough ordering.

Parameters for a conditional PutItem in node.js are detailed here. http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/frames.html#!AWS/DynamoDB.html. If you had previously read a value of 38 from Counter, your conditional PutItem request might look like this.

var conditionalPutParams = {
    TableName: 'NextIdTable',
    Item: {
        Counter: {
            N: '39'
        }
    },
    Expected: {
        Counter: {
            AttributeValueList: [
                {
                    N: '38'
                }
            ],
            ComparisonOperator: 'EQ'
        }
    }
};
pisomojado
  • 893
  • 1
  • 10
  • 11
5

Addition to @yadutaf's answer

AWS supports Atomic Counters.

Create a separate table (order_id) with a row holding the latest order_number:

+----+--------------+
| id | order_number |
+----+--------------+
|  0 |         5000 |
+----+--------------+

This will allow to increment order_number by 1 and get the incremented result in a callback from AWS DynamoDB:

config={
  region: 'us-east-1',
  endpoint: "http://localhost:8000"
};
const docClient = new AWS.DynamoDB.DocumentClient(config); 

let param = {
            TableName: 'order_id',
            Key: {
                "id": 0
            },
            UpdateExpression: "set order_number = order_number + :val",
            ExpressionAttributeValues:{
                ":val": 1
            },
            ReturnValues: "UPDATED_NEW"
        };
        
       
docClient.update(params, function(err, data) {
   if (err) {
                console.log("Unable to update the table. Error JSON:", JSON.stringify(err, null, 2));
   } else {
                console.log(data);
                console.log(data.Attributes.order_number); // <= here is our incremented result
    }
  });

Be aware that in some rare cases their might be problems with the connection between your caller point and AWS API. It will result in the dynamodb row being incremented, while you will get a connection error. Thus, there might appear some unused incremented values.

You can use incremented data.Attributes.order_number in your table, e.g. to insert {id: data.Attributes.order_number, otherfields:{}} into order table.

Community
  • 1
  • 1
Artur A
  • 7,115
  • 57
  • 60
5

For those coding in Java, DynamoDBMapper can now generate unique UUIDs on your behalf.

DynamoDBAutoGeneratedKey

Marks a partition key or sort key property as being auto-generated. DynamoDBMapper will generate a random UUID when saving these attributes. Only String properties can be marked as auto-generated keys.

Use the DynamoDBAutoGeneratedKey annotation like this

@DynamoDBTable(tableName="AutoGeneratedKeysExample")
public class AutoGeneratedKeys { 
    private String id;

    @DynamoDBHashKey(attributeName = "Id")
    @DynamoDBAutoGeneratedKey
    public String getId() { return id; }
    public void setId(String id) { this.id = id; } 

As you can see in the example above, you can apply both the DynamoDBAutoGeneratedKey and DynamoDBHashKey annotation to the same attribute to generate a unique hash key.

F_SO_K
  • 13,640
  • 5
  • 54
  • 83
4

I don't believe it is possible to to a SQL style auto-increment because the tables are partitioned across multiple machines. I generate my own UUID in PHP which does the job, I'm sure you could come up with something similar like this in javascript.

Community
  • 1
  • 1
greg
  • 6,853
  • 15
  • 58
  • 71
2

I've had the same problem and created a small web service just for this purpose. See this blog post, that explains how I'm using stateful.co with DynamoDB in order to simulate auto-increment functionality: http://www.yegor256.com/2014/05/18/cloud-autoincrement-counters.html

Basically, you register an atomic counter at stateful.co and increment it every time you need a new value, through RESTful API. The service is free.

yegor256
  • 102,010
  • 123
  • 446
  • 597
  • How stable is this to be used in production? What are the uptime stats? – Mirage Jul 21 '16 at 18:41
  • @Mirage last 1000 days stats from statuscake.com: https://www.statuscake.com/App/button/index.php?Track=vAZJhwOtwc&Days=1000&Design=1 – yegor256 Jul 22 '16 at 01:59
2

Auto Increment is not good from performance perspective as it will overload specific shards while keeping others idle, It doesn't make even distribution if you're storing data to Dynamodb.

awsRequestId looks like its actually V.4 UUID (Random), code snippet below to try it:

exports.handler = function(event, context, callback) {
    console.log('remaining time =', context.getRemainingTimeInMillis());
    console.log('functionName =', context.functionName);
    console.log('AWSrequestID =', context.awsRequestId);
    callback(null, context.functionName);
};

In case you want to generate this yourself, you can use https://www.npmjs.com/package/uuid or Ulide to generate different versions of UUID based on RFC-4122

  • V1 (timestamp based)
  • V3 (Namespace)
  • V4 (Random)

For Go developers, you can use these packages from Google's UUID, Pborman, or Satori. Pborman is better in performance, check these articles and benchmarks for more details.

More Info on Universal Unique Identifier Specification could be found here.

Muhammad Soliman
  • 21,644
  • 6
  • 109
  • 75
0

Incase you are using NoSQL DynamoDB then using Dynamoose ORM, you can easily set default unique id. Here is the simple user creation example

// User.modal.js

const dynamoose = require("dynamoose");

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

const { v4: uuidv4 } = require("uuid");    
const User = require("./user.model");

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
0

Instead of using UUID use KSUID for ids. Naturally ordered by generation time.

https://www.npmjs.com/package/ksuid?activeTab=readme

-1

Create the new file.js and put this code:

exports.guid = function () {
    function _p8(s) {
        var p = (Math.random().toString(16)+"000000000").substr(2,8);
        return s ? "-" + p.substr(0,4) + "-" + p.substr(4,4) : p ;
    }
    return (_p8() + _p8(true) + _p8(true)+new Date().toISOString().slice(0,10)).replace(/-/g,"");
}

Then you can apply this function to the primary key id. It will generate the UUID.

Julien Lopez
  • 1,794
  • 5
  • 18
  • 24
Jivi
  • 29
  • 10