Przejdź do głównej zawartości

Projektowanie baz danych - Cursor

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:

  • Projektowanie schematów baz danych wspomagane AI
  • Strategiczny wybór między SQL a NoSQL
  • Implementację wzorców wielodostępności
  • Optymalizację pod kątem wzorców odczytu/zapisu
  • Tworzenie wydajnych indeksów i ograniczeń
  • Strategie migracji i wersjonowania
  • Podstawowe zrozumienie baz danych (SQL/NoSQL)
  • Ukończone poprzednie lekcje
  • Zainstalowany PostgreSQL lub MySQL
  • Opcjonalnie: Redis dla przykładów cachowania
  • Opcjonalnie: Serwery MCP baz danych (PostgreSQL, SQLite, Redis)

Zaprojektuj bazę danych, która:

  • Wspiera izolację wielodostępną
  • Efektywnie obsługuje złożone relacje
  • Skaluje się do milionów rekordów
  • Utrzymuje zgodność ACID tam gdzie potrzeba
  • Zapewnia wydajność zapytań w czasie rzeczywistym
  • Zawiera ścieżki audytu i soft delete
  1. Definiowanie wymagań danych

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 audytu
Jakie pytania powinienem najpierw zadać?"
  1. Analiza wzorców dostępu
"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"
  1. Wybór strategii bazy danych

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"

Faza 1.5: Serwery MCP baz danych dla szybkiego rozwoju

Dział zatytułowany „Faza 1.5: Serwery MCP baz danych dla szybkiego rozwoju”
  1. Dostępne serwery MCP baz danych

Skonfiguruj połączenia baz danych przez MCP:

~/.cursor/mcp.json
{
"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"
}
}
}
}
  1. Projektowanie schematu z MCP
// 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"
  1. Zaawansowane użycie MCP baz danych
"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"
  1. Redis MCP do cachowania
"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"
  1. Integracja z bazą danych wektorową

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:

  • Używaj poświadczeń tylko do odczytu do analizy
  • Nigdy nie wystawiaj poświadczeń produkcyjnych
  • Regularnie rotuj hasła MCP baz danych
  • Używaj szyfrowania connection string

Wskazówki wydajnościowe:

  • Ogranicz zestawy wyników zapytaniami MCP
  • Używaj ustawień connection pooling
  • Monitoruj czas wykonania zapytań MCP
  • Cachuj częste operacje MCP
  1. Zaprojektuj podstawowy schemat
"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 ID
Stwó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żytkownicy
CREATE 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 organizacji
CREATE 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)
);
-- Projekty
CREATE 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)
);
  1. Zaprojektuj tabele wspierające
@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"
  1. Implementuj zaawansowane funkcje
"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"
  1. Stwórz optymalne indeksy
@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_tasks
ON tasks (assignee_id, updated_at DESC)
WHERE deleted_at IS NULL AND status != 'done';
-- Lista zadań projektu z filtrami
CREATE INDEX idx_project_tasks_filtered
ON tasks (project_id, status, priority DESC, created_at DESC)
WHERE deleted_at IS NULL;
-- Wyszukiwanie pełnotekstowe w zadaniach
CREATE INDEX idx_tasks_search
ON tasks USING gin(
to_tsvector('english', title || ' ' || COALESCE(description, ''))
);
-- Wyszukiwanie metadanych JSONB
CREATE INDEX idx_tasks_metadata
ON tasks USING gin(metadata);
-- Agregacje śledzenia czasu
CREATE INDEX idx_time_entries_reporting
ON time_entries (organization_id, user_id, date)
INCLUDE (hours);
  1. Implementuj strategię cachowania
"Zaprojektuj strategię cachowania z Redis:
- Podgrzewanie cache dla dashboardów
- Wzorce unieważniania
- Cachowanie wyników zapytań
- Przechowywanie sesji
- Dane obecności w czasie rzeczywistym
Stwórz plan implementacji"
  1. Optymalizacja zapytań
// 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 czasu
Użyj EXPLAIN ANALYZE i zasugeruj ulepszenia"
// MCP może bezpośrednio testować optymalizacje:
"Używając PostgreSQL MCP:
1. Stwórz sugerowany indeks
2. Ponownie uruchom EXPLAIN ANALYZE
3. Porównaj wydajność przed/po
4. Wycofaj jeśli nie poprawione"
  1. Implementuj Row Level Security
"Implementuj PostgreSQL RLS dla wielodostępności:
- Polityki dla każdej tabeli
- Implikacje wydajnościowe
- Strategie testowania
- Bypass dla operacji administratora
Zapewnij pełną izolację najemców"

Przykład implementacji RLS:

-- Włącz RLS
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
-- Stwórz polityki
CREATE 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 najemcy
CREATE 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;
  1. Zaprojektuj izolację najemców
"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"
  1. Monitoring wydajności
"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"
  1. Stwórz system migracji
"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:

migrations/001_add_task_dependencies.ts
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');
};
  1. Planuj ewolucję schematu
"Planuj przyszłe zmiany schematu:
- Dodawanie pól niestandardowych per tenant
- Strategie archiwizacji
- Przygotowanie do shardingu
- Konfiguracja read replica
- Wersjonowanie schematu"
  1. Stwórz dane testowe
"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.tables
WHERE 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ści
CREATE 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 ltree
CREATE 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ści
WITH 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 JSONB
CREATE 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+1
const 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 join
const 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
`);

Rozwiń swój projekt bazy danych dalej:

  1. Zaawansowane funkcje

    • Implementuj event sourcing dla ścieżki audytu
    • Dodaj subskrypcje w czasie rzeczywistym z LISTEN/NOTIFY
    • Stwórz zmaterializowane widoki dla analityki
    • Implementuj wyszukiwanie na poziomie bazy z pg_trgm
  2. Strategie skalowania

    • Zaprojektuj strategię shardingu według organizacji
    • Implementuj read replica z monitoringiem opóźnień
    • Dodaj connection pooling z PgBouncer
    • Stwórz proces archiwizacji starych danych
  3. Dostrajanie wydajności

    • Implementuj cachowanie wyników zapytań
    • Dodaj ograniczanie szybkości na poziomie bazy
    • Stwórz niestandardowe funkcje agregujące
    • Optymalizuj pod konkretne obciążenia

Twój projekt bazy danych jest udany gdy:

  • ✅ Wszystkie zapytania wykonują się poniżej 100ms w skali
  • ✅ Zero niespójności danych
  • ✅ Pełna izolacja najemców zweryfikowana
  • ✅ Migracje uruchamiają się bez przestojów
  • ✅ Utrzymane 99.9% uptime
  • ✅ Backup/restore przetestowane i poniżej 5min
  • ✅ Monitoring wychwytuje problemy proaktywnie
  • ✅ Schemat wspiera przyszłe funkcje

Zespoły używające tych wzorców osiągają:

  • 10x poprawę wydajności zapytań
  • 90% redukcję bugów związanych z bazą danych
  • Zero wycieków danych najemców
  • 5-minutowy cel czasu odzyskiwania

Przed wejściem na produkcję:

  • Schemat przejrzany przez zespół
  • Indeksy przeanalizowane z danymi produkcyjnymi
  • Izolacja wielodostępności przetestowana
  • Strategia backupów zaimplementowana
  • Monitoring i alerty skonfigurowane
  • Rollback migracji przetestowany
  • Wydajność zbenchmarkowana
  • Audyt bezpieczeństwa zakończony
  1. Projektuj pod zapytania: Poznaj swoje wzorce dostępu przed projektowaniem
  2. Planuj skalę: Projektuj zakładając 100x wzrost
  3. Indeksuj strategicznie: Za mało lub za dużo - oba szkodzą
  4. Testuj z prawdziwymi danymi: Dane deweloperskie ukrywają problemy
  5. Monitoruj wszystko: Nie można optymalizować tego czego się nie mierzy
  • Analiza wymagań: 2 godziny
  • Początkowy projekt schematu: 3 godziny
  • Optymalizacja i indeksy: 2 godziny
  • Konfiguracja wielodostępności: 2 godziny
  • Testowanie i udoskonalenie: 3 godziny
  • Razem: ~12 godzin (vs 40+ tradycyjnie)

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 →