Ya hemos visto en la práctica que todo documento almacenado en MongoDB debe contener una clave única cuyo nombre va a ser por defecto "_id".
El valor almacenado dentro de este identificador único puede ser de cualquier tipo (una cadena, un número...) pero si dejamos que se genere de manera automática (que es el caso más habitual) su tipo va a ser ObjectId.
Dentro de una colección este campo debe ser único, por lo que no puede almacenar dos valores iguales.
En un sistema de base de datos tradicional se suelen usar valores auto-numéricos, es decir, números crecientes que se aumentan con cada registro introducido en la base de datos. En el caso de MongoDB y otros gestores distribuidos masivamente escalables se utiliza por defecto otra técnica más sencilla que permite asegurar la unicidad de ese valor sin tener que coordinar a los diferentes nodos.
Nota: Recuerda además que un nodo puede estar caído o haber perdido la comunicación con los demás, por lo que en ese caso ni siquiera se podrían sincronizar para generar los ids únicos.
Este es, por ejemplo, el aspecto que presenta un valor ObjectId y que ya hemos visto en los ejemplos prácticos:
5294e1005e953e0dcbc515db
Los valores de tipo ObjectId están formados por 12 bytes de información dividida en grupos con la siguiente distribución:
- Los cuatro primeros bytes son una marca de tiempo que indica el número de segundos transcurridos desde la fecha epoch de UNIX (el 1 de Enero de 1970). Esto nos da una referencia temporal de cada inserción y hace que los registros se guarden en disco aproximadamente en el orden de inserción.
- Los tres siguientes bytes son un identificador único de la máquina que ha generado el ObjectId. Por regla general es un resumen digital (hash) obtenido a partir del nombre de red de la máquina. De esta forma varias máquinas de un sistema distribuido generan valores distintos para esta parte del identificador.
- Los dos siguientes son el identificador del proceso que genera el ObjectId. De este modo en una misma máquina si hay varios procesos ejecutándose a la vez (lo habitual) cada uno tendrá un valor diferente para esta parte.
- Los tres últimos bytes son un contador incremental. De esta forma se asegura un valor único para cada segundo (primer grupo) dentro de una misma máquina y un mismo proceso. Como son tres bytes eso nos da para almacenar hasta 256^3 valores diferentes (casi 17 millones) por cada proceso en un mismo segundo, algo más que suficiente para la velocidad que puede dar un ordenador actualmente (y en los próximos años).
Podemos verlo mejor gráficamente en la siguiente figura:
Nota: Aunque los ObjectId ocupan solamente 12 bytes de espacio de almacenamiento, cuando los vemos representados en nuestros documentos aparentan ocupar justo el doble (la cadena de nuestro ejemplo tiene 24 caracteres de longitud). En realidad su representación textual transforma cada dígito en su valor hexadecimal, y como se necesitan dos caracteres para cada byte, sale el doble de longitud al verlo por pantalla. Sin embargo ocupan mucho menos y no son nada pesados, así que no hay nada de que preocuparse.
Por ejemplo, si echamos un vistazo a los siguientes identificadores:
Podemos observar que los del registro #2 y #3 se han generado muy seguidos en el tiempo, desde la misma máquina pero por procesos diferentes. Sin embargo el primero se generó desde una máquina diferente. Algunos drivers incluso nos ofrecen la posibilidad de saber la fecha exacta de generación del registro obteniendo esta información de los cuatro primeros bytes del identificador.
Nota: No obstante los diferentes nodos de MongoDB no tienen que estar sincronizados en cuanto a tiempo (aunque conviene) y puede que de hecho no lo estén y se falseen esos datos temporales en el ObjectId. De todos modos eso no es importante. Lo único importante es que el identificador sea único dentro de la colección y de este modo lo tenemos asegurado incluso en el caso de que cada máquina tenga una hora diferente.
La generación de estos identificadores generalmente se delega al driver de conexión desde el cliente de la base de datos, de modo que libera al servidor de tener que hacerlo. Esto refleja muy bien la filosofía de MongoDB que quiere hacer el servidor lo más ligero posible, y hace que sea todavía más fácil de escalar horizontalmente. Además al hacerlo así obtendremos automáticamente el identificador de los objetos en el lado cliente tras una inserción sin necesidad de tener que recuperarla desde el servidor, ahorrando viajes por la red y descargando más al servidor.
Sigan sintonizándonos
En los próximos artículos empezaremos a ver el uso de MongoDB con un driver y desde un lenguaje concretos. Para el caso he elegido C# y la plataforma .NET. Veremos de qué diferentes maneras podemos conectarnos a MongoDB desde C# y cómo podemos realizar las operaciones más comunes.