Short Ids

January 05, 2018 uuids

Hace poco me encontré con la siguiente necesidad. Debía pasar un identificador de recurso por url, el cual se da de alta en una base de datos (junto con otros datos) y tiene un nombre descriptivo (que debe ser único), asociado que es especificado por un humano. En principio se me ocurrieron 2 formas de identificar al objeto:

  1. Usar un valor autoincremental de la base de datos como PK y agragar una restricción para que no se repita el nombre, más un índice.
  2. Usar un valor random generado (por ejemplo los UUID que están de moda), más la restricción y el índice
  3. Usar el nombre como PK

De la primera opción no me gusta el hecho de estar pasando un número autoincremental en la url, lo que permite que cualquiera pueda fácilmente modificarlo y adivinar otro recurso por su id.

La segunda opción es bastante utilizada, sólo que los UUID en su representación como string ocupan 36 caracteres (32 si quitamos los separadores), lo cual me parece demasiado largo para un identificador. Pero si tienen como ventaja que son URL friendly (es decir que no hace falta hacerles un urlencode), y también es improbable que un usuario acierte a otro recurso (a menos que contemos con 2.71 quintillones de recursos, en cuyo caso la probabilidad será del 50%: más sobre el tema).

La tercera opción implica hacer el urlencode del nombre, no es fácil de deducir otro recurso y con respecto al largo podría ser un problema. Pero creo que la contra que tiene es la de revelar cuál es el recurso: si estuviéramos identificando a una empresa, estaríamos revelando el nombre de la empresa, y quizás esto no sea deseable (por ejemplo si estamos en un sistema de licitaciones).

El caso de los UUIDs se puede resolver usando una codificación en base64 en vez de base16. La codificación base64 representa 6 bits por caracter y su alfabeto permite usarlos en una URL. Al representar 6 bits por caracter en vez de 4, pasamos de 32 caracteres (128 bits / 4) a 22 caracteres (128 / 6, redondeado). La cuestión es que 22 caracteres sigue pareciendo mucho para un identificador, aunque no deja de ser una buena solución ya que usando los 8 bits que representan cada byte tendríamos el mínimo de 16 caracteres, pero no serían URL friendly.

Si la opción de los UUIDs resulta útil, quizás convenga darle una mirada a http://hashids.org o también pueden ver unas pruebas que hice para representar los UUIDs en base 64: https://github.com/gastonfournier/short-ids

En mi caso quería que el id tuviera una correlación con los datos de entrada, para así poder obtener la PK sin acceder a la base y así no requerir agregar otro índice. La función no necesariamente debe ser reversible. Qué ventaja tiene esto? En principio no mucha, pero supongamos que tenemos un índice de algunos de nuestros datos entre los que está el identificador (un Elasticsearch por ejemplo), y queremos buscar un dato en el índice, pero sólo contamos con el nombre del recurso que queremos buscar. Sin necesidad de acceder a la base de datos yo podría calcular la PK que me serviría para encontrar el dato en el índice. Si hubiera usado los UUIDs, hubiera necesitado acceder por el nombre del recurso para así obtener el id y luego poder buscar en el índice. Como decía, no se gana mucho... quizás es un caso muy particular, pero tampoco es más costoso hacerlo de esta manera:

  1. tomo el nombre del recurso
  2. calculo el hashCode del nombre (esto da un Integer con una probabilidad baja de colisiones)
  3. convierto el entero a binario (en Java un integer está representado por 32 bits, esto es 1/4 de lo que ocupan los UUIDs)
  4. uso el algoritmo de pasar de binario a base64 (obteniendo un string de 6 caracteres de largo: 32/6)

Profile picture

Written by Gastón Fournier Software Engineer at @getunleash working from Cunit, Tarragona, Spain, where I live with my wife and 2 dogs. Find me on Twitter Github LinkedIn