Какво е разделяне на данни?
За бази данни с изключително големи таблици, разделянето е прекрасен и хитър трик за дизайнерите на бази данни, за да подобрят производителността на базата данни и да направят поддръжката много по-лесна. Максималният размер на таблицата, разрешен в база данни на PostgreSQL, е 32TB, но освен ако не работи на все още неизмислен компютър от бъдещето, може да възникнат проблеми с производителността на таблица със само една стотна от това пространство.
Разделянето разделя таблица на множество таблици и обикновено се извършва по начин, по който приложенията, които имат достъп до таблицата, не забелязват никаква разлика, освен че са по-бързи за достъп до данните, от които се нуждае. Чрез разделянето на таблицата на множество таблици идеята е да позволи изпълнението на заявките да сканира много по-малки таблици и индекси, за да намери необходимите данни. Независимо от това колко ефективна е една индексна стратегия, сканирането на индекс за таблица с 50GB винаги ще бъде много по-бързо от индекс, който е за таблица с 500GB. Това важи и за сканирането на таблици, защото понякога сканирането на таблици е просто неизбежно.
Когато въвеждате разделена таблица в планировчика на заявки, трябва да знаете и разберете няколко неща за самия плановик на заявки. Преди каквато и да е заявка действително да бъде изпълнена, плановникът на заявки ще вземе заявката и ще планира най-ефективния начин, по който ще има достъп до данните. Чрез разделяне на данните в различни таблици, плановникът може да реши до кои таблици да има достъп и кои да игнорира напълно, въз основа на съдържанието на всяка таблица.
Това става чрез добавяне на ограничения към разделените таблици, които определят какви данни са разрешени във всяка таблица, и с добър дизайн можем да накараме планировщика на заявки да сканира малка част от данни, а не цялото нещо.
Трябва ли една таблица да бъде разделена?
Разделянето може драстично да подобри производителността на таблица, когато е направено правилно, но ако е направено неправилно или когато не е необходимо, може да влоши производителността, дори да стане неизползваема.
Колко голяма е масата?
Няма истинско твърдо правило за това колко голяма трябва да бъде една таблица, преди разделянето да стане опция, но въз основа на тенденциите в достъпа до база данни, потребителите и администраторите на базата данни ще започнат да виждат, че производителността на конкретна таблица започва да се влошава, когато стане по-голяма. Като цяло разделянето трябва да се има предвид само когато някой каже „Не мога да направя X, защото масата е твърде голяма“. За някои хостове 200 GB може да са подходящият момент за разделяне, за други може да е време за разделяне, когато достигне 1TB.
Ако е определено, че таблицата е „твърде голяма“, време е да разгледаме моделите за достъп. Или като познаваме приложенията, които осъществяват достъп до базата данни, или като наблюдаваме регистрационните файлове и генерираме отчети за заявки с нещо като pgBadger, можем да видим как се осъществява достъп до таблица и в зависимост от това как се осъществява достъп до нея, можем да имаме опции за добра стратегия за разделяне.
За да научите повече за pgBadger и как да го използвате, моля, вижте предишната ни статия за pgBadger.
Проблем ли е раздуването на масата?
Актуализираните и изтрити редове водят до мъртви кортежи, които в крайна сметка трябва да бъдат почистени. Масите за прахосмукачка, ръчно или автоматично, преминават през всеки ред в таблицата и определя дали трябва да бъдат възстановени или оставени сами. Колкото по-голяма е таблицата, толкова по-дълго отнема този процес и толкова повече системни ресурси се използват. Дори ако 90% от таблицата са непроменени данни, тя трябва да се сканира всеки път, когато се изпълнява вакуум. Разделянето на таблицата може да помогне за намаляване на таблицата, която се нуждае от вакуумиране до по-малки, намаляване на количеството непроменени данни, които трябва да бъдат сканирани, по-малко време за вакуумиране като цяло и повече системни ресурси, освободени за достъп на потребителите, а не за поддръжка на системата.
Как се изтриват данните, ако изобщо?
Ако данните се изтриват по график, да речем, че данните по-стари от 4 години се изтриват и архивират, това може да доведе до тежки удари на оператори за изтриване, чието изпълнение може да отнеме време и както споменахме по-горе, създаване на мъртви редове, които трябва да бъдат изчистени. Ако се приложи добра стратегия за разделяне, многочасов оператор DELETE с поддръжка на вакуумиране след това може да бъде превърнат в едноминутен оператор DROP TABLE на стара месечна таблица с нулева поддръжка на вакуум.
Как трябва да бъде разделена таблицата?
Ключовете за модели за достъп са в клаузата WHERE и условията JOIN. Всеки път, когато заявка посочва колони в клаузите WHERE и JOIN, тя казва на базата данни „това са данните, които искам“. Подобно на проектиране на индекси, които са насочени към тези клаузи, стратегиите за разделяне разчитат на насочване на тези колони за разделяне на данни и достъп на заявката до възможно най-малко дялове.
Примери:
- Таблица на транзакциите с колона за дата, която винаги се използва в клауза where.
- Таблица с клиенти с колони за местоположение, като например държава на пребиваване, която винаги се използва в клаузите where.
Най-често срещаните колони, върху които трябва да се съсредоточите за разделяне, обикновено са времеви печати, тъй като обикновено голяма част от данни е историческа информация и вероятно ще има доста предсказуеми данни, разпределени в различни времеви групировки.
Определете разпространението на данни
След като идентифицираме кои колони да разделим, трябва да разгледаме разпространението на данни, с цел да създадем размери на дялове, които да разпределят данните възможно най-равномерно в различните дъщерни дялове.
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)
В този пример съкращаваме колоната с времеви печат до годишна таблица, което води до около 20 милиона реда годишно. Ако всички наши заявки посочват дата(и) или период(и) и посочените обикновено обхващат данни в рамките на една година, това може да е чудесна начална стратегия за разделяне, тъй като би довело до една таблица на година , с управляем брой редове на таблица.
Изтеглете Бялата книга днес Управление и автоматизация на PostgreSQL с ClusterControl Научете какво трябва да знаете, за да внедрите, наблюдавате, управлявате и мащабирате PostgreSQLD Изтеглете Бялата книгаСъздаване на разделена таблица
Има няколко начина за създаване на разделени таблици, но ние ще се съсредоточим главно върху най-богатите на функции тип на разположение, разделяне на базата на тригери. Това изисква ръчна настройка и малко кодиране на процедурния език plpgsql, за да работи.
Той работи, като има родителска таблица, която в крайна сметка ще стане празна (или ще остане празна, ако е нова), и дъщерни таблици, които НАСЛЕДЯВАТ родителската таблица. Когато родителската таблица е заявена, дъщерните таблици също се търсят за данни поради INHERIT, приложен към дъщерните таблици. Въпреки това, тъй като дъщерните таблици съдържат само подмножества от данните на родителя, ние добавяме ОГРАНИЧЕНИЕ към таблицата, което прави ПРОВЕРКА и проверява дали данните съответстват на разрешеното в таблицата. Това прави две неща:Първо отказва данни, които не принадлежат, и второ, казва на планировщика на заявки, че в тази таблица са разрешени само данни, съответстващи на това ПРОВЕРКА НА ОГРАНИЧЕНИЕТО, така че ако търсите данни, които не съвпадат с таблицата, не дори не си прави труда да го търся.
И накрая, ние прилагаме тригер към родителската таблица, която изпълнява съхранена процедура, която решава коя дъщерна таблица да постави данните.
Създаване на таблица
Създаването на родителската таблица е като всяко друго създаване на таблица.
severalnines=# CREATE TABLE data_log (data_log_sid SERIAL PRIMARY KEY,
date TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
event_details VARCHAR);
CREATE TABLE
Създаване на дъщерни таблици
Създаването на дъщерните таблици е подобно, но включва някои допълнения. От съображения за организация ще имаме дъщерните ни таблици да съществуват в отделна схема. Направете това за всяка дъщерна таблица, като съответно промените детайлите.
ЗАБЕЛЕЖКА:Името на последователността, използвана в nextval(), идва от последователността, създадена от родителя. Това е от решаващо значение за всички дъщерни таблици да използват една и съща последователност.
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
Създаване на функция и тригер
Накрая създаваме нашата съхранена процедура и добавяме тригера към нашата родителска таблица.
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
Изпробвайте го
Сега, когато всичко е създадено, нека го тестваме. В този тест добавих още годишни таблици, обхващащи 2013 - 2020 г.
Забележка:Отговорът за вмъкване по-долу е „INSERT 0 0“, което предполага, че не е вмъкнал нищо. Това ще бъде разгледано по-късно в тази статия.
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)
Съществува, но нека погледнем планировщика на заявки, за да се уверим, че редът идва от правилната дъщерна таблица и родителската таблица изобщо не е върнала редове.
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)
Добри новини, единичният ред, който вмъкнахме, попадна в таблицата за 2018 г., където му е мястото. Но както виждаме, заявката не посочва клауза where, използвайки колоната за дата, така че за да извлече всичко, плановникът и изпълнението на заявки направиха последователно сканиране на всяка една таблица.
След това нека тестваме с помощта на клауза 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)
Тук можем да видим, че планирането на заявки и изпълнението са направили последователно сканиране на две таблици, родителската и дъщерната таблица за 2018 г. Има дъщерни таблици за годините 2013 - 2020, но тези, различни от 2018, никога не са били достъпни, защото клаузата where има диапазон, принадлежащ само към 2018 г. Инструментът за планиране на заявки изключи всички останали таблици, тъй като CHECK CONSTRAINT счита, че е невъзможно данните да съществуват в тези таблици.
Работни дялове със строги ORM инструменти или валидиране на вмъкнат ред
Както споменахме по-горе, примерът, който изградихме, връща „INSERT 0 0“, въпреки че сме вмъкнали ред. Ако приложенията, които вмъкват данни в тези разделени таблици, разчитат на проверка дали вмъкнатите редове са правилни, те ще се провалят. Има поправка, но добавя още един слой сложност към разделената таблица, така че може да бъде игнориран, ако този сценарий не е проблем за приложенията, използващи разделената таблица.
Използване на изглед вместо родителската таблица.
Поправката за този проблем е да се създаде изглед, който отправя заявки към родителската таблица и да насочва изрази INSERT към изгледа. Вмъкването в изглед може да звучи налудничаво, но точно тук идва задействането на изгледа.
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
Запитването на този изглед ще изглежда точно като запитване към основната таблица, а клаузите WHERE, както и JOINS ще работят според очакванията.
Преглед на специфична функция и задействане
Вместо да използвате функцията и тригера, които дефинирахме преди, и двете ще бъдат малко по-различни. Промените са с удебелен шрифт.
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();
Дефиницията „INSTEAD OF“ поема командата за вмъкване в изгледа (която така или иначе няма да работи) и вместо това изпълнява функцията. Функцията, която дефинирахме, има много конкретно изискване за извършване на „ВРЪЩАНЕ НОВО;“, след като вмъкването в дъщерните таблици приключи. Без това (или да го направим както преди с „RETURN NULL“) ще доведе до „INSERT 0 0“ вместо „INSERT 0 1“, както бихме очаквали.
Пример:
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)
Тестването на приложения за правилен вмъкнат „брой редове“ ще установи, че тази корекция работи според очакванията. В този пример добавихме _view към нашия изглед и съхранена процедура, но ако е необходимо таблицата да бъде разделена без никакви потребители да знаят / промяна на приложението, тогава ще преименуваме родителската таблица на data_log_parent и ще извикаме изгледа от стария името на родителската таблица.
Актуализиране на ред и промяна на стойността на разделената колона
Едно нещо, което трябва да имате предвид, е, че ако се извърши актуализация на данните в разделената таблица и промяната на стойността на колоната на нещо, което не е позволено от ограничението, ще доведе до грешка. Ако този тип актуализация никога няма да се случи, тогава тя може да бъде игнорирана, но ако е възможно, трябва да се напише нов тригер за процеси UPDATE, който ефективно ще изтрие реда от стария дъщерен дял и ще вмъкне нов в нов целеви дъщерен дял.
Създаване на бъдещи дялове
Създаването на бъдещи дялове може да се извърши по няколко различни начина, всеки със своите плюсове и минуси.
Бъдещ създател на дялове
Външна програма може да бъде написана за създаване на бъдещи дялове X време, преди те да са необходими. В пример за разделяне, разделен на дата, следващият необходим дял за създаване (в нашия случай 2019 г.) може да бъде настроен да бъде създаден някъде през декември. Това може да бъде ръчен скрипт, изпълняван от администратора на базата данни, или настроен да го стартира cron, когато е необходимо. Годишните дялове биха означавали, че се изпълнява веднъж годишно, но ежедневните дялове са често срещани, а ежедневната работа на cron прави по-щастлив DBA.
Автоматичен създател на дялове
Със силата на plpgsql можем да улавяме грешки, ако се опитваме да вмъкнем данни в дъщерен дял, който не съществува, и в движение да създадем необходимия дял, след което да опитаме да вмъкнем отново. Тази опция работи добре, освен в случаите, когато много различни клиенти, които вмъкват подобни данни по едно и също време, могат да причинят състояние на състезание, при което един клиент създава таблицата, докато друг се опитва да създаде същата таблица и получава грешка за вече съществуваща. Умното и усъвършенствано програмиране на plpgsql може да поправи това, но дали си струва нивото на усилията или не, е предмет на дебат. Ако това състояние на състезанието не се случи поради шаблоните на вмъкване, тогава няма за какво да се притеснявате.
Отпадане на дялове
Ако правилата за запазване на данни диктуват, че данните се изтриват след определен период от време, това става по-лесно с разделените таблици, ако са разделени по колона с дата. Ако искаме да изтрием данни, които са на 10 години, това може да бъде толкова просто:
severalnines=# DROP TABLE part.data_log_2007;
DROP TABLE
Това е много по-бързо и по-ефективно от израза „DELETE“, тъй като не води до мъртви кортежи за почистване с вакуум.
Забележка:Ако премахвате таблици от настройката на дяла, кодът в тригерните функции също трябва да бъде променен, за да не насочва датата към изпуснатата таблица.
Неща, които трябва да знаете, преди да разделите
Таблиците за разделяне могат да предложат драстично подобрение на производителността, но също така могат да я влошат. Преди да се насочи към производствени сървъри, стратегията за разделяне трябва да бъде тествана обстойно за последователност на данните, скорост на производителност, всичко. Разделянето на таблица има няколко движещи се части, всички те трябва да бъдат тествани, за да се уверите, че няма проблеми.
Когато става въпрос за определяне на броя на дяловете, силно се препоръчва броят на дъщерните таблици да остане под 1000 таблици и дори по-нисък, ако е възможно. След като броят на дъщерните таблици надхвърли ~1000, производителността започва да намалява, тъй като самият плановик на заявки отнема много повече време, само за да направи плана на заявката. Не е нечувано планът на заявката да отнема много секунди, докато действителното изпълнение отнема само няколко милисекунди. Ако се обслужват хиляди заявки в минута, няколко секунди биха могли да доведат до спиране на приложенията.
Съхранените процедури за задействане на plpgsql също могат да станат сложни и ако са твърде сложни, също да забавят производителността. Съхранената процедура се изпълнява веднъж за всеки ред, вмъкнат в таблицата. Ако в крайна сметка извършва твърде много обработка за всеки ред, вмъкванията могат да станат твърде бавни. Тестването на производителността ще се увери, че тя все още е в приемлив диапазон.
Бъдете креативни
Таблиците на дялове в PostgreSQL могат да бъдат толкова напреднали, колкото е необходимо. Вместо колони за дата, таблиците могат да бъдат разделени на колона „държава“, с таблица за всяка държава. Разделянето може да се извърши на множество колони, като колона „дата“ и „държава“. Това ще направи съхранената процедура за обработка на вложките по-сложна, но е 100% възможно.
Не забравяйте, че целите на разделянето са да се разбият изключително големи таблици на по-малки и да се направи това по добре обмислен начин, за да се даде възможност на планировчика на заявки да получи достъп до данните по-бързо, отколкото би могъл да има в по-голямата оригинална таблица.
Декларативно разделяне
В PostgreSQL 10 и по-късно беше въведена нова функция за разделяне „Декларативно разделяне“. Това е по-лесен начин за настройване на дялове, но има някои ограничения. Ако ограниченията са приемливи, вероятно ще работи по-бързо от ръчната настройка на дялове, но обилни количества тестове ще потвърдят това.
Официалната документация на postgresql има информация за декларативното разделяне и как работи. Това е ново в PostgreSQL 10 и с версия 11 на PostgreSQL на хоризонта към момента на писане на тази статия, някои от ограниченията са фиксирани, но не всички. С развитието на PostgreSQL декларативното разделяне може да стане пълна замяна на по-сложното разделяне, обхванато в тази статия. Дотогава декларативното разделяне може да бъде по-лесна алтернатива, ако нито едно от ограниченията не ограничава нуждите от разделяне.
Декларативни ограничения за разделяне
Документацията на PostgreSQL разглежда всички ограничения с този тип разделяне в PostgreSQL 10, но страхотен преглед може да се намери в Официалната PostgreSQL Wiki, която изброява ограниченията в по-лесен за четене формат, както и отбелязва кои от тях са коригирани в предстоящата PostgreSQL 11.
Попитайте общността
Администраторите на бази данни по целия свят разработват усъвършенствани и персонализирани стратегии за разделяне от дълго време и много от нас се мотаят в IRC и пощенски списъци. Ако е необходима помощ за определяне на най-добрата стратегия или просто отстраняване на грешка в съхранена процедура, общността е тук, за да помогне.
- IRC
Freenode има много активен канал, наречен #postgres, където потребителите си помагат взаимно да разбират концепции, да коригират грешки или да намерят други ресурси. - Пощенски списъци
PostgreSQL има шепа пощенски списъци, които могат да бъдат присъединени. Тук могат да се изпращат въпроси/проблеми с по-дълга форма и могат да достигнат до много повече хора от IRC във всеки един момент. Списъците могат да бъдат намерени на уебсайта на PostgreSQL, а списъците pgsql-general или pgsql-admin са добри ресурси.