stack i tech
PostgreSQL jako fundament automatyzacji.
Postgres to nie tylko OLTP dla aplikacji. W automatyzacji używam go jako backbone dla wielu rzeczy: pgvector dla RAG, JSONB dla elastycznych eventów, triggers dla event sourcing, listen/notify dla real-time pipeline. Jedna baza, wiele funkcji.
Pięć ról Postgresa w automatyzacji
Dobrze zaprojektowany workflow używa Postgresa do więcej niż "baza na dane":
- OLTP baza aplikacji: klienci, zamówienia, faktury. Standardowa rola.
- Vector store dla RAG: pgvector extension. Embeddings dokumentów plus similarity search w SQL. Zamiast dedykowanej bazy wektorowej (Pinecone, Weaviate) używam pgvector, gdy wolumeny na to pozwalają (do około miliona wektorów).
- Event store: logi wszystkich zdarzeń (workflow executions, API calls, user actions) w JSONB. Audyt plus debug plus retencja per typ.
- Job queue: dla prostych kolejek (bez Redisa) używam Postgres z SKIP LOCKED dla distributed queue. Worker'y zabierają zadania bez konfliktu.
- Configuration store: settings per klient, feature flags, thresholds. Zmieniane runtime, bez redeploy. JSONB dla elastyczności.
Zalet: jedna baza zamiast pięciu (oszczędność infra i operations), spójność danych (możesz JOIN'ować event z klientem), dojrzałość Postgres (backup, monitoring, HA dobrze znane).
pgvector dla RAG
pgvector to extension Postgresa dodające typ vector plus similarity operators. Pełny setup:
CREATE EXTENSION vector;
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT,
embedding vector(768),
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
Workflow:
- Embed dokumentu przez Vertex AI albo inne API, trafia jako wektor 768-wymiarowy.
- INSERT do tabeli plus metadata (tytuł, autor, data).
- Przy pytaniu embed query, SELECT z ORDER BY distance plus LIMIT top-k.
Indeksy: HNSW dla większości zastosowań (szybki), IVFFlat dla bardzo dużych zbiorów (oszczędzający pamięć). Dla małych kolekcji (poniżej 10 000 wektorów) nawet bez indeksu jest OK, sequential scan.
Wygląda jak zwykły Postgres (bo jest), więc: standardowy backup, standardowy monitoring, możliwość JOIN'owania z tabelami klientów dla access control.
JSONB dla eventów i elastycznego schema
JSONB to binary JSON w Postgres. Szybki, zaindeksowalny, elastyczny. Perfect dla zdarzeń z różnym schema:
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
event_type VARCHAR(64),
payload JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_events_type ON events (event_type);
CREATE INDEX idx_events_payload ON events USING gin (payload);
INSERT INTO events (event_type, payload) VALUES
('order_placed', '{{"customer_id": 123, "amount": 299.90, "currency": "PLN"}}'),
('invoice_sent', '{{"invoice_id": "FV/2026/001", "recipient": "a@b.pl"}}');
-- Query po wartości w JSONB:
SELECT * FROM events
WHERE event_type = 'order_placed'
AND payload->>'currency' = 'PLN'
AND (payload->>'amount')::numeric > 500;
Elastyczność: różne eventy mają różne pola, nie trzeba ALTER TABLE przy dodawaniu nowego typu. Migracje schema ograniczone do kolumn strukturalnych (event_type, created_at).
Pułapki: JSONB nie jest idealny dla bardzo dużych payloadów (powyżej 1 MB), dla heavy analytics lepiej kolumny typowe. Idealny dla average eventu 1-10 KB.
Triggers i LISTEN/NOTIFY dla event-driven
Postgres potrafi wysyłać notyfikacje do aplikacji gdy coś się zmienia. Połączenie triggers plus LISTEN/NOTIFY daje real-time pipeline bez dodatkowej infrastruktury.
CREATE OR REPLACE FUNCTION notify_order_changes()
RETURNS TRIGGER AS $$
BEGIN
PERFORM pg_notify('order_changes', json_build_object(
'id', NEW.id,
'status', NEW.status,
'amount', NEW.amount
)::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER orders_notify
AFTER INSERT OR UPDATE ON orders
FOR EACH ROW EXECUTE FUNCTION notify_order_changes();
W workflow n8n plus Python listener:
- Listener (Python, Node.js) subskrybuje kanał
order_changesprzez LISTEN. - Gdy trigger wypali, listener dostaje payload natychmiast (milliseconds).
- Listener triggeruje workflow n8n (POST do webhook) z payload.
Wady: jeśli listener padnie, notyfikacje giną (nie ma persistency). Dla krytycznych workflow dodaję fallback: cron co minutę sprawdza zmiany według updated_at timestamp.
Postgres jako job queue (bez Redisa)
Dla prostych workflow'ów nie musisz wprowadzać Redisa, Postgres wystarcza:
CREATE TABLE job_queue (
id BIGSERIAL PRIMARY KEY,
queue_name VARCHAR(64) NOT NULL,
payload JSONB NOT NULL,
status VARCHAR(16) DEFAULT 'pending',
attempts INT DEFAULT 0,
locked_until TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Worker pobiera zadanie:
BEGIN;
UPDATE job_queue SET
status = 'processing',
locked_until = NOW() + INTERVAL '5 minutes',
attempts = attempts + 1
WHERE id = (
SELECT id FROM job_queue
WHERE queue_name = 'emails'
AND status = 'pending'
AND (locked_until IS NULL OR locked_until < NOW())
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING *;
COMMIT;
FOR UPDATE SKIP LOCKED to klucz: pozwala wielu worker'om równocześnie pobierać zadania bez konfliktu. Każdy worker bierze pierwsze niezablokowane, pomija zajęte.
Zalety vs Redis: jedna baza, pełna durability (crash safe), łatwy audit (SELECT z historii). Wady: wyższa latencja (kilka ms zamiast sub-ms), ograniczona skala (praktyczny cap około 10 000 jobs/sec).
Typowe pułapki Postgresa w automatyzacji
- Brak autovacuum-owej higieny: na tabelach z dużym ruchem (eventy, jobs) dead tuples rosną bez vacuum. Po miesiącach query stają się wolne. Monitor pg_stat_user_tables, automatyczny vacuum plus manual analyze co miesiąc.
- JSONB zamiast kolumn: entuzjazm JSONB bywa za duży. Jeśli pole występuje w 100 procent eventów i jest używane w WHERE, powinno być typed column, nie JSONB path. Typed column ma indexy B-tree (szybsze) i pragma check type.
- Brak partitioningu tabel historycznych: tabela events po roku ma 10 mln wierszy, query po created_at zaczynają być wolne. Partitioning by month (pg_partman albo deklaratywne partitioning w PG 11+) rozwiązuje.
- Long-running transactions: workflow trzymający transaction przez 10 minut blokuje vacuum na wszystkich tabelach. Krótkie transakcje (commit często) plus monitoring
pg_stat_activity. - Brak read replicas dla raportów: raporting queries konkurują o zasoby z OLTP. Read replica dla heavy reads (materiały, dashboards, RAG embeddings) zdejmuje load z primary.
Najczęstsze pytania
Czy pgvector jest wystarczający zamiast Pinecone?
Dla większości wdrożeń tak. pgvector obsługuje do milionów wektorów z HNSW indeksem, latencja query typowo pod 100 ms. Powyżej 10 milionów wektorów albo gdy potrzeba rozproszonej infrastruktury (multi-region), wyspecjalizowane bazy (Pinecone, Qdrant, Weaviate) zaczynają wygrywać. 90 procent moich wdrożeń zostaje na pgvector.
Postgres 16 czy starsze wersje?
Postgres 16 dla nowych projektów (ma ulepszone partitioning, logical replication, lepszy parallel query). Dla istniejących klientów staram się zmigrować do PG 15+ (obie mają długi support). PG 14 i starsze wymagają upgrade przy większych zmianach. PG 12 i starsze nie bierze już do nowych integracji.
Czy workflow n8n powinien mieć swoją bazę czy dzielić z aplikacją klienta?
Zależy. Dla małych wdrożeń (do 10 000 executions dziennie) współdzielenie bazy z aplikacją klienta jest OK, jeden serwer wystarcza. Dla większych wdrożeń oddzielna baza albo osobna instancja, żeby heavy load n8n nie wpływał na performance aplikacji biznesowej.
Jak backupować pgvector dane?
Standardowym pg_dump albo pg_basebackup. pgvector dane są w tabelach jak każde inne, backup działa out-of-the-box. Dla bardzo dużych kolekcji wektorów (powyżej 10 GB) rozważam logical replication do backup replica zamiast batch dump.
Czy używasz ORM (SQLAlchemy, Prisma) czy raw SQL?
Kombinacja. ORM (SQLAlchemy w Pythonie, Drizzle w TypeScripcie) dla CRUD i migracji schema. Raw SQL dla complex queries (agregacje, window functions, pgvector similarity search, CTE). ORM nie zawsze generuje optymalny SQL dla nietrywialnych zapytań.
Chcesz pogadać o konkretnym projekcie?
Warsztat otwierający to 90 minut, zdalnie albo w Poznaniu, bez zobowiązań. Mówimy, czy Twój proces nadaje się do automatyzacji i w jakiej skali.
napisz do mnie → adi@workinflows.pl · LinkedIn