Przejdź do głównej zawartości

Modelowanie danych NoSQL z Claude Code, Codeksem lub Cursorem działa wtedy, gdy agent projektuje na podstawie spisanej listy wzorców dostępu, czyta żywy schemat przez serwer MCP MongoDB lub Neo4j w trybie tylko do odczytu i oddaje mapę wzorców dostępu oraz testy dowodzące, że każde zapytanie korzysta z indeksu. Schemat NoSQL bez tych dowodów to zgadywanie.

Ta strona jest dla programistów, którzy pozwalają agentowi projektować albo zmieniać model danych: dokumentowy, klucz-wartość, grafowy lub wektorowy. Awaria, której zapobiega, wygląda tak: schemat MongoDB był czysty na demie, jeden dokument user z osadzonymi zamówieniami, adresami i koszykiem. Pół roku później tablica zamówień ma 4000 wpisów, dokument zbliża się do limitu 16 MB w MongoDB, a każdy odczyt profilu ciągnie całość przez sieć. Agent, który napisał ten schemat, nigdy nie widział twoich wzorców dostępu, więc zoptymalizował go pod demo.

W NoSQL nie ma schematu relacyjnego, za którym można się schować. Model danych jest projektem, a błędna decyzja „osadzić czy referencjonować” albo zły klucz partycji kosztuje sporo, gdy pojawi się ruch. Poniższe przepisy każą agentowi uzasadnić każdą decyzję, a potem udowodnić ją na prawdziwym silniku. Ty przeglądasz tabelę i raport z testów, a nie każde zapytanie.

  • Konfigurację MCP tylko do odczytu dla MongoDB i Neo4j w Claude Code, Codeksie i Cursorze, z flagą, dzięki której MongoDB odrzuca skany kolekcji
  • Prompt „osadzić czy referencjonować”, który zmusza agenta do argumentowania wzorcami dostępu i granicami wzrostu
  • Prompt do agregacji, w którym plan explain jest częścią wyniku
  • Prompt do projektu jednej tabeli DynamoDB, który przypisuje każdemu wzorcowi dostępu warunek klucza albo GSI
  • Prompty do Neo4j 5 i Atlas Vector Search, które omijają usuniętą składnię, wciąż generowaną przez agentów
  • Zestaw testów wzorców dostępu, pisany przez agenta i uruchamiany w CI, żeby nikt nie musiał czytać każdego zapytania

Każdy magazyn danych przechodzi te same cztery kroki. Zmienia się silnik, pętla zostaje.

  1. Najpierw spisz wzorce dostępu. Numerowaną listę odczytów i zapisów, których potrzebuje funkcja, z przybliżoną częstotliwością. Ta lista jest specyfikacją i to ją zatwierdzasz.
  2. Każ agentowi oddać projekt jako artefakt. Tabelę, która każdemu wzorcowi dostępu przypisuje kształt dokumentu, warunek klucza albo indeks, a nie akapit prozy.
  3. Sprawdź na prawdziwym silniku. Agent czyta prawdziwy schemat i uruchamia explain przez MCP albo pracuje na DynamoDB Local, zamiast zgadywać nazwy pól.
  4. Utrwal to testami. Jeden test na wzorzec dostępu sprawdza wynik i plan (użyty indeks, brak skanu). CI uruchamia je przy każdej zmianie.

Bez połączenia z bazą agent wymyśla nazwy kolekcji i pól. Z połączeniem najpierw pobiera przykładowy dokument, widzi, że już zapisujesz customerId, a nie userId, i trzyma się twojej konwencji. Ogólną konfigurację, role tylko do odczytu i serwery dla innych baz opisuje strona o serwerach MCP dla baz danych; ten krok to część specyficzna dla NoSQL.

Tę stronę obsługują dwa serwery, oba zweryfikowane 2026-09-26:

  • MongoDB MCP Server (npm mongodb-mcp-server 3.0.4, oficjalny). Narzędzia to m.in. collection-schema, collection-indexes, find, aggregate i explain. --readOnly usuwa narzędzia do tworzenia, aktualizacji i usuwania. --indexCheck odrzuca każde zapytanie, które skanowałoby całą kolekcję, więc „użyj indeksu” przestaje być radą i staje się twardym błędem, który agent musi naprawić.
  • Neo4j Cypher MCP server (PyPI mcp-neo4j-cypher 0.6.0, z neo4j-contrib). Narzędzia to get_neo4j_schema (wymaga wtyczki APOC), read_neo4j_cypher i write_neo4j_cypher; --read-only usuwa narzędzie zapisu. To pakiet Pythona, więc uruchamiasz go przez uvx, nie npx.

Serwery działają tak samo we wszystkich trzech narzędziach; różni się tylko miejsce konfiguracji.

Uruchom w terminalu w katalogu głównym repozytorium. -s project zapisuje .mcp.json, a ujęte w apostrofy ${NEO4J_PASSWORD} jest rozwijane z twojej powłoki przy starcie serwera, więc sekret nie trafia do pliku:

Okno terminala
claude mcp add -s project mongodb \
-e MDB_MCP_CONNECTION_STRING=mongodb://localhost:27017/shop \
-- npx -y mongodb-mcp-server@3.0.4 --readOnly --indexCheck
claude mcp add -s project neo4j \
-e NEO4J_URI=bolt://localhost:7687 -e NEO4J_USERNAME=neo4j \
-e 'NEO4J_PASSWORD=${NEO4J_PASSWORD}' \
-- uvx mcp-neo4j-cypher@0.6.0 --read-only

Krok 2: zdecyduj „osadzić czy referencjonować” na podstawie wzorców dostępu

Dział zatytułowany „Krok 2: zdecyduj „osadzić czy referencjonować” na podstawie wzorców dostępu”

Najważniejsza decyzja w bazie dokumentowej to, czy powiązane dane osadzić, czy trzymać przez referencję. Agent podejmuje ją dobrze, gdy dostaje wzorce dostępu, wzorzec zapisów i granice wzrostu, a nie prośbę o „dobry schemat”.

Dobra odpowiedź osadza pozycje zamówienia (ograniczone, niezmienne, zawsze czytane razem), zapisuje migawkę customer: { id, name } zamiast robić $lookup przy każdym odczycie i obsługuje wzorzec 2 indeksem złożonym { customerId: 1, createdAt: -1 }. Wzorzec 1 zwraca cały dokument, więc nie może być zapytaniem pokrytym (covered query). Jeśli agent twierdzi inaczej, nie przemyślał projekcji — odeślij mu to do poprawki. Tabela wzorców dostępu to artefakt, który zatwierdzasz. Kod z niej wynika.

Krok 3: każ każdej agregacji udowodnić swój indeks

Dział zatytułowany „Krok 3: każ każdej agregacji udowodnić swój indeks”

Potoki agregacji to miejsce, w którym agent oszczędza najwięcej czasu, i zarazem to, gdzie najczęściej oddaje zapytanie zwracające poprawny wynik przy skanie całej kolekcji. Plan wykonania musi być częścią wyniku.

Ponieważ serwer działa z --indexCheck, potok, którego pierwszy $match skanuje kolekcję, kończy się błędem, zamiast zwrócić wiarygodnie wyglądającą odpowiedź. Agent widzi błąd, proponuje indeks, a na lokalnej bazie (gdzie zdjąłeś --readOnly) tworzy go i ponawia explain, aż plan zacznie się od IXSCAN. Liczba, którą czytasz, to stosunek totalDocsExamined do nReturned: bliski 1 jest zdrowy, setki do jednego oznaczają indeks o złym kształcie.

DynamoDB najmocniej karze za mgliste prompty. Klucz partycji o niskiej kardynalności tworzy gorącą partycję, która dławi się pod obciążeniem, a pominięty wzorzec dostępu oznacza Scan na produkcji. Nie ma tu schematu tylko do odczytu, który można by podejrzeć, więc agent pracuje z listy i dowodzi projektu na DynamoDB Local (obraz Dockera amazon/dynamodb-local).

Część wykonywalna jest niewielka. Oto zapytanie dla wzorca 3 w AWS SDK v3 (@aws-sdk/lib-dynamodb 3.1141.0 w npm, 2026-09-26); łańcuch .query(params).promise() z v2 tu nie działa:

import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, QueryCommand } from '@aws-sdk/lib-dynamodb';
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
// GSI1: GSI1PK = USER#<id>, GSI1SK = TASK#<dueDate>#<taskId>
export async function getUserTasks(userId, fromDate) {
const { Items } = await ddb.send(
new QueryCommand({
TableName: 'SaaSPlatform',
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk AND GSI1SK >= :start',
ExpressionAttributeValues: {
':pk': `USER#${userId}`,
':start': `TASK#${fromDate}`,
},
ScanIndexForward: true, // ascending by due date
}),
);
return Items;
}

Zapisy wsadowe używają BatchWriteCommand, który nadal przyjmuje najwyżej 25 elementów na żądanie.

Przy mocno powiązanych danych (grafy społecznościowe, rekomendacje, siatki oszustów) poproś najpierw o model węzłów i relacji, a dopiero potem o Cypher. Wymagaj składni Neo4j 5: dane treningowe są pełne formy ograniczeń z Neo4j 4, CREATE CONSTRAINT ON ... ASSERT, którą Neo4j 5 odrzuca.

Tak wygląda poprawna odpowiedź:

CREATE CONSTRAINT user_id IF NOT EXISTS
FOR (u:User) REQUIRE u.id IS UNIQUE;
CREATE CONSTRAINT post_id IF NOT EXISTS
FOR (p:Post) REQUIRE p.id IS UNIQUE;
// Posts friends liked in the last 7 days that this user has not viewed
MATCH (user:User {id: $userId})-[:FRIENDS]-(friend:User)-[l:LIKED]->(post:Post)
WHERE l.at > datetime() - duration('P7D')
AND NOT EXISTS { (user)-[:VIEWED]->(post) }
WITH post, count(DISTINCT friend) AS friendLikes
RETURN post.id, post.content, friendLikes
ORDER BY friendLikes DESC
LIMIT 10;

Ograniczenie user_id daje zapytaniu punkt startowy: EXPLAIN powinien pokazać NodeUniqueIndexSeek na :User(id). AllNodesScan oznacza brak ograniczenia albo literówkę w etykiecie.

Embeddingi zapisane obok dokumentów dają wyszukiwanie semantyczne bez osobnej bazy wektorowej. W MongoDB Atlas odpytujesz je etapem agregacji $vectorSearch na indeksie typu vectorSearch. Starsze mapowanie pola knnVector należy do Atlas Search, jest przestarzałe i nie obsługuje $vectorSearch, a agenci wciąż je generują.

from pymongo.operations import SearchIndexModel
# A vectorSearch index needs SearchIndexModel(type="vectorSearch").
# A plain-dict create_search_index() call creates an Atlas Search index instead.
db.products.create_search_index(
model=SearchIndexModel(
definition={
"fields": [
{"type": "vector", "path": "text_embedding",
"numDimensions": 384, "similarity": "cosine"},
{"type": "filter", "path": "price"},
{"type": "filter", "path": "categories"},
]
},
name="product_embeddings",
type="vectorSearch",
)
)
pipeline = [
{"$vectorSearch": {
"index": "product_embeddings",
"path": "text_embedding",
"queryVector": query_embedding, # 384 floats from the same model
"numCandidates": 200,
"limit": 10,
"filter": {"price": {"$lte": 100}},
}},
{"$addFields": {"score": {"$meta": "vectorSearchScore"}}},
{"$project": {"text_embedding": 0}},
]

Jak zweryfikować projekt NoSQL bez czytania każdego zapytania?

Dział zatytułowany „Jak zweryfikować projekt NoSQL bez czytania każdego zapytania?”

Zatwierdzasz dwie rzeczy: tabelę wzorców dostępu z kroku projektowego i raport z testów. Nie czytasz każdego potoku, warunku klucza ani zapytania Cypher, bo testy przy każdym przebiegu CI sprawdzają na prawdziwym silniku to, czego szukałby recenzent. To ta sama zmiana, którą opisuje strona o czytaniu dowodów zamiast kodu.

MagazynCo sprawdza testKiedy test nie przechodzi
MongoDBKażdy wzorzec dostępu zwraca właściwe dokumenty, a wynik explain nie zawiera COLLSCANBrakujący albo źle uporządkowany indeks
MongoDBWalidator $jsonSchema odrzuca dokument bez wymaganego polaKształt rozminął się z zatwierdzonym projektem
DynamoDBKażdy wzorzec obsługuje Query albo GetItem; middleware rzuca wyjątek przy każdym ScanCommandWzorzec dostępu, którego projekt kluczy nie pokrywa
Neo4jEXPLAIN każdego zapytania nie zawiera AllNodesScan; SHOW CONSTRAINTS wymienia każdy klucz unikalnyUsunięte ograniczenie albo literówka w etykiecie
Wektoryrecall@10 na złotych zapytaniach nie spada poniżej zapisanej wartości bazowej; długość każdego embeddingu równa się numDimensionsZmiana modelu bez ponownego wygenerowania embeddingów

Asercja na planie sprawdza cały plan, więc działa zarówno dla planu find, jak i dla planu agregacji:

const plan = await db.collection('orders').aggregate(pipeline).explain('executionStats');
expect(JSON.stringify(plan)).not.toContain('COLLSCAN');

Uruchamiaj te testy w CI na prawdziwym silniku: kontener MongoDB przez Testcontainers (@testcontainers/mongodb 12.1.0 w npm, 2026-09-26), DynamoDB Local i kontener Neo4j. Zamockowany sterownik nie wygeneruje planu zapytania, więc niczego tu nie dowodzi. Szerszą konfigurację opisują wzorce testowania integracyjnego, a zasilanie bazy realistyczną ilością danych strategie danych testowych.

Kto zatwierdza: programista odpowiedzialny za funkcję akceptuje tabelę wzorców dostępu, zanim powstanie jakikolwiek kod, a CI musi być zielone przed merge’em. Recenzent czyta tabelę, nazwy testów i raport. Jeśli agent zmienia asercję zamiast zapytania, traktuj to jako nieudaną zmianę, tak jak opisuje to strona o ochronie wyroczni testowej.

  • Tablica rośnie bez ograniczeń w stronę limitu 16 MB. Objaw: odczyty profilu z miesiąca na miesiąc zwalniają, potem zapisy kończą się błędem. Naprawa: przenieś tablicę do kolekcji podrzędnej kluczowanej identyfikatorem rodzica (albo podziel ją na kubełki miesięczne), uzupełnij dane migracją i dopisz pytanie o nieograniczony wzrost do promptu projektowego, żeby następny model to uwzględnił.
  • Gorąca partycja w DynamoDB. Klucz typu TENANT#<id> zaczyna być dławiony, gdy ruch jednego najemcy przekroczy limit przepustowości pojedynczej partycji, a dodatkowa przydzielona przepustowość tego limitu nie podnosi. Naprawa: dodaj sufiks sharda (TENANT#<id>#<0-9>), zapisuj do losowego sharda i przy odczycie zbieraj wyniki ze wszystkich (scatter-gather).
  • Projekcja GSI pomija atrybut. Zapytania do indeksu zwracają elementy bez tego pola, a kod czyta undefined. Projekcji GSI nie da się zmienić po utworzeniu. Naprawa: utwórz nowy GSI z właściwą projekcją, przełącz odczyty, potem usuń stary.
  • Superwęzeł w Neo4j. Użytkownik z milionami relacji FRIENDS zamienia każde przejście grafu od niego w skan. Naprawa: ogranicz rozgałęzienie w zapytaniu (WITH friend LIMIT 1000) albo zamodeluj gorącą relację jako osobny węzeł.
  • Nieaktualne embeddingi. Wyniki wektorowe po cichu się pogarszają, gdy zmieniasz model embeddingów, a istniejących dokumentów nie przeliczasz. Naprawa: test wymiaru wyłapie zmianę długości; przy zamianie na model o tej samej długości przelicz wszystko i w tej samej zmianie zapisz nową wartość bazową recall.
  • Agent wymyśla schemat. Zapytania odwołują się do pól, których nie masz. Naprawa: sprawdź, czy serwer MCP jest podłączony (/mcp w Claude Code, codex mcp list w Codeksie), i każ agentowi wywołać collection-schema albo get_neo4j_schema, zanim cokolwiek napisze.
  • Agent próbuje zapisać przez MCP. Z --readOnly narzędzia zapisu nie istnieją, więc próba kończy się niegroźnym błędem. Naprawa: zostaw flagę i przeprowadź zmianę migracją; nigdy nie zdejmuj jej, żeby obejść błąd. Szerszy model zagrożeń opisuje strona o zabezpieczaniu serwerów MCP.

Czy ten artykuł Ci pomógł?

Jedno kliknięcie, bez logowania.

wcale bardzo