Base de datos de Messenger (parte 2): particionamos "beneficio"

Hemos diseñado con éxito la estructura de nuestra base de datos PostgreSQL para almacenar correspondencia, ha pasado un año, los usuarios la están llenando activamente, ahora tiene millones de registros y ... algo comenzó a disminuir.



El hecho es que con el crecimiento del volumen de la tabla, la "profundidad" de los índices también crece , aunque logarítmicamente. Pero con el tiempo, esto obliga al servidor a procesar muchas páginas de datos para realizar las mismas tareas de lectura / escritura que al principio.

Aquí es donde la partición viene al rescate.

Observo que no se tratará de fragmentación, es decir, la distribución de datos entre diferentes bases de datos o servidores. Porque, incluso dividiendo los datos en varios servidores, no puede deshacerse del problema de la "hinchazón" de los índices con el tiempo. Está claro que si puede permitirse poner en marcha un nuevo servidor todos los días, sus problemas ya no se encontrarán en el plano de una base de datos específica.

Consideraremos no scripts específicos para implementar la partición "en hardware", sino el enfoque en sí mismo: qué y cómo "cortar en segmentos", y a qué conduce ese deseo.

Concepto


Una vez más, definimos nuestro objetivo: queremos asegurarnos de que hoy, mañana y después de un año, el número de datos de PostgreSQL leídos durante cualquier operación de lectura / escritura permanezca aproximadamente igual.

Para cualquier dato acumulado cronológicamente (mensajes, documentos, registros, archivos, ...), la elección natural como clave de partición es la fecha / hora del evento . En nuestro caso, tal evento es el momento en que se envió el mensaje .

Tenga en cuenta que los usuarios casi siempre trabajan solo con los datos "más recientes" : leen los mensajes más recientes, analizan los registros más recientes ... No, por supuesto, pueden desplazarse más atrás en el tiempo, solo lo hacen muy raramente.

A partir de estas restricciones, resulta obvio que las secciones "diarias" serán la mejor solución para los mensajes; después de todo, nuestro usuario casi siempre leerá lo que le llegó "hoy" o "ayer".

Si escribimos y leemos casi solo una sección durante el día, esto nos da un uso aún más eficiente de la memoria y el disco , ya que todos los índices de sección caben fácilmente en la RAM, a diferencia de las secciones "grandes y en negrita" de toda la tabla.

paso a paso


En general, todo lo anterior suena como un beneficio sólido. Y es posible, pero por el bien de esto tendremos que esforzarnos mucho, porque la decisión de dividir una de las entidades lleva a la necesidad de "cortar" y asociarlo .

Mensaje, sus propiedades y proyecciones.


Dado que decidimos cortar los mensajes por fechas, es razonable dividir las propiedades de las entidades (archivos adjuntos, listas de correo) y también por la fecha del mensaje , dependiendo de ellas.

Dado que una de nuestras tareas típicas es solo ver registros de mensajes (no leídos, entrantes, todos), también es lógico "dibujarlos" en la partición por fechas de mensaje.


Agregue la clave de partición (fecha del mensaje) a todas las tablas: destinatarios, archivo, registros. No puede agregar al mensaje en sí, pero use el DateTime existente.

Temas


Como el tema es uno en varios mensajes, no será posible "cortarlo" en el mismo modelo; uno debe confiar en otra cosa. En nuestro caso, la fecha del primer mensaje en correspondencia es ideal, es decir, el momento de creación del tema en sí.


Agregue la clave de partición (fecha del tema) a todas las tablas: tema, participante.

Pero ahora tenemos dos problemas a la vez:

  • en qué sección buscar publicaciones sobre el tema?
  • en qué sección buscar un tema de un mensaje?

Puede, por supuesto, continuar buscando en todas las secciones, pero será muy triste y negará todas nuestras ganancias. Por lo tanto, para saber exactamente dónde buscar, haremos enlaces / punteros lógicos a las secciones:

  • en el mensaje, agregue un campo con la fecha del tema
  • agregue al tema un conjunto de fechas de mensajes para esta correspondencia (puede usar una tabla separada, o puede usar una matriz de fechas)



Dado que habrá pocas modificaciones en la lista de fechas de mensajes para cada correspondencia individual (después de todo, casi todos los mensajes caen en los próximos 1-2 días), me detendré en esta opción.

Total, la estructura de nuestra base tomó la siguiente forma, teniendo en cuenta la partición:

Tablas: RU, si no te gusta cirílico, es mejor no mirar los nombres de tablas / campos
--     CREATE TABLE "_YYYYMMDD"( "" uuid PRIMARY KEY , "" uuid , "" date , "" uuid , "" --    timestamp , "" text ); CREATE TABLE "_YYYYMMDD"( "" date , "" uuid , "" uuid , PRIMARY KEY("", "") ); CREATE TABLE "_YYYYMMDD"( "" date , "" uuid PRIMARY KEY , "" uuid , "BLOB" uuid , "" text ); CREATE TABLE "_YYYYMMDD"( "" date , "" uuid , "" smallint , "" timestamp , "" uuid , PRIMARY KEY("", "", "") ); CREATE INDEX ON "_YYYYMMDD"("", "", "" DESC); --     CREATE TABLE "_YYYYMMDD"( "" date , "" uuid PRIMARY KEY , "" uuid , "" text ); CREATE TABLE "_YYYYMMDD"( "" date , "" uuid , "" uuid , PRIMARY KEY("", "") ); CREATE TABLE "_YYYYMMDD"( "" date , "" uuid PRIMARY KEY , "" date ); 


Ahorre un centavo bonito


Bueno, si no utilizamos la opción clásica de particionamiento basada en la distribución de valores de campo (a través de activadores y herencia o PARTICIÓN POR), sino "manualmente" a nivel de aplicación, podemos ver que el valor de la clave de particionamiento ya está almacenado en el nombre de la tabla misma.

Por lo tanto, si está tan preocupado por la cantidad de datos almacenados , puede deshacerse de estos campos "adicionales" y consultar tablas específicas. Es cierto que todas las muestras de varias secciones en este caso deberán enviarse al lado de la solicitud.

Source: https://habr.com/ru/post/483170/


All Articles