¿Qué es el particionamiento de datos?
Para bases de datos con tablas extremadamente grandes, la partición es un truco maravilloso y astuto para que los diseñadores de bases de datos mejoren el rendimiento de la base de datos y faciliten el mantenimiento. El tamaño máximo de tabla permitido en una base de datos PostgreSQL es de 32 TB; sin embargo, a menos que se ejecute en una computadora del futuro que aún no se ha inventado, pueden surgir problemas de rendimiento en una tabla con solo una centésima parte de ese espacio.
El particionamiento divide una tabla en varias tablas y, por lo general, se realiza de manera que las aplicaciones que acceden a la tabla no noten ninguna diferencia, aparte de ser más rápidas para acceder a los datos que necesita. Al dividir la tabla en varias tablas, la idea es permitir que la ejecución de las consultas tenga que escanear tablas e índices mucho más pequeños para encontrar los datos necesarios. Independientemente de cuán eficiente sea una estrategia de índice, escanear un índice para una tabla de 50 GB siempre será mucho más rápido que un índice para una tabla de 500 GB. Esto también se aplica a los escaneos de tablas, porque a veces los escaneos de tablas son simplemente inevitables.
Al introducir una tabla particionada en el planificador de consultas, hay algunas cosas que debe saber y comprender sobre el planificador de consultas en sí. Antes de que se ejecute cualquier consulta, el planificador de consultas tomará la consulta y planificará la forma más eficiente de acceder a los datos. Al dividir los datos en diferentes tablas, el planificador puede decidir a qué tablas acceder y qué tablas ignorar por completo, según el contenido de cada tabla.
Esto se hace agregando restricciones a las tablas divididas que definen qué datos se permiten en cada tabla y, con un buen diseño, podemos hacer que el planificador de consultas escanee un pequeño subconjunto de datos en lugar de todo.
¿Debe dividirse una tabla?
La creación de particiones puede mejorar drásticamente el rendimiento en una tabla cuando se hace correctamente, pero si se hace mal o cuando no se necesita, puede empeorar el rendimiento, incluso dejarlo inutilizable.
¿Qué tamaño tiene la mesa?
No existe una regla estricta real sobre el tamaño de una tabla antes de que la partición sea una opción, pero según las tendencias de acceso a la base de datos, los usuarios y administradores de la base de datos comenzarán a ver que el rendimiento en una tabla específica comienza a degradarse a medida que crece. En general, la partición solo debe considerarse cuando alguien dice "No puedo hacer X porque la tabla es demasiado grande". Para algunos hosts, 200 GB podría ser el momento adecuado para particionar, para otros, puede ser el momento de particionar cuando llegue a 1 TB.
Si se determina que la tabla es "demasiado grande", es hora de mirar los patrones de acceso. Ya sea al conocer las aplicaciones que acceden a la base de datos o al monitorear los registros y generar informes de consulta con algo como pgBadger, podemos ver cómo se accede a una tabla y, dependiendo de cómo se acceda, podemos tener opciones para una buena estrategia de partición.
Para obtener más información sobre pgBadger y cómo usarlo, consulte nuestro artículo anterior sobre pgBadger.
¿Es el exceso de tablas un problema?
Las filas actualizadas y eliminadas dan como resultado tuplas muertas que, en última instancia, deben limpiarse. Al aspirar tablas, ya sea de forma manual o automática, se revisa cada fila de la tabla y se determina si se debe recuperar o dejar sola. Cuanto más grande sea la tabla, más tardará este proceso y se utilizarán más recursos del sistema. Incluso si el 90% de una tabla son datos que no cambian, debe escanearse cada vez que se ejecuta un vacío. Particionar la tabla puede ayudar a reducir la tabla que necesita limpieza a otras más pequeñas, lo que reduce la cantidad de datos que no cambian que necesitan escanearse, menos tiempo de limpieza en general y más recursos del sistema liberados para el acceso de los usuarios en lugar del mantenimiento del sistema.
¿Cómo se eliminan los datos, si es que se eliminan?
Si los datos se eliminan según un cronograma, digamos que los datos de más de 4 años se eliminan y archivan, esto podría resultar en declaraciones de eliminación de gran impacto que pueden demorar en ejecutarse y, como se mencionó anteriormente, crear filas muertas que deben vaciarse. Si se implementa una buena estrategia de partición, una declaración DELETE de varias horas con mantenimiento de vacío posterior podría convertirse en una declaración DROP TABLE de un minuto en una tabla mensual antigua sin mantenimiento de vacío.
¿Cómo se debe particionar la tabla?
Las claves para los patrones de acceso están en la cláusula WHERE y las condiciones JOIN. Cada vez que una consulta especifica columnas en las cláusulas WHERE y JOIN, le dice a la base de datos "estos son los datos que quiero". Al igual que el diseño de índices que apuntan a estas cláusulas, las estrategias de partición se basan en apuntar a estas columnas para separar los datos y hacer que la consulta acceda a la menor cantidad de particiones posible.
Ejemplos:
- Una tabla de transacciones, con una columna de fecha que siempre se usa en una cláusula where.
- Una tabla de clientes con columnas de ubicación, como el país de residencia que siempre se usa en las cláusulas where.
Las columnas más comunes en las que centrarse para la partición suelen ser las marcas de tiempo, ya que normalmente una gran parte de los datos es información histórica y probablemente tendrá una distribución de datos bastante predecible en diferentes grupos de tiempo.
Determinar la distribución de datos
Una vez que identifiquemos en qué columnas particionar, deberíamos echar un vistazo a la distribución de datos, con el objetivo de crear tamaños de partición que distribuyan los datos de la manera más uniforme posible entre las diferentes particiones secundarias.
severalnines=# SELECT DATE_TRUNC('year', view_date)::DATE, COUNT(*) FROM website_views GROUP BY 1 ORDER BY 1;
date_trunc | count
------------+----------
2013-01-01 | 11625147
2014-01-01 | 20819125
2015-01-01 | 20277739
2016-01-01 | 20584545
2017-01-01 | 20777354
2018-01-01 | 491002
(6 rows)
En este ejemplo, truncamos la columna de marca de tiempo en una tabla anual, lo que da como resultado unas 20 millones de filas por año. Si todas nuestras consultas especifican una(s) fecha(s) o un(os) rango(s) de fechas, y las especificadas generalmente cubren datos dentro de un solo año, esta puede ser una excelente estrategia inicial para la partición, ya que daría como resultado una sola tabla por año. , con un número manejable de filas por tabla.
Descargue el documento técnico hoy Administración y automatización de PostgreSQL con ClusterControlObtenga información sobre lo que necesita saber para implementar, monitorear, administrar y escalar PostgreSQLDescargar el documento técnicoCrear una tabla con particiones
Hay un par de formas de crear tablas particionadas, sin embargo, nos centraremos principalmente en el tipo más rico en funciones disponible, el particionamiento basado en activadores. Esto requiere una configuración manual y un poco de codificación en el lenguaje de procedimiento plpgsql para que funcione.
Funciona al tener una tabla principal que finalmente quedará vacía (o permanecerá vacía si es una tabla nueva) y tablas secundarias que HEREDAN la tabla principal. Cuando se consulta la tabla principal, también se buscan datos en las tablas secundarias debido a que INHERIT se aplica a las tablas secundarias. Sin embargo, dado que las tablas secundarias solo contienen subconjuntos de los datos de los principales, agregamos una RESTRICCIÓN en la tabla que hace una COMPROBACIÓN y verifica que los datos coincidan con lo permitido en la tabla. Esto hace dos cosas:primero, rechaza los datos que no pertenecen y, segundo, le dice al planificador de consultas que solo se permiten datos que coincidan con esta RESTRICCIÓN DE VERIFICACIÓN en esta tabla, por lo que si busca datos que no coinciden con la tabla, no lo haga. Ni siquiera te molestes en buscarlo.
Por último, aplicamos un activador a la tabla principal que ejecuta un procedimiento almacenado que decide en qué tabla secundaria colocar los datos.
Crear tabla
La creación de la tabla principal es como la creación de cualquier otra tabla.
severalnines=# CREATE TABLE data_log (data_log_sid SERIAL PRIMARY KEY,
date TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
event_details VARCHAR);
CREATE TABLE
Crear tablas secundarias
La creación de las tablas secundarias es similar, pero implica algunas adiciones. Por el bien de la organización, haremos que nuestras tablas secundarias existan en un esquema separado. Haga esto para cada tabla secundaria, cambiando los detalles en consecuencia.
NOTA:El nombre de la secuencia utilizada en nextval() proviene de la secuencia que creó el padre. Esto es crucial para que todas las tablas secundarias utilicen la misma secuencia.
severalnines=# CREATE SCHEMA part;
CREATE SCHEMA
severalnines=# CREATE TABLE part.data_log_2018 (data_log_sid integer DEFAULT nextval('public.data_log_data_log_sid_seq'::regclass),
date TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
event_details VARCHAR)
INHERITS (public.data_log);
CREATE TABLE
severalnines=# ALTER TABLE ONLY part.data_log_2018
ADD CONSTRAINT data_log_2018_pkey PRIMARY KEY (data_log_sid);
ALTER TABLE
severalnines=# ALTER TABLE part.data_log_2018 ADD CONSTRAINT data_log_2018_date CHECK (date >= '2018-01-01' AND date < '2019-01-01');
ALTER TABLE
Crear función y disparador
Finalmente, creamos nuestro procedimiento almacenado y agregamos el activador a nuestra tabla principal.
severalnines=# CREATE OR REPLACE FUNCTION
public.insert_trigger_table()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
IF NEW.date >= '2018-01-01' AND NEW.date < '2019-01-01' THEN
INSERT INTO part.data_log_2018 VALUES (NEW.*);
RETURN NULL;
ELSIF NEW.date >= '2019-01-01' AND NEW.date < '2020-01-01' THEN
INSERT INTO part.data_log_2019 VALUES (NEW.*);
RETURN NULL;
END IF;
END;
$function$;
CREATE FUNCTION
severalnines=# CREATE TRIGGER insert_trigger BEFORE INSERT ON data_log FOR EACH ROW EXECUTE PROCEDURE insert_trigger_table();
CREATE TRIGGER
Pruébelo
Ahora que todo está creado, vamos a probarlo. En esta prueba, agregué más tablas anuales que abarcan de 2013 a 2020.
Nota:la respuesta de inserción a continuación es "INSERTAR 0 0", lo que sugeriría que no insertó nada. Esto se abordará más adelante en este artículo.
severalnines=# INSERT INTO data_log (date, event_details) VALUES ('2018-08-20 15:22:14', 'First insert');
INSERT 0 0
severalnines=# SELECT * FROM data_log WHERE date >= '2018-08-01' AND date < '2018-09-01';
data_log_sid | date | event_details
--------------+----------------------------+---------------
1 | 2018-08-17 23:01:38.324056 | First insert
(1 row)
Existe, pero veamos el planificador de consultas para asegurarnos de que la fila provenga de la tabla secundaria correcta y que la tabla principal no devuelva ninguna fila.
severalnines=# EXPLAIN ANALYZE SELECT * FROM data_log;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------
Append (cost=0.00..130.12 rows=5813 width=44) (actual time=0.016..0.019 rows=1 loops=1)
-> Seq Scan on data_log (cost=0.00..1.00 rows=1 width=44) (actual time=0.007..0.007 rows=0 loops=1)
-> Seq Scan on data_log_2015 (cost=0.00..21.30 rows=1130 width=44) (actual time=0.001..0.001 rows=0 loops=1)
-> Seq Scan on data_log_2013 (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
-> Seq Scan on data_log_2014 (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
-> Seq Scan on data_log_2016 (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
-> Seq Scan on data_log_2017 (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
-> Seq Scan on data_log_2018 (cost=0.00..1.02 rows=2 width=44) (actual time=0.005..0.005 rows=1 loops=1)
-> Seq Scan on data_log_2019 (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
-> Seq Scan on data_log_2020 (cost=0.00..17.80 rows=780 width=44) (actual time=0.001..0.001 rows=0 loops=1)
Planning time: 0.373 ms
Execution time: 0.069 ms
(12 rows)
Buenas noticias, la única fila que insertamos aterrizó en la tabla de 2018, donde pertenece. Pero como podemos ver, la consulta no especifica una cláusula where usando la columna de fecha, por lo que para obtener todo, el planificador de consultas y la ejecución realizaron un escaneo secuencial en cada tabla.
A continuación, probemos usando una cláusula where.
severalnines=# EXPLAIN ANALYZE SELECT * FROM data_log WHERE date >= '2018-08-01' AND date < '2018-09-01';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
Append (cost=0.00..2.03 rows=2 width=44) (actual time=0.013..0.014 rows=1 loops=1)
-> Seq Scan on data_log (cost=0.00..1.00 rows=1 width=44) (actual time=0.007..0.007 rows=0 loops=1)
Filter: ((date >= '2018-08-01 00:00:00'::timestamp without time zone) AND (date < '2018-09-01 00:00:00'::timestamp without time zone))
-> Seq Scan on data_log_2018 (cost=0.00..1.03 rows=1 width=44) (actual time=0.006..0.006 rows=1 loops=1)
Filter: ((date >= '2018-08-01 00:00:00'::timestamp without time zone) AND (date < '2018-09-01 00:00:00'::timestamp without time zone))
Planning time: 0.591 ms
Execution time: 0.041 ms
(7 rows)
Aquí podemos ver que el planificador de consultas y la ejecución realizaron un escaneo secuencial en dos tablas, la tabla principal y la secundaria para 2018. Hay tablas secundarias para los años 2013 - 2020, pero nunca se accedió a las que no sean 2018 porque la cláusula where tiene un rango que pertenece solo a 2018. El planificador de consultas descartó todas las demás tablas porque CHECK CONSTRAINT considera imposible que los datos existan en esas tablas.
Particiones de trabajo con herramientas ORM estrictas o validación de fila insertada
Como se mencionó anteriormente, el ejemplo que construimos devuelve un 'INSERTAR 0 0' aunque insertamos una fila. Si las aplicaciones que insertan datos en estas tablas particionadas confían en verificar que las filas insertadas sean correctas, fallarán. Hay una solución, pero agrega otra capa de complejidad a la tabla particionada, por lo que puede ignorarse si este escenario no es un problema para las aplicaciones que usan la tabla particionada.
Usar una vista en lugar de la tabla principal.
La solución para este problema es crear una vista que consulte la tabla principal y dirija las declaraciones INSERT a la vista. Insertar en una vista puede parecer una locura, pero ahí es donde entra en juego el activador de la vista.
severalnines=# CREATE VIEW data_log_view AS
SELECT data_log.data_log_sid,
data_log.date,
data_log.event_details
FROM data_log;
CREATE VIEW
severalnines=# ALTER VIEW data_log_view ALTER COLUMN data_log_sid SET default nextval('data_log_data_log_sid_seq'::regclass);
ALTER VIEW
Consultar esta vista se parecerá a consultar la tabla principal, y las cláusulas WHERE y JOINS funcionarán como se esperaba.
Ver función específica y disparador
En lugar de usar la función y el disparador que definimos antes, ambos serán ligeramente diferentes. Cambios en negrita.
CREATE OR REPLACE FUNCTION public.insert_trigger_view()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
IF NEW.date >= '2018-01-01' AND NEW.date < '2019-01-01' THEN
INSERT INTO part.data_log_2018 VALUES (NEW.*);
RETURN NEW;
ELSIF NEW.date >= '2019-01-01' AND NEW.date < '2020-01-01' THEN
INSERT INTO part.data_log_2019 VALUES (NEW.*);
RETURN NEW;
END IF;
END;
$function$;
severalnines=# CREATE TRIGGER insert_trigger INSTEAD OF INSERT ON data_log_view FOR EACH ROW EXECUTE PROCEDURE insert_trigger_view();
La definición "INSTEAD OF" se hace cargo del comando de inserción en la vista (que de todos modos no funcionaría) y ejecuta la función en su lugar. La función que definimos tiene un requisito muy específico de hacer un 'VOLVER NUEVO' después de que se complete la inserción en las tablas secundarias. Sin esto (o hacerlo como lo hicimos antes con 'RETURN NULL') dará como resultado 'INSERT 0 0' en lugar de 'INSERT 0 1' como esperaríamos.
Ejemplo:
severalnines=# INSERT INTO data_log_view (date, event_details) VALUES ('2018-08-20 18:12:48', 'First insert on the view');
INSERT 0 1
severalnines=# EXPLAIN ANALYZE SELECT * FROM data_log_view WHERE date >= '2018-08-01' AND date < '2018-09-01';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
Append (cost=0.00..2.03 rows=2 width=44) (actual time=0.015..0.017 rows=2 loops=1)
-> Seq Scan on data_log (cost=0.00..1.00 rows=1 width=44) (actual time=0.009..0.009 rows=0 loops=1)
Filter: ((date >= '2018-08-01 00:00:00'::timestamp without time zone) AND (date < '2018-09-01 00:00:00'::timestamp without time zone))
-> Seq Scan on data_log_2018 (cost=0.00..1.03 rows=1 width=44) (actual time=0.006..0.007 rows=2 loops=1)
Filter: ((date >= '2018-08-01 00:00:00'::timestamp without time zone) AND (date < '2018-09-01 00:00:00'::timestamp without time zone))
Planning time: 0.633 ms
Execution time: 0.048 ms
(7 rows)
severalnines=# SELECT * FROM data_log_view WHERE date >= '2018-08-01' AND date < '2018-09-01';
data_log_sid | date | event_details
--------------+---------------------+--------------------------
1 | 2018-08-20 15:22:14 | First insert
2 | 2018-08-20 18:12:48 | First insert on the view
(2 rows)
Las aplicaciones que prueben que el 'recuento de filas' insertado sea correcto encontrarán que esta solución funciona como se esperaba. En este ejemplo, agregamos _view a nuestra vista y procedimiento almacenado, pero si se desea dividir la tabla sin que ningún usuario sepa/cambie la aplicación, cambiaríamos el nombre de la tabla principal a data_log_parent y llamaríamos a la vista por el antiguo nombre de la tabla principal.
Actualizar una fila y cambiar el valor de la columna particionada
Una cosa a tener en cuenta es que si realiza una actualización de los datos en la tabla particionada y cambia el valor de la columna a algo no permitido por la restricción, se producirá un error. Si este tipo de actualización nunca sucederá, puede ignorarse, pero si es una posibilidad, se debe escribir un nuevo activador para los procesos de ACTUALIZACIÓN que eliminará efectivamente la fila de la partición secundaria anterior e insertará una nueva en la partición secundaria. nueva partición secundaria de destino.
Creación de futuras particiones
La creación de futuras particiones se puede hacer de diferentes maneras, cada una con sus pros y sus contras.
Futuro creador de particiones
Se puede escribir un programa externo para crear futuras particiones X tiempo antes de que se necesiten. En un ejemplo de particionamiento particionado en una fecha, la próxima partición necesaria para crear (en nuestro caso, 2019) podría configurarse para crearse en algún momento de diciembre. Puede ser un script manual ejecutado por el administrador de la base de datos, o configurado para que cron lo ejecute cuando sea necesario. Las particiones anuales significarían que se ejecuta una vez al año, sin embargo, las particiones diarias son comunes y un trabajo cron diario hace que un DBA sea más feliz.
Creador automático de particiones
Con el poder de plpgsql, podemos capturar errores si intentamos insertar datos en una partición secundaria que no existe, y sobre la marcha crear la partición necesaria, luego intentar insertar nuevamente. Esta opción funciona bien, excepto en el caso de que muchos clientes diferentes inserten datos similares al mismo tiempo, lo que podría provocar una condición de carrera en la que un cliente crea la tabla, mientras que otro intenta crear la misma tabla y obtiene un error de que ya existe. La programación plpgsql inteligente y avanzada puede solucionar esto, pero si vale la pena o no el nivel de esfuerzo está en debate. Si esta condición de carrera no ocurre debido a los patrones de inserción, entonces no hay nada de qué preocuparse.
Eliminación de particiones
Si las reglas de retención de datos dictan que los datos se eliminen después de una cierta cantidad de tiempo, esto se vuelve más fácil con tablas particionadas si se particionan en una columna de fecha. Si vamos a eliminar datos que tienen 10 años, podría ser tan simple como:
severalnines=# DROP TABLE part.data_log_2007;
DROP TABLE
Esto es mucho más rápido y más eficiente que una instrucción 'ELIMINAR', ya que no genera tuplas muertas para limpiar con una aspiradora.
Nota:si elimina tablas de la configuración de la partición, el código en las funciones de activación también debe modificarse para no dirigir la fecha a la tabla eliminada.
Cosas que debe saber antes de particionar
Las tablas de partición pueden ofrecer una mejora drástica en el rendimiento, pero también pueden empeorarlo. Antes de pasar a los servidores de producción, la estrategia de particionamiento debe probarse exhaustivamente, en cuanto a la coherencia de los datos, la velocidad de rendimiento, todo. Particionar una mesa tiene algunas partes móviles, todas deben probarse para asegurarse de que no haya problemas.
Cuando se trata de decidir el número de particiones, se recomienda encarecidamente mantener el número de tablas secundarias por debajo de 1000 tablas, e incluso más bajo si es posible. Una vez que el recuento de tablas secundarias supera los ~1000, el rendimiento comienza a disminuir, ya que el planificador de consultas termina tardando mucho más en hacer el plan de consulta. No es inaudito que un plan de consulta tome muchos segundos, mientras que la ejecución real solo toma unos pocos milisegundos. Si atiende miles de consultas por minuto, varios segundos podrían paralizar las aplicaciones.
Los procedimientos almacenados del disparador plpgsql también pueden complicarse y, si son demasiado complicados, también pueden ralentizar el rendimiento. El procedimiento almacenado se ejecuta una vez por cada fila insertada en la tabla. Si termina haciendo demasiado procesamiento para cada fila, las inserciones podrían volverse demasiado lentas. Las pruebas de rendimiento se asegurarán de que todavía esté en un rango aceptable.
Sea creativo
Las tablas de partición en PostgreSQL pueden ser tan avanzadas como sea necesario. En lugar de columnas de fecha, las tablas se pueden dividir en una columna de "país", con una tabla para cada país. La partición se puede realizar en varias columnas, como una columna de "fecha" y "país". Esto hará que el procedimiento almacenado que maneja las inserciones sea más complejo, pero es 100 % posible.
Recuerde, los objetivos de la partición son dividir tablas extremadamente grandes en otras más pequeñas y hacerlo de una manera bien pensada para permitir que el planificador de consultas acceda a los datos más rápido de lo que podría haberlo hecho en la tabla original más grande.
Particionamiento declarativo
En PostgreSQL 10 y versiones posteriores, se introdujo una nueva función de particionamiento, el 'particionamiento declarativo'. Es una forma más fácil de configurar particiones, sin embargo, tiene algunas limitaciones. Si las limitaciones son aceptables, probablemente funcionará más rápido que la configuración manual de particiones, pero una gran cantidad de pruebas lo verificarán.
La documentación oficial de postgresql tiene información sobre el particionamiento declarativo y cómo funciona. Es nuevo en PostgreSQL 10, y con la versión 11 de PostgreSQL en el horizonte en el momento de escribir este artículo, algunas de las limitaciones están solucionadas, pero no todas. A medida que evoluciona PostgreSQL, el particionamiento declarativo puede convertirse en un reemplazo completo para el particionamiento más complejo que se trata en este artículo. Hasta entonces, el particionamiento declarativo puede ser una alternativa más fácil si ninguna de las limitaciones limita las necesidades de particionamiento.
Limitaciones del particionamiento declarativo
La documentación de PostgreSQL aborda todas las limitaciones con este tipo de particionamiento en PostgreSQL 10, pero se puede encontrar una excelente descripción general en The Official PostgreSQL Wiki que enumera las limitaciones en un formato más fácil de leer, además de señalar cuáles se han corregido en el próximo PostgreSQL 11.
Pregunte a la comunidad
Los administradores de bases de datos de todo el mundo han estado diseñando estrategias de partición avanzadas y personalizadas durante mucho tiempo, y muchos de nosotros pasamos el rato en IRC y listas de correo. Si se necesita ayuda para decidir la mejor estrategia, o simplemente para resolver un error en un procedimiento almacenado, la comunidad está aquí para ayudar.
- IRC
Freenode tiene un canal muy activo llamado #postgres, donde los usuarios se ayudan mutuamente a comprender conceptos, corregir errores o encontrar otros recursos. - Listas de correo
PostgreSQL tiene un puñado de listas de correo a las que se puede unir. Aquí se pueden enviar preguntas/problemas de formato más largo, y pueden llegar a muchas más personas que IRC en un momento dado. Las listas se pueden encontrar en el sitio web de PostgreSQL, y las listas pgsql-general o pgsql-admin son buenos recursos.