Является ли 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