Bazy grafowe
Eksploruj Neo4j dla złożonych relacji
Twój startup buduje wielodostępną platformę SaaS do zarządzania projektami. Musisz zaprojektować bazę danych, która obsługuje: organizacje użytkowników, projekty, zadania, śledzenie czasu, załączniki plików, komentarze w czasie rzeczywistym i kanały aktywności. System musi skalować się do tysięcy organizacji z milionami zadań przy jednoczesnym utrzymaniu wydajności zapytań poniżej 100ms.
Po ukończeniu tej lekcji opanujesz:
Zaprojektuj bazę danych, która:
Zacznij od trybu Ask, aby wyjaśnić wymagania:
"Pomóż mi zaprojektować bazę danych dla SaaS zarządzania projektami:- Wielodostępna (B2B)- Organizacje mają wielu użytkowników- Projekty zawierają zadania z podzadaniami- Śledzenie czasu na zadanie- Załączniki plików (URL S3)- Komentarze w czasie rzeczywistym- Kanały aktywności- Pełna ścieżka audytuJakie pytania powinienem najpierw zadać?"
"Na podstawie tych funkcji, przeanalizuj wzorce dostępu:- Dashboard użytkownika (projekty, najnowsze zadania, powiadomienia)- Widok projektu (zadania, kamienie milowe, członkowie zespołu)- Szczegóły zadania (komentarze, załączniki, dzienniki czasu)- Raporty (czas według projektu, produktywność użytkownika)- Wyszukiwanie (we wszystkich encjach)Zidentyfikuj stosunek odczytów/zapisów i gorące ścieżki"
Przełącz na tryb Agent:
"Porównaj opcje baz danych dla tego przypadku użycia:- PostgreSQL z JSONB- MySQL z odpowiednim indeksowaniem- MongoDB dla elastyczności- Podejście hybrydowe (PostgreSQL + Redis)Rozważ: potrzeby ACID, złożoność zapytań, strategię skalowania"
Skonfiguruj połączenia baz danych przez MCP:
{ "mcpServers": { "postgres-dev": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-postgres", "postgresql://user:pass@localhost:5432/projektdb" ] }, "sqlite-local": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-sqlite", "./data/local.db" ] }, "redis-cache": { "command": "npx", "args": ["-y", "@redis/mcp-redis"], "env": { "REDIS_URL": "redis://localhost:6379" } } }}
// Z aktywnym PostgreSQL MCP:"Używając PostgreSQL MCP, przeanalizuj obecny schemat i:- Pokaż wszystkie tabele i relacje- Zidentyfikuj brakujące indeksy- Zasugeruj ulepszenia normalizacji- Sprawdź antywzorce"
// Bezpośrednie tworzenie schematu:"Stwórz tabele organizations i users z:- Kluczami głównymi UUID- Odpowiednimi ograniczeniami- Kolumnami audytu (created_at, updated_at)- Wsparciem soft delete"
"Używając PostgreSQL MCP:- Wylistuj wszystkie relacje kluczy obcych- Znajdź tabele bez kluczy głównych- Zidentyfikuj nieużywane indeksy- Pokaż rozmiary tabel i liczby wierszy"
"Przeanalizuj wolne zapytania z MCP:- Uruchom EXPLAIN ANALYZE na tym zapytaniu- Zasugeruj ulepszenia indeksów- Pokaż plan wykonania zapytania- Zidentyfikuj problemy ze skanowaniem tabel"
"Używając SQLite MCP do prototypowania:- Eksportuj schemat do formatu PostgreSQL- Generuj skrypty migracji- Waliduj integralność danych- Stwórz procedury wycofania"
"Używając Redis MCP, implementuj cachowanie dla:- Danych sesji użytkownika (SET/GET z TTL)- Wyników zapytań dashboard (z unieważnianiem)- Śledzenia obecności w czasie rzeczywistym (używając Redis Sets)- Liczników ograniczania szybkości (używając INCR z EXPIRE)"
// Przykład operacji Redis przez MCP:"Skonfiguruj ogranicznik szybkości używając Redis:- Użyj INCR do liczenia żądań- Ustaw wygaśnięcie na 60 sekund- Zwróć bieżącą liczbę- Obsłuż warunki wyścigu"
Dla funkcji wspomaganych AI:
{ "mcpServers": { "chroma": { "command": "npx", "args": ["-y", "chroma-mcp"], "env": { "CHROMA_URL": "http://localhost:8000" } } }}
"Używając Chroma MCP, implementuj:- Wyszukiwanie semantyczne zadań- Rekomendacje podobnych projektów- Filtrowanie oparte na treści- Przechowywanie embeddings dla funkcji ML"
Względy bezpieczeństwa:
Wskazówki wydajnościowe:
"Zaprojektuj podstawowy schemat bazy danych z:- Odpowiednią normalizacją (3NF gdzie właściwe)- Strategią wielodostępności (wspólny schemat vs oddzielne)- Implementacją soft delete- Polami audytu (created_by, updated_at, itp.)- UUID vs serial IDStwórz instrukcje SQL DDL"
Przykład schematu:
-- Organizacje (najemcy)CREATE TABLE organizations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, slug VARCHAR(100) UNIQUE NOT NULL, plan VARCHAR(50) DEFAULT 'free', settings JSONB DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), deleted_at TIMESTAMPTZ);
-- UżytkownicyCREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(255) NOT NULL, password_hash VARCHAR(255), settings JSONB DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), deleted_at TIMESTAMPTZ);
-- Członkostwo w organizacjiCREATE TABLE organization_members ( organization_id UUID REFERENCES organizations(id), user_id UUID REFERENCES users(id), role VARCHAR(50) NOT NULL DEFAULT 'member', joined_at TIMESTAMPTZ DEFAULT NOW(), PRIMARY KEY (organization_id, user_id));
-- ProjektyCREATE TABLE projects ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), organization_id UUID REFERENCES organizations(id) NOT NULL, name VARCHAR(255) NOT NULL, description TEXT, status VARCHAR(50) DEFAULT 'active', settings JSONB DEFAULT '{}', created_by UUID REFERENCES users(id), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), deleted_at TIMESTAMPTZ, -- Indeks kompozytowy dla izolacji najemców INDEX idx_org_projects (organization_id, status, created_at DESC));
-- Zadania ze strukturą hierarchicznąCREATE TABLE tasks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), organization_id UUID REFERENCES organizations(id) NOT NULL, project_id UUID REFERENCES projects(id) NOT NULL, parent_task_id UUID REFERENCES tasks(id), title VARCHAR(500) NOT NULL, description TEXT, status VARCHAR(50) DEFAULT 'todo', priority INTEGER DEFAULT 0, due_date DATE, assignee_id UUID REFERENCES users(id), estimated_hours DECIMAL(5,2), metadata JSONB DEFAULT '{}', created_by UUID REFERENCES users(id), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), deleted_at TIMESTAMPTZ, -- Indeksy dla częstych zapytań INDEX idx_org_tasks (organization_id, project_id, status), INDEX idx_assignee_tasks (assignee_id, status, due_date), INDEX idx_parent_tasks (parent_task_id));
@schema.sql"Dodaj tabele wspierające dla:- Wpisów śledzenia czasu- Komentarzy (polimorficzne)- Załączników (polimorficzne)- Kanału aktywności- PowiadomieńZawrzyj odpowiednie indeksy i ograniczenia"
"Implementuj zaawansowane funkcje bazy danych:- Row Level Security dla wielodostępności- Zmaterializowane widoki dla dashboardów- Konfigurację wyszukiwania pełnotekstowego- Logowanie audytu oparte na trigger'ach- Strategię partycjonowania dla dużych tabel"
@schema.sql"Przeanalizuj wzorce zapytań i stwórz indeksy:- Indeksy pokrywające dla gorących zapytań- Indeksy częściowe dla zapytań filtrowanych- Indeksy GIN dla wyszukiwania JSONB- Indeksy kompozytowe dla sortowań- Rozważ koszty utrzymania indeksów"
Przykład strategii indeksowania:
-- Zapytania dashboard (najczęstsze)CREATE INDEX idx_user_recent_tasksON tasks (assignee_id, updated_at DESC)WHERE deleted_at IS NULL AND status != 'done';
-- Lista zadań projektu z filtramiCREATE INDEX idx_project_tasks_filteredON tasks (project_id, status, priority DESC, created_at DESC)WHERE deleted_at IS NULL;
-- Wyszukiwanie pełnotekstowe w zadaniachCREATE INDEX idx_tasks_searchON tasks USING gin( to_tsvector('english', title || ' ' || COALESCE(description, '')));
-- Wyszukiwanie metadanych JSONBCREATE INDEX idx_tasks_metadataON tasks USING gin(metadata);
-- Agregacje śledzenia czasuCREATE INDEX idx_time_entries_reportingON time_entries (organization_id, user_id, date)INCLUDE (hours);
"Zaprojektuj strategię cachowania z Redis:- Podgrzewanie cache dla dashboardów- Wzorce unieważniania- Cachowanie wyników zapytań- Przechowywanie sesji- Dane obecności w czasie rzeczywistymStwórz plan implementacji"
// Metoda 1: Używanie MCP bazy danych"Używając PostgreSQL MCP, zoptymalizuj to zapytanie dashboard:SELECT * FROM tasks WHERE assignee_id = $1 AND status = 'active'- Uruchom EXPLAIN ANALYZE- Zasugeruj lepsze indeksy- Pokaż redukcję kosztów zapytania"
// Metoda 2: Optymalizacja manualna@queries/dashboard.sql"Zoptymalizuj te krytyczne zapytania:- Dashboard użytkownika (projekty + najnowsze zadania)- Przegląd projektu (statystyki + zespół + postęp)- Wyszukiwanie zadań z filtrami- Agregacje raportów czasuUżyj EXPLAIN ANALYZE i zasugeruj ulepszenia"
// MCP może bezpośrednio testować optymalizacje:"Używając PostgreSQL MCP:1. Stwórz sugerowany indeks2. Ponownie uruchom EXPLAIN ANALYZE3. Porównaj wydajność przed/po4. Wycofaj jeśli nie poprawione"
"Implementuj PostgreSQL RLS dla wielodostępności:- Polityki dla każdej tabeli- Implikacje wydajnościowe- Strategie testowania- Bypass dla operacji administratoraZapewnij pełną izolację najemców"
Przykład implementacji RLS:
-- Włącz RLSALTER TABLE projects ENABLE ROW LEVEL SECURITY;ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
-- Stwórz politykiCREATE POLICY tenant_isolation_projects ON projects FOR ALL TO application_user USING (organization_id = current_setting('app.current_org_id')::UUID);
CREATE POLICY tenant_isolation_tasks ON tasks FOR ALL TO application_user USING (organization_id = current_setting('app.current_org_id')::UUID);
-- Funkcja do ustawiania bieżącego najemcyCREATE OR REPLACE FUNCTION set_current_tenant(org_id UUID)RETURNS void AS $$BEGIN PERFORM set_config('app.current_org_id', org_id::text, true);END;$$ LANGUAGE plpgsql;
"Stwórz kompletną strategię izolacji najemców:- Connection pooling per tenant- Monitoring i limity zapytań- Kwoty storage- Strategie backupów- Możliwości eksportu danych"
"Skonfiguruj monitoring wydajności:- Logowanie wolnych zapytań- Statystyki użycia indeksów- Monitoring rozdęcia tabel- Metryki connection pool- Użycie zasobów przez najemców"
"Skonfiguruj system migracji bazy danych:- Kontrola wersji dla schematu- Możliwości rollback- Migracje bez przestojów- Strategie migracji danych- Testowanie migracji"
Przykład migracji:
export const up = async (db: Database) => { await db.schema.createTable('task_dependencies', (table) => { table.uuid('id').primary().defaultTo(db.raw('gen_random_uuid()')); table.uuid('task_id').references('id').inTable('tasks').notNullable(); table.uuid('depends_on_task_id').references('id').inTable('tasks').notNullable(); table.enum('dependency_type', ['blocks', 'relates_to', 'duplicates']); table.timestamps(true, true);
table.unique(['task_id', 'depends_on_task_id']); table.index(['depends_on_task_id']); });
// Dodaj ograniczenie sprawdzające zapobiegające samo-zależnościom await db.raw(` ALTER TABLE task_dependencies ADD CONSTRAINT no_self_dependency CHECK (task_id != depends_on_task_id) `);};
export const down = async (db: Database) => { await db.schema.dropTable('task_dependencies');};
"Planuj przyszłe zmiany schematu:- Dodawanie pól niestandardowych per tenant- Strategie archiwizacji- Przygotowanie do shardingu- Konfiguracja read replica- Wersjonowanie schematu"
"Generuj realistyczne dane testowe:- Wiele organizacji- Różne objętości danych- Przypadki brzegowe (głębokie zagnieżdżenie itp.)- Scenariusze testów wydajności- Weryfikacja integralności danych"
Wykorzystaj MCP do szybkiej iteracji:
// Eksploracja schematu w czasie rzeczywistym"Używając PostgreSQL MCP:- Pokaż mi obecny schemat- Znajdź wszystkie tabele z > 1M wierszy- Wylistuj indeksy nieużywane przez 30 dni"
// Bezpośrednie wykonywanie zapytań"Używając PostgreSQL MCP, uruchom to zapytanie:SELECT table_name, pg_size_pretty(pg_total_relation_size(table_name::regclass))FROM information_schema.tablesWHERE table_schema = 'public'ORDER BY pg_total_relation_size(table_name::regclass) DESC"
// Analiza wydajności"Przeanalizuj to wolne zapytanie z PostgreSQL MCP:- Pokaż plan wykonania- Zidentyfikuj wąskie gardła- Zasugeruj optymalizacje"
Używaj sprawdzonych wzorców:
-- Asocjacje polimorficzne dla elastycznościCREATE TABLE comments ( id UUID PRIMARY KEY, commentable_type VARCHAR(50), -- 'task', 'project' commentable_id UUID, content TEXT, -- Indeksy częściowe dla każdego typu INDEX idx_task_comments (commentable_id) WHERE commentable_type = 'task');
-- Dane hierarchiczne z ltreeCREATE EXTENSION ltree;CREATE TABLE categories ( id SERIAL PRIMARY KEY, path ltree NOT NULL, name VARCHAR(255));CREATE INDEX idx_category_path ON categories USING gist(path);
Optymalizuj złożone zapytania:
-- Używaj CTE dla czytelności i wydajnościWITH user_projects AS ( SELECT p.* FROM projects p JOIN organization_members om ON p.organization_id = om.organization_id WHERE om.user_id = $1 AND p.deleted_at IS NULL),recent_tasks AS ( SELECT t.* FROM tasks t WHERE t.project_id IN (SELECT id FROM user_projects) AND t.updated_at > NOW() - INTERVAL '7 days' ORDER BY t.updated_at DESC LIMIT 10)SELECT * FROM recent_tasks;
Mądrze wykorzystuj JSONB:
-- Ustawienia z domyślnymi wartościami i walidacjąCREATE TABLE tenant_settings ( organization_id UUID PRIMARY KEY, settings JSONB NOT NULL DEFAULT '{ "features": { "timeTracking": true, "customFields": false }, "limits": { "maxProjects": 10, "maxUsersPerProject": 50 } }', CONSTRAINT valid_settings CHECK ( settings ? 'features' AND settings ? 'limits' ));
-- Efektywne zapytania JSONBCREATE INDEX idx_premium_tenants ON tenant_settings ((settings->'features'->>'customFields')) WHERE (settings->'features'->>'customFields')::boolean = true;
Problem: Ładowanie powiązanych danych w pętlach
Rozwiązanie:
// Źle: zapytania N+1const projects = await db.query('SELECT * FROM projects');for (const project of projects) { project.tasks = await db.query( 'SELECT * FROM tasks WHERE project_id = $1', [project.id] );}
// Dobrze: Pojedyncze zapytanie z joinconst projectsWithTasks = await db.query(` SELECT p.*, COALESCE( json_agg(t.*) FILTER (WHERE t.id IS NOT NULL), '[]' ) as tasks FROM projects p LEFT JOIN tasks t ON t.project_id = p.id GROUP BY p.id`);
Problem: Wolne zapytania na dużych tabelach
Rozwiązanie:
-- Analizuj wydajność zapytańEXPLAIN (ANALYZE, BUFFERS)SELECT * FROM tasksWHERE organization_id = $1 AND status = 'active'ORDER BY created_at DESC;
-- Dodaj odpowiedni indeksCREATE INDEX CONCURRENTLY idx_active_tasksON tasks (organization_id, created_at DESC)WHERE status = 'active' AND deleted_at IS NULL;
Problem: Deadlock’i i wolne aktualizacje
Rozwiązanie:
-- Użyj advisory locks dla złożonych operacjiSELECT pg_advisory_lock(hashtext('project_' || $1));
-- Wykonaj operacje
SELECT pg_advisory_unlock(hashtext('project_' || $1));
-- Lub użyj SKIP LOCKED dla kolejekUPDATE jobs SET status = 'processing'WHERE id = ( SELECT id FROM jobs WHERE status = 'pending' ORDER BY created_at FOR UPDATE SKIP LOCKED LIMIT 1)RETURNING *;
Rozwiń swój projekt bazy danych dalej:
Zaawansowane funkcje
Strategie skalowania
Dostrajanie wydajności
Twój projekt bazy danych jest udany gdy:
Zespoły używające tych wzorców osiągają:
Przed wejściem na produkcję:
Opanowałeś projektowanie baz danych. Gotowy na więcej?
Bazy grafowe
Eksploruj Neo4j dla złożonych relacji
Szeregi czasowe
Projektuj dla danych IoT i metryk
Event sourcing
Implementuj architekturę opartą na zdarzeniach
Kontynuuj do Frontend UI z projektu →