Вы находитесь на странице: 1из 9

Consorcio SIU

PARTICIONADO DE TABLAS PARA MEJORA DE PERFORMANCE

Consideraciones Preliminares
Si
bien
Postgresql
no
soporta
directamente particionado horizontal de tablas
(refirindome a la sentencia de Particionado a
travs de DDL), existen mtodos de hacerlo
aunque sean algo engorrosos o no muy
soportados.

escogiramos para el mismo caso, particionar


por mes, podramos consultar con buenos
resultados por este rango, pero si consultamos
por periodo la performance seria menor ya que
el ejecutor deber navegar por varias tablas
para traer el resultado. Es ah donde se produce
el pequeo overhead. Si por alguna razn
debera consultar de todos los periodos, la
performance podra ser menor que si no
hubiese particin.

Tanto las tcnicas de Particionado


horizontal como vertical de una tabla, intentan
disminuir los accesos a disco, ordenando
De esto se desprende que el Particionado
fsicamente los datos por un factor en comn.
debe ser pensado de acuerdo a las consultas
que realizaremos.
Si bien el particionado puede ser
establecido previo a una carga de datos, por lo
Esta planteado para versiones futuras, que
general los desarrolladores no tienen en cuenta el particionado ser incluido en el DDL. La
esto.
tcnica que utilizaremos es con herencia de
tablas (mejor dicho herencia de clases).
El particionado genera un pequeo
overhead (sobrecarga) de procesamiento
proporcional, en comparacin con la tabla sin
Particionado Vertical
esta tcnica. Por lo que particionar una tabla
con pocos registros puede que no sea la mejor
El particionado vertical busca disminuir los
de las ideas.
accesos a disco a travs de la proyeccin
(seleccin de columnas).
Ambos tipos de Particionado son distintos y
se logran de distinta manera. En tanto el
El particionado vertical bien puede ser
Particionado horizontal es por rango, el vertical considerado desde el diseo ( dos o ms tablas,
divide una tabla.
unidas por una llave con cardinalidad 1:1 ).
Particionado horizontal
En el caso de Particionado horizontal, el
acceso a disco disminuye si la consulta que
realizamos busca solamente unos pocos
elementos del conjunto de rangos (por
seleccin). Si las consultas por lo general
acceden a elementos de todos los grupos, el
Particionado horizontal no ayudar mucho, e
inclusive puede que empeore la performance.

En este caso sucede lo mismo que con las


de particionado horizontal, pero la sobrecarga
no es el mismo ya que no utiliza herencia.
El modo de simular esto es creando ambas
tablas y crear una vista global de ambas, que
incluyan todas las columnas.
El particionado vertical, por lo general
puede ser tenido en cuenta por el desarrollador,
pero
el
DBA
puede
implementarlo
posteriormente.

Por ejemplo, supongamos este ejemplo:


tenemos una tabla que contiene movimientos de
los ltimos 3 aos. Nuestras consultas, casi
siempre consultan por periodo (1er semestre 2do semestre / ao), por lo que si particionamos
por periodo obtendremos 6 particiones
reduciendo los accesos a disco prcticamente
Caso 1: ejemplo particionando
unas 6 veces en consultas sobre 1 periodo. Si preexistentes
SIU

tablas

PGINA 1 DE 9

Consorcio SIU
Consorcio SIU

INSERT INTO anio_2009 (select * from


Supongamos que tenemos la siguiente padre2 where EXTRACT(year from fecha) =
tabla:
'2009');
CREATE TABLE padre AS
DELETE FROM padre
SELECT
WHERE
(now() - (round(random()*1) ||
tableoid = ('padre'::regclass)::oid;
'
year')::interval)::date
as
fecha,
Con esto, tendremos los registros
i,
separados en las particiones, pudiendo acceder
random()*100 as flotante
a los mismos realizando una simple consulta a
FROM
la tabla padre:
generate_series(1,50000) j(i);
SELECT DISTINCT
Esta sentencia crea una tabla con una
tableoid::regclass FROM padre;
columna de tipo date que contendr fechas
aleatorias con 2 aos distintos (actual y actualCon esto sabremos en que particin est
1). La columna i contiene los valores de la almacenada cada registro:
fuente del FROM y la otra columna es
simplemente un valor aleatorio.
SELECT tableoid::regclass, *
FROM padre;
Podremos observar los aos de la tabla
con la cantidad de tuplas para cada uno, con la
siguiente consulta:
Los datos estn particionados, pero: Qu
pasa a partir de ahora?
SELECT DISTINCT
EXTRACT(year from fecha),
La forma ms sencilla de bifurcar los datos
count(*)
a su particin correspondiente es utilizando
FROM padre
RULES. Otra forma ms eficiente pero compleja
GROUP BY fecha;
es a travs de disparadores.
Suponiendo que queremos particionar por
Bsicamente las RULES son disparadores
ao,
deberemos
crear
las
tablas pero ms limitados pero ms sencillos de
correspondientes para cada uno de los mismos: declarar.
CREATE TABLE anio_2008
CREATE RULE ins_2008 AS ON INSERT
(CHECK
TO padre WHERE EXTRACT(year from
(EXTRACT(year from fecha) = '2008')) fecha) = '2008'
INHERITS (padre);
DO INSTEAD INSERT INTO anio_2008
VALUES (NEW.*);
CREATE TABLE anio_2009
(CHECK
CREATE RULE ins_2009 AS ON INSERT
(EXTRACT(year from fecha) = '2009'))
TO padre WHERE EXTRACT(year from
INHERITS (padre);
fecha) = '2009'
DO INSTEAD INSERT INTO anio_2009
Luego, insertaremos todos los registros de VALUES (NEW.*);
la tabla padre en cada una de las particiones
que tengamos y borraremos los registros que
estn insertados en la tabla padre (no en las
Ahora, cada vez que intentemos insertar en
hijas):
la tabla padre, los registros sern bifurcados a
las particiones.
INSERT INTO anio_2008 (select * from
padre2 where EXTRACT(year from fecha) =
Adicionalmente, tendremos que crear los
'2008');
ndices para las particiones y la tabla padre (ya
SIU

PGINA 2 DE 9

Consorcio SIU

PARTICIONADO DE TABLAS PARA MEJORA DE PERFORMANCE

que si por alguna razn la consulta viaja por


ms de 1 particin, necesitaremos las
ubicaciones generales):
CREATE INDEX x_fecha_anio_2008
ON anio_2008 (fecha);

super
(tipo tipo_persona
CHECK
(upper(tipo) IN (cliente,persona)) )
INHERITS (cliente, persona);

CREATE INDEX x_fecha_anio_2009


ON anio_2009 (fecha);
CREATE INDEX x_fecha_padre
ON padre2 (fecha);
Para actualizar las particiones, deberemos
utilizar en este caso:
INSERT INTO anio_2009
(select * from anio_2008
WHERE
EXTRACT (year from fecha) = '2009');
DELETE FROM anio_2008
WHERE
EXTRACT (year from fecha) = '2009';

Caso 2: Ejemplo de particionado complejo


con disparadores.

Padre

cliente

Super

persona

INSERT
S

En este ejemplo implementaremos un


particionado por tipo. Esto quiere decir que
tendremos
particiones
que
contendrn
elementos de un determinado tipo.
Como vemos, tenemos dos tipos (cliente y
persona). La tabla padre contendr los IDs y
por ende, los tendremos duplicados ya que los
hijas tambin tendrn el ID del padre para
distinguir los registros.

En este ejemplo haremos algo un poco ms


Para la bifurcacin, en este caso,
osado. Suponiendo el siguiente esquema de
utilizaremos un disparador en PL/pgsql, ya que
tablas:
las RULES no permitiran hacer operaciones
complejas como las que necesitamos.
CREATE TABLE
padre (id int PRIMARY KEY);
CREATE OR REPLACE FUNCTION
simple_part()
RETURNS TRIGGER AS $simple_part$
DECLARE
CREATE TABLE
flag boolean = true;
persona
BEGIN
(sexo char(1) CHECK (upper(sexo) IN
('M','F')))
IF (NEW.tipo = 'cliente') THEN
INHERITS (padre);
INSERT INTO cliente(id,dato)
VALUES (NEW.id, NEW.dato);
CREATE DOMAIN tipo_persona AS text
ELSIF (NEW.tipo = 'persona') THEN
CHECK (VALUE IN ('cliente', 'persona'));
BEGIN
INSERT INTO persona(id,sexo)
CREATE TABLE
CREATE TABLE
cliente (dato text) INHERITS (padre);

SIU

PGINA 3 DE 9

Consorcio SIU
Consorcio SIU

VALUES (NEW.id, NEW.sexo);


EXCEPTION
Ejecutaremos
la
opcin
EXPLAIN
WHEN check_violation THEN
ANALYZE para verificar el la ruta de la consulta:
flag = false;
END;
END IF;
SELECT *
if flag = true THEN
FROM padre2
INSERT INTO padre
WHERE fecha
VALUES (NEW.id);
BETWEEN '2009-09-09'
END IF;
AND '2009-10-10';
RETURN NULL;
EXCEPTION
WHEN others THEN
RAISE NOTICE 'ERROR (%)',
SQLERRM;
END;
$simple_part$ VOLATILE LANGUAGE
plpgsql;
Luego de creada la funcin que retorna el
disparador, debemos declarar el mismo:
CREATE TRIGGER simple_part
BEFORE INSERT ON super
FOR EACH ROW
EXECUTE PROCEDURE simple_part();
Podemos hacer un pequeo set de prueba:
INSERT INTO
super(id,tipo,dato)
VALUES (1,'cliente', 'dato cualquiera');
INSERT INTO
super(id,tipo,sexo)
VALUES (2,'persona','d'); --debe fallar
INSERT INTO
super (id,tipo,sexo)
VALUES (3,'persona','m');

Podemos observar que para las 3


tablas implicadas (padre2, anio_2008 y
anio_2009), se accedi de manera distinta.
En la tabla padre2 no hay registros por lo
que utilizo Bitmap Index Scan. Si bien no hay
datos directamente en la tabla padre, el nodo
APPEND rene todos los datos dependientes
de las tablas heredadas.
Para la tabla anio_2008, simplemente se
utilizo un Index Scan, evitando accesos ms
costosos.

En cuanto a la tabla anio_2009, se accedi


por acceso secuencial. Esto sucedi porque
esta consulta trajo todos los registros de la tabla
Las consultas las realizaremos sobre la (ya que cuando insertamos los valores de
clase padre, de la misma manera que prueba no cambiamos los meses). En el caso
de
que
hubiese
menos
ocurrencias,
ejecutamos en el ejemplo 1.
seguramente hubiese utilizado el ndice.
Entendiendo a fondo el trabajo
planeador con el particionado horizontal
Para explicar el funcionamiento
planeador tomaremos el caso del ejemplo 1.
SIU

El overhead, es generado por el nodo


APPEND. Si la consulta hubiese obtenido
ocurrencias de la otra tabla hija, no tendramos
mejoras en la performance comparado a una
del sola tabla.

del

PGINA 4 DE 9

Consorcio SIU

PARTICIONADO DE TABLAS PARA MEJORA DE PERFORMANCE

Si quisiramos buscar solamente por ao y


no por rango de fechas, podramos crear ndices
por expresin:
CREATE INDEX x_fecha_anio ON padre2
(EXTRACT(year from fecha));

(SELECT
'{a1,b2,c3,e4}'::text[]) a(n)) as
texto,
round(random()*100) as entero,
i + round(random()*1000) as entero2
FROM generate_series(1, 50000) j(i);

Por lo tanto la consulta quedara:


Aadimos una llave:
SELECT *
FROM padre2
WHERE
EXTRACT(year from fecha) = '2009' ;

ALTER TABLE general ADD PRIMARY


KEY(i);

Con esto, estamos simulando una tabla,


Si realizamos un EXPLAIN ANALYZE, que ya est cargada con datos. Ahora, lo que
veremos que el planeador a decidido utilizar vamos a hacer a continuacin es particionarla
nuestro ndice:
verticalmente en dos tablas. Luego la tabla
general puede ser borrada o dejada de backup.
Para ello creamos las tablas:
CREATE TABLE general1 AS
SELECT i, floaty, epoch, arreglo
FROM general;

De esto se desprende que los ndices sobre


la tabla padre afectarn a todas las consultas
registros de las hijas.

Caso 3: Ejemplo de particionado vertical


Supongamos el ejemplo de la siguiente
tabla:
CREATE TABLE general AS
SELECT
i,
random()::double precision as
floaty,
(now() - (round(random()*100) ||
' days')::interval)::timestamp as epoch,
('{' || i + round(random()*10) || ','
||i + round(random()*100)||'}')::int[] as arreglo,
(select
n[round(random()*4)]::text
FROM
SIU

CREATE TABLE general2 AS


SELECT i, texto, entero, entero2
FROM general;
ALTER TABLE general
RENAME TO general_TO_DROP;
Paso siguiente, creamos la vista que
contendr la combinacin de ambas tablas:
CREATE VIEW general AS
SELECT *
FROM general1 JOIN general2 USING (i);
Por lo tanto, cuando hagamos consultas
que traigan solamente columnas de una de las
dos particiones, el planeador decidir no
acceder a la particin no ocurrente.
Vemos como realizar una consulta a la
vista:
part=# SELECT * FROM general LIMIT 1;
-[ RECORD 1 ]----------------------PGINA 5 DE 9

Consorcio SIU
Consorcio SIU

i
|1
floaty | 0.791756465099752
epoch | 2009-09-28 15:31:34.818921
arreglo | {9,39}
texto | c3
entero | 75
entero2 | 483
Y el EXPLAIN nos muestra:
EXPLAIN SELECT floaty, epoch
FROM general;
QUERY PLAN
---------------------------------------------------------Hash Join
(cost=1615.00..4127.50
rows=50000 width=16)
Hash Cond: (general1.i = general2.i)
->
Seq Scan on general1
(cost=0.00..991.00 rows=50000 width=20)
->
Hash
(cost=819.00..819.00
rows=50000 width=4)
-> Seq Scan on general2
(cost=0.00..819.00 rows=50000 width=4)
Consideraciones finales
Por lo que apreciamos, no hay mucha
mejora sustancial (comparado al particionado
horizontal), sin embargo se usan para casos
muy distintos ambos modelos.
En los dos mtodos, esta tcnica es
transparente a la aplicacin por lo que no es
necesario efectuar modificaciones a las
consultas.

Extendiendo las vistas del particionado con


reglas
Creada la vista tres sobre las tablas una
y dos, queremos que al insertar sobre la vista,
automticamente bifurque los valores sobre las
tablas correspondientes. Para ello podemos
utilizar disparadores, o en su defecto, unos
RULE:
CREATE OR REPLACE RULE R_tres_in
AS ON INSERT
TO tres DO INSTEAD
(INSERT INTO una
VALUES(NEW.i, NEW.floaty) ;
INSERT INTO dos
VALUES(NEW.g,NEW.valor));
CREATE OR REPLACE RULE R_tres_up
AS ON UPDATE
TO tres DO INSTEAD (
UPDATE una
SET i =NEW.i, floaty = NEW.floaty
WHERE i = OLD.i AND floaty =OLD.floaty;
UPDATE dos
SET g =NEW.g , valor = NEW.valor
WHERE g = OLD.g AND valor = OLD.valor;
);
CREATE OR REPLACE RULE R_tres_del
AS ON DELETE
TO tres DO INSTEAD (
DELETE FROM una
WHERE i = OLD.i OR floaty = OLD.floaty;
DELETE FROM dos
WHERE g = OLD.g OR valor = OLD.valor;
);

En el caso preciso del particionado vertical,


el hecho de que la tupla sea ms chica,
permitir que existan mayor cantidad de
Con estas reglas, la aplicacin intentar
registros por bloque.
insertar en la vista tres pensando que es la
tabla correspondiente. Cabe destacar que esta
En el particionado horizontal, el registro no tcnica se utilizara para los particionados
vara de tamao, lo que hace que haya la misma verticales.
cantidad de registros por bloque. Lo que hace la
reduccin de accesos es el orden. Por eso es
La intencin de agregar esto, es que sea
crucial la buena seleccin del campo que totalmente transparente a la aplicacin, de
dividir las particiones.
modo tal de que pueda ser implementado en un
entorno en produccin sin cambios en el
Por eso, si nuestras consultas no respetan aplicativo.
rangos, es posible que el particionado vertical
Vase que las reglas tienen una sentencia
mejore la performance.
por tabla (que insertar, modificar o borrar de
acuerdo a los valores nuevos).

SIU

PGINA 6 DE 9

Consorcio SIU

PARTICIONADO DE TABLAS PARA MEJORA DE PERFORMANCE

Constraint Exclusion
nombre | text
|
Hasta ahora hemos visto el particionado
en s de las tablas. Como hemos visto tambin, contenido | bytea
|
cada particin requiere un CHECK o constraint.
Quizs hasta ahora pareca redundante, pero Indexes:
veremos como agregando esa restriccin se
peude hacer que el planeador mejore y haga
"ix_maestra_cdu_date" btree (cdu_date)
ms inteligente la bsqueda.
Triggers:
La
variable
constraint_exclusion,
permite optimizar las bsquedas en las tablas
t_part_maestra_date_char BEFORE INSERT
heredadas. Puede tener 3 valores en la versin ON maestra FOR EACH ROW EXECUTE
8.4 ('on', 'off','partition').
PROCEDURE part_maestra()
particionado=# select name, setting, context,
extra_desc from pg_settings where name =
'constraint_exclusion';
[RECORD 1 ]
name

| constraint_exclusion

La particin esta basada sobre el


campo date_char el cual contiene 8 caracteres
para la representacion de la fecha en formato
YYYYMMDD.

setting

| partition

En
una
bsqueda
cotidiana
obtendriamos algo parecido a esto con la
variable activada en 'partition':

context

| user

particionado=# show constraint_exclusion;

extra_desc | Table scans will be skipped if their


constraints guarantee that no rows match the
query.

constraint_exclusion
| partition
(1 row)

En el siguiente ejemplo tenemos una


tabla llamada maestra con 33 particiones en
perodos de 1 mes cada una.
explain select * from maestra
substring(cdu_date,0,7) = '200804';
particionado=# \d maestra
Table "public.maestra"
Column |

Type

where

Result (cost=0.00..696.89 rows=27 width=82)


| Modifiers

-> Append
width=82)

(cost=0.00..696.89 rows=27

-----------+-----------------------------+----------date_char | character varying(8)


entero
floaty
fecha
SIU

| integer
| double precision

->
Seq Scan on maestra
(cost=0.00..18.25 rows=3 width=118)
Filter: ("substring"((cdu_date)::text, 0,
7) = '200804'::text)

| timestamp without time zone |

-> Seq Scan on maestra_200804 maestra


(cost=0.00..678.64 rows=24 width=78)
PGINA 7 DE 9

Consorcio SIU
Consorcio SIU

Filter: ("substring"((cdu_date)::text, 0,
Filter: ("substring"((cdu_date)::text, 0,
7) = '200804'::text)
7) = '200804'::text)
(6 rows)

-> Seq Scan on maestra_200708 maestra


(cost=0.00..358.40 rows=13 width=78)

En cambio, desactivada tendremos:


particionado=# set constraint_exclusion = 'off';

Filter: ("substring"((cdu_date)::text, 0,
7) = '200804'::text)
-> Seq Scan on maestra_200807 maestra
(cost=0.00..733.54 rows=26 width=79)

SET
(. y continua)
particionado=# explain select * from maestra
where substring(cdu_date,0,7) = '200804';
Result
width=79)

(cost=0.00..13764.65

Verifiquen los costes uds. mismos los


costes de ambas consultas.

rows=493

-> Append (cost=0.00..13764.65 rows=493


width=79)
->
Seq Scan on maestra
(cost=0.00..18.25 rows=3 width=118)
Filter: ("substring"((cdu_date)::text, 0,
7) = '200804'::text)
-> Seq Scan on maestra_200710 maestra
(cost=0.00..354.94 rows=13 width=79)
Filter: ("substring"((cdu_date)::text, 0,
7) = '200804'::text)
-> Seq Scan on maestra_200806 maestra
(cost=0.00..699.97 rows=25 width=79)
Filter: ("substring"((cdu_date)::text, 0,
7) = '200804'::text)
-> Seq Scan on maestra_200808 maestra
(cost=0.00..725.67 rows=26 width=78)
Filter: ("substring"((cdu_date)::text, 0,
7) = '200804'::text)
-> Seq Scan on maestra_200706 maestra
(cost=0.00..359.44 rows=13 width=78)
Filter: ("substring"((cdu_date)::text, 0,
7) = '200804'::text)
-> Seq Scan on maestra_200811 maestra
(cost=0.00..699.91 rows=25 width=78)
SIU

PGINA 8 DE 9

SIU

PGINA 9 DE 9

Вам также может понравиться