Является ли python uuid1 последовательным как временные метки?

Python docs утверждает, что uuid1 использует текущее время для формирования значения uuid. Но я не мог найти ссылку, которая гарантирует, что UUID1 является последовательным.

>>> import uuid
>>> u1 = uuid.uuid1()
>>> u2 = uuid.uuid1()
>>> u1 < u2
True
>>> 

Ответ 1

Но не всегда:

>>> def test(n):
...     old = uuid.uuid1()
...     print old
...     for x in range(n):
...             new = uuid.uuid1()
...             if old >= new:
...                     print "OOops"
...                     break
...             old = new
...     print new
>>> test(1000000)
fd4ae687-3619-11e1-8801-c82a1450e52f
OOops
00000035-361a-11e1-bc9f-c82a1450e52f

Ответ 2

UUID не последовательны

Нет, стандартные UUID не должны быть последовательными.

По-видимому, были предприняты некоторые попытки с GUID (зависание Microsoft по UUID), чтобы сделать их последовательными, чтобы помочь с производительностью в определенных сценариях базы данных. Но , являющийся последовательным, не является целью UUID. http://en.wikipedia.org/wiki/Globally_unique_identifier

MAC - последний, не первый

Нет, в стандартных UUID MAC-адрес не является первым компонентом. MAC-адрес является последним компонентом UUID Версии 1. http://en.wikipedia.org/wiki/Universally_unique_identifier

Не допускайте, какой тип UUID

Различные версии UUID должны быть совместимы друг с другом. Поэтому может быть необоснованно ожидать, что у вас всегда есть UUID версии 1. Другие программисты могут использовать другие версии.

Спецификация

Прочитайте спецификацию UUID, RFC 4122, IETF. Только дюжина страниц.

Ответ 3

Из документов UUID python:

Сгенерировать UUID из идентификатора хоста, порядкового номера и текущего времени. Если node не задано, getnode() используется для получения аппаратного адреса. Если указано clock_seq, оно используется как порядковый номер; в противном случае выбирается случайный 14-разрядный порядковый номер.

Из этого я делаю вывод, что сначала указывается MAC-адрес, затем (возможно, случайный) порядковый номер, а затем текущее время. Поэтому я не ожидал, что они гарантированно будут монотонно увеличиваться даже для UUID, сгенерированных одной машиной/процессом.

Ответ 4

Я наткнулся на вероятный ответ в Cassandra/Python из http://doanduyhai.wordpress.com/2012/07/05/apache-cassandra-tricks-and-traps/

Лексикографическое упорядочение TimeUUID

Cassandra обеспечивает среди всех примитивных типов поддержку значений UUID типа 1 (время и сервер) и тип 4 (случайный).

Основное использование UUID (Unique Universal IDentifier) ​​- это получение действительно уникального идентификатора в потенциально распределенной среде.

Cassandra действительно поддерживает UUID версии 1. Он дает уникальный идентификатор, объединяя MAC-адрес компьютера и число 100-наносекундных интервалов с начала григорианского календаря.

Как вы можете видеть, точность составляет всего 100 наносекунд, но, к счастью, она смешана с тактовой частотой, чтобы добавить случайность. Кроме того, MAC-адрес также используется для вычисления UUID, поэтому очень маловероятно, чтобы вы столкнулись с конфликтом на одном кластере машины, если вам не нужно обрабатывать действительно действительно огромный объем данных (не забывайте, что не все являются Twitter или Facebook).

Одним из наиболее подходящих вариантов использования UUID и espcecially TimeUUID является использование его в качестве столбца. Поскольку ключи столбца Cassandra отсортированы, мы можем воспользоваться этой возможностью, чтобы иметь естественный порядок для наших семейств столбцов.

Проблема с стандартным com.eaio.uuid.UUID, предоставленным клиентом Hector, заключается в том, что с ним работать нелегко. В качестве идентификатора вам может потребоваться довести это значение с сервера до уровня представления, а также получить.

В принципе, com.eaio.uuid.UUID переопределяет toString(), чтобы предоставить строковое представление UUID. Однако это форматирование строк не может быть отсортировано лексикографически...

Ниже приведено несколько TimeUUID, сгенерированных последовательно:

8e4cab00-c481-11e1-983b-20cf309ff6dc at some t1
2b6e3160-c482-11e1-addf-20cf309ff6dc at some t2 with t2 > t1

"2b6e3160-c482-11e1-addf-20cf309ff6dc".compareTo("8e4cab00-c481-11e1-983b-20cf309ff6dc") дает -6, что означает, что "2b6e3160-c482-11e1-addf-20cf309ff6dc" меньше/до "8e4cab00-c481-11e1-983b-20cf309ff6dc" , который является неправильным.

Текущее текстовое отображение TimeUUID разделяется следующим образом:

time_low – time_mid – time_high_and_version – variant_and_sequence – node

Если мы переупорядочим его, начиная с time_high_and_version, мы можем отсортировать его лексикографически:

time_high_and_version – time_mid – time_low – variant_and_sequence – node

Класс утилиты приведен ниже:

public static String reorderTimeUUId(String originalTimeUUID)
    {
        StringTokenizer tokens = new StringTokenizer(originalTimeUUID, "-");
        if (tokens.countTokens() == 5)
        {
            String time_low = tokens.nextToken();
            String time_mid = tokens.nextToken();
            String time_high_and_version = tokens.nextToken();
            String variant_and_sequence = tokens.nextToken();
            String node = tokens.nextToken();

            return time_high_and_version + '-' + time_mid + '-' + time_low + '-' + variant_and_sequence + '-' + node;

        }

        return originalTimeUUID;
    }

TimeUUID становятся:

11e1-c481-8e4cab00-983b-20cf309ff6dc
11e1-c482-2b6e3160-addf-20cf309ff6dc

Теперь получим:

"11e1-c481-8e4cab00-983b-20cf309ff6dc".compareTo("11e1-c482-2b6e3160-addf-20cf309ff6dc") = -1

Ответ 5

Использование аргумента uuid.uuid1() без uuid.uuid1() дает непоследовательные результаты (см. Ответ по @basil-bourque), но его можно легко сделать последовательным, если установить аргументы clock_seq или node (потому что в этом случае uuid1 использует реализацию python, гарантирующую уникальная и последовательная timestamp части UUID в текущем процессе):

import time

from uuid import uuid1, getnode
from random import getrandbits

_my_clock_seq = getrandbits(14)
_my_node = getnode()


def sequential_uuid(node=None):
    return uuid1(node=node, clock_seq=_my_clock_seq)


def alt_sequential_uuid(clock_seq=None):
    return uuid1(node=_my_node, clock_seq=clock_seq)



if __name__ == '__main__':
    from itertools import count
    old_n = uuid1()  # "Native"
    old_s = sequential_uuid()  # Sequential

    native_conflict_index = None

    t_0 = time.time()

    for x in count():
        new_n = uuid1()
        new_s = sequential_uuid()

        if old_n > new_n and not native_conflict_index:
            native_conflict_index = x

        if old_s >= new_s:
            print("OOops: non-sequential results for 'sequential_uuid()'")
            break

        if (x >= 10*0x3fff and time.time() - t_0 > 30) or (native_conflict_index and x > 2*native_conflict_index):
            print('No issues for 'sequential_uuid()'')
            break

        old_n = new_n
        old_s = new_s

    print(f'Conflicts for 'uuid.uuid1()': {bool(native_conflict_index)}')
    print(f"Tries: {x}")

Проблемы с несколькими процессами

НО, если вы выполняете несколько параллельных процессов на одном компьютере, то:

  • node который по умолчанию uuid.get_node() будет одинаковым для всех процессов;
  • clock_seq есть небольшой шанс быть одинаковым для некоторых процессов (шанс 1/16384)

Это может привести к конфликтам! Это общая проблема для использования uuid.uuid1 в параллельных процессах на одной машине, если у вас нет доступа к SafeUUID из Python3.7.

Если вы также убедитесь, что для node задано уникальное значение для каждого параллельного процесса, выполняющего этот код, конфликты возникнуть не должны.

Даже если вы используете SafeUUID и устанавливаете уникальный node, все равно возможно иметь непоследовательные идентификаторы, если они генерируются в разных процессах.

Если допустимы некоторые накладные расходы, связанные с блокировкой, то вы можете сохранить clock_seq во внешнем атомарном хранилище (например, в "заблокированном" файле) и увеличивать его при каждом вызове: это позволяет иметь одинаковое значение для node во всех параллельных процессах, а также будет сделать id-ы последовательными Для случаев, когда все параллельные процессы являются подпроцессами, созданными с использованием multiprocessing: clock_seq можно "разделить" с помощью multiprocessing.Value multiprocessing. clock_seq