random – Collisions when generating UUIDs in JavaScript?-ThrowExceptions

Exception or error:

This relates to this question. I am using the code below from this answer to generate UUID in JavaScript:

'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
});

This solution appeared to be working fine, but I am getting collisions. Here’s what I have:

  • A web-app running in Google Chrome.
  • 16 users.
  • about 4000 UUIDs have been generated in the past 2 months by these users.
  • I got about 20 collisions – e.g. new UUID generated today was the same as about 2 months ago (different user).

What is causing this issue and how can I avoid it?

How to solve:

My best guess is that Math.random() is broken on your system for some reason (bizarre as that sounds). This is the first report I’ve seen of anyone getting collisions.

node-uuid has a test harness that you can use to test the distribution of hex digits in that code. If that looks okay then it’s not Math.random(), so then try substituting the UUID implementation you’re using into the uuid() method there and see if you still get good results.

[Update: Just saw Veselin’s report about the bug with Math.random() at startup. Since the problem is only at startup, the node-uuid test is unlikely to be useful. I’ll comment in more detail on the devoluk.com link.]

###

Indeed there are collisions but only under Google Chrome. Check out my experience on the topic here

http://devoluk.com/google-chrome-math-random-issue.html

(Link broken as of 2019. Archive link: https://web.archive.org/web/20190121220947/http://devoluk.com/google-chrome-math-random-issue.html.)

Seems like collisions only happen on the first few calls of Math.random. Cause if you just run the createGUID / testGUIDs method above (which obviously was the first thing I tried) it just works with no collisions whatsoever.

So to make a full test one needs to restart Google Chrome, generate 32 byte, restart Chrome, generate, restart, generate…

###

Just so that other folks can be aware of this – I was running into a surprisingly large number of apparent collisions using the UUID generation technique mentioned here. These collisions continued even after I switched to seedrandom for my random number generator. That had me tearing my hair out, as you can imagine.

I eventually figured out that the problem was (almost?) exclusively associated with Google’s web crawler bots. As soon as I started ignoring requests with “googlebot” in the user-agent field, the collisions disappeared. I’m guessing that they must cache the results of JS scripts in some semi-intelligent way, with the end result that their spidering browser can’t be counted on to behave the way that normal browsers do.

Just an FYI.

###

I wanted to post this as a comment to your question, but apparently StackOverflow won’t let me.

I just ran a rudimentary test of 100,000 iterations in Chrome using the UUID algorithm you posted and got no collisions. Here’s a code snippet:

var createGUID = function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
    });
}

var testGUIDs = function(upperlimit) {
    alert('Doing collision test on ' + upperlimit + ' GUID creations.');
    var i=0, guids=[];
    while (i++<upperlimit) {
        var guid=createGUID();
        if (guids.indexOf(guid)!=-1) {
            alert('Collision with ' + guid + ' after ' + i + ' iterations');
        }
        guids.push(guid);
    }
    alert(guids.length + ' iterations completed.');
}

testGUIDs(100000);

Are you sure there isn’t something else going on here?

###

The answer that originally posted this UUID solution was updated on 2017-06-28:

A good article from Chrome developers discussing the state of Math.random PRNG quality in Chrome, Firefox, and Safari. tl;dr – As of late-2015 it’s “pretty good”, but not cryptographic quality. To address that issue, here’s an updated version of the above solution that uses ES6, the crypto API, and a bit of JS wizardy I can’t take credit for:

function uuidv4() {
  return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  )
}

console.log(uuidv4());

###

The answers here deal with “what’s causing the issue?” (Chrome Math.random seed issue) but not “how can I avoid it?”.

If you are still looking for how to avoid this issue, I wrote this answer a while back as a modified take on Broofa’s function to get around this exact problem. It works by offsetting the first 13 hex numbers by a hex portion of the timestamp, meaning that even if Math.random is on the same seed it will still generate a different UUID unless generated at the exact same millisecond.

Leave a Reply

Your email address will not be published. Required fields are marked *