preloader
  • Inicio
  • Destacando las 10 nuevas caraterísticas de Postgres: Replicación lógica y particionamiento

blog-thumb

¡Heya! En este artículo, vamos a explorar dos de las principales caraterísticas incluidas en la próxima versión de PostgreSQL: Replicación Lógica y particionamiento. No hace falta decir que estas funciones aún no están disponibles en la versión estable, por lo que es posible cambiarlas o ampliarlas.

Advertencia de Publicidad! EL artículo es solo u adelanto de la próxima charla Desmitificando
la replicación lógica en PostgreSQL en Percona Live Santa Clara 2017. 

Replicación Lógica

El mecanismo de replicación lógica actual solo se basa en filas. Si está en el mundo MySQL, notará que el modo de declaración no es compatible. Si no está familiarizado con la diferencia entre los modos, TL; DR, no importa cuántas filas estén involucradas en la consulta de origen, se enviarán como filas individuales a los subordinados. Es decir, una declaración única de varias filas como INSERT de la fuente producirá una entrada por fila modificada.

Esto es algo que es posible que desee tener en cuenta al realizar cargas masivas, ya que existen otras herramientas/técnias que podrían ser más adecuadas además de transmitir todo desde el maestro utilizando el flujo de replicación lógica.

Generalmente, consta de tres elementos visibles, también detallados en la imagen a continuación.

  • una publicación (fuente)
  • una suscripción (consumidor)
  • y una ranura de replicación lógica.

La más importante y probablemente la más compleja es la ranura de la replicación lógica. La magia se hace internamente a través del “pgoutput” complemento, que es el fragmento de código encargado de traducir los registros WAL (“pg_wal”) en entradas en el log lógico ( pg_logical )

El panorama completo se puede resumir de la siguiente manera: los consumidores se suscriben a un solo publicador mediante una ranura, que contiene la instantánea (LSN) de la base de datos (el momento dado del clúster). La ranura proporcionará la información al motor sobre el momento en el que se deben replicar los cambios.

En este punto, es importante tener en cuenta que la función completa no está completamente comprometida y se espera que cuente on una WITH COPY DATA opción en la creación del evento de suscripción para sincronizar los datos de la fuente. Actualmente, el parche tiene algunos errores y está en proceso de revisión.

Aunque todo el tema es interesante, todo lo relacionado con la decodificación lógica se omitirá en este artículo. Puede hacer más que solo la replicación de Postgres a Postgres.

Fraccionamiento

En las versiones anteriores, era posible alcanzar un enfoque de partición muy flexible al combinar la herencia y los activadores basados en varios idiomas. La implementación actual no permite mezclar herencia y particionamiento, pero aún tiene cierta flexibilidad para separar y adjuntar particiones, usando una sintaxis explícita.

En el ejemplo actual, vamos a crear tres particiones sin datos, solo para mantenernos enfocados solo en el POC.

POC

El concepto actual funciona en torno a tener subordinados con una política de retención diferente de cada partición replicando cada uno en diferentes destinos y filtrando las operaciones DELETE. Además, podemos crear una estructura ficticia, para puntar a cada partición externa para informar o consultar datos históricos.

El concepto tiene tres tipos de nodos / bases de datos:

  • Un proxy (que contiene solo contenedores de datos externos que apuntan a tablas secundarias en herencia de una tabla ficticia)
  • Un maestro (que contiene toda las particiones)
  • Bases de datos de fragmentos (solo contienen la información de fragmentos correspondientes)

Más o menos, usando los comandos de este artículo, debería terminar con una imagén como esta:

Como probablemente notará, al eliminar las filas en la base de datos de origen y filtrar los eventos DELETE en el momento de la publicación, terminará con subordinados que contienen mas datos, lo que permite consultas de mayor tiempo. Esto es particularmente útil para dividir consultas de BI diferentes capas dependiendo de las especificaciones de los rangos de fechas, guardar los propósitos de almacenamiento en la fuente o mantener también un tamaño de tabla más mantenible. Las consultas contra el archivo se pueden realizar directamente en los nodos o mediante la implementación de proxy mencionada adelante.

Partición en la base de datos de origen / punto de entrada

La base de datos maestra contendrá las definiciones y los datos más recientes. El concepto actual, se alimenta del tema de un corredor de Apache Kafka que está dividido en tres. Vamos a alimentar esta tabla con flujos usando el comando COPY.

Las tablas de la base de datos maestra actual DDL es:

CREATE TABLE main (group_id char(2), stamp timestamp without time zone DEFAULT now(), payload jsonb) PARTITION BY LIST(group_id); CREATE TABLE main_shard0 PARTITION OF main FOR VALUES IN (‘P0’); CREATE TABLE main_shard1 PARTITION OF main FOR VALUES IN (‘P1’); CREATE TABLE main_shard2 PARTITION OF main FOR VALUES IN (‘P2’);

CREATE INDEX ix_main_shard_p0_key ON main_shard0 (stamp,(payload->>'key'));
CREATE INDEX ix_main_shard_p1_key ON main_shard1 (stamp,(payload->>'key'));
CREATE INDEX ix_main_shard_p2_key ON main_shard2 (stamp,(payload->>'key'));

La group_id columna contiene el número de partición del tema del que se han consumido los datos del corredor de Kafka.

Ahora es el momento de publicarlos dentro del filtrado de eventos correspondientes. En este punto, no hay ningún espacio de replicación asociado con las publicaciones:

CREATE PUBLICATION P_main_P0 FOR TABLE main_shard0 WITH (NOPUBLISH DETELE);
CREATE PUBLICATION P_main_P1 FOR TABLE main_shard1 WITH (NOPUBLISH DETELE);
CREATE PUBLICATION P_main_P2 FOR TABLE main_shard2 WITH (NOPUBLISH DETELE);

Según el estado actual de las últimas confirmaciones en PostgreSQL, la replicación lógica no admite el filtrado por valor de columna como lo hace la herramienta pglogical. Incluso aunque es posible filtrar por declaración de evento, que sigue siendo bastante útil para nuestro propósito ( NOPUBLISH|PUBLISH ) como se describe anteriormente.

Creando los nodos

La definición de la tabla en los nodos debería ser sencilla:

CREATE TABLE main_shard0 (group_id char(2), stamp timestamp without time zone, payload jsonb);

Ahora necesitamos crear la SUSCRIPCIÓN para alimentarnos de la PUBLICACIÓN correspondiente en la base de datos maestra. Como la implementación actual del evento SUBSCRIPTION no es compatible con los datos de copia y las particiones están vacías, vamos a crear una ranura de replicación lógica en la fuente. Esto se hace facilmente usando la CREATE SLOT cláusula. Esto significa que establecerá la posición LSN desde la que se deben aplicar los cambios al destino:

CREATE SUBSCRIPTION P_main_P0
    CONNECTION 'port=7777 user=postgres dbname=master'
    PUBLICATION P_main_P0 WITH (CREATE SLOT);

Es notable notar que después de la creación de la suscripción notarás nuevos trabajadores a cargo de enviar y recibir esos cambios, como se describe en la imagen de arriba.

Como no es el alcance de este artículo, voy a omitir la explicación de las ranuras de replicación Lógica | transmición para mantenerlo legible. Sin embargo, es un concepto central de la función de replicación.

Consultar desde una base de datos externa

Este ejemplo no tiene otro propósito que mostrar una caraterísticas ya existente (aunque mejorada en versiones recientes) en acción. Pero muy especialmente voy a destacar la HERENCIA en un MESA EXTRANJERA.

La siguiente DLL reside en una proxy base de datos, que no contiene ningún dato de las particiones y solo pretende mostrar algunas capacidades relativamente nuevas en Postgres,

CREATE EXTENSION postgres_fdw; CREATE SERVER shard0 FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host ‘127.0.0.1’, port ‘7777’,dbname ‘shard0’); CREATE SERVER shard1 FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host ‘127.0.0.1’, port ‘8888’,dbname ‘shard1’); CREATE SERVER shard2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host ‘127.0.0.1’, port ‘9999’,dbname ‘shard2’);

CREATE USER MAPPING FOR postgres SERVER shard0 OPTIONS(user 'postgres');
CREATE USER MAPPING FOR postgres SERVER shard1 OPTIONS(user 'postgres');
CREATE USER MAPPING FOR postgres SERVER shard2 OPTIONS(user 'postgres');

CREATE TALBE main (group_id char(2), payload jsonb);
CREATE FOREIGN TABLE main_shard0 (CHECK (group_id = 'P0'))INHERITS (main) SERVER shard0;
CREATE FOREIGN TABLE main_shard1 (CHECK (group_id = 'P1'))INHERITS (main) SERVER shard1;
CREATE FOREIGN TABLE main_shard2 (CHECK (group_id = 'P2'))INHERITS (main) SERVER shard1;

Como puede apreciar, estamos combinando herencia, verificaciones de restricciones y envoltorios de datos externos para evitar consultas a tablas remotas que no coinciden con el “group_id” filtro. Además, adjunté una EXPLICACIÓN como prueba de que ninguna de las otras tablas extranjeras ha sido examinada.

proxy=$ SELECT * FROM main WHERE payload->>'key' = '847f5dd2-f892-4f56-b04a-b106063cfe0d' and group_id = 'P0';
    group_id  |           payload
--------------+-----------------------------------------------------------
    P0        |  {"key": "847f5dd2-f892-4f56-b04a-b106063cfe0d", "topic":  "PGSHARD",       "offset": 47, "payload": "PXdmzb3EhEeNDdn5surg2VNmEdJoIys9", "partition": 0}
(1 row)

proxy=# EXPLAIN SELECT *
            FROM main
            WHERE payload->>'key' = '847f5dd2-f892-4f56-b04a-b106063cfe0d'
                AND group_id = 'P0';
                QUERY PLAN  
---------------------------------------------------------------------------
Append   (cost=0.00..135.07 rows=2 width=44)
    -> Seq Scan on main  (cost=0.00..0.00 rows=1 width=44)
            Filter: ((group_id = 'P0'::bpchar) AND ((payload ->> 'key'::text) = '847f5dd2-f892-4f56-b04a-b106063cfe0d'::text))
    -> Foreign Scan on main_shard0  (cost=100.00..135.07 rows=1 width=44)
            Filter:  ((payload ->> 'key'::text) = '847f5dd2-f892-4f56-b04a-b106063cfe0d'::text)
(5 rows)

Espero que les haya gustado el artículo!