data-modeling
Skill0 starsChanged 43 days agoMITView on GitHub
Use when designing or changing a database schema — tables, primary keys (bigint identity, UUIDv7, ULID, Snowflake), constraints (NOT NULL, CHECK, UNIQUE, foreign keys, EXCLUDE), enums vs lookup tables, money, timestamps and time zones, names, addresses, emails and phones, soft delete vs archive, audit and history tables, multi-tenant schemas (tenant_id, composite foreign keys, row-level security), JSONB, many-to-many, trees, hot-row counters, optimistic locking, idempotency and outbox tables, zero-downtime migrations and backfills, indexing, naming, DynamoDB single-table design, event-sourcing storage, PII retention, analytics via CDC, and SQLite in production. Also use when the user says "design the schema", "UUID or auto-increment?", "how do I store money or time zones?", "add a column to a big table", or "review this migration". Not for choosing a database product (use data-infrastructure), slow-query triage or sharding (use scalability), or the end-to-end design method (use system-design).
16 other projects use a version of this.
Adopted by
Most recently taken up by
- nisarth/claude-skills-storetheir own version, first seen Nov 8, 2026
- getditto/agent-skillstheir own version, first seen Nov 8, 2026
- SWEStash/swe-workflow-skillstheir own version, first seen Nov 8, 2026
- timmywheels/skillstheir own version, first seen Nov 1, 2026
- vindreshsingh/engineering-skillstheir own version, first seen Oct 20, 2026
- MartinOlivero/saas-buildertheir own version, first seen Oct 13, 2026
Used with
Projects with it also use containerization, incident-response and 3 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
mmondoraSkillcontainerization
Docker best practices for cloud-native applications. Multi-stage builds, distroless images, security scanning, non-root users, Cloud Run orchestration. Use when writing Dockerfiles, configuring container builds, or deploying to Cloud Run/GKE.
Also in 3 projects that use this
AnthropicSkillincident-response
Run an incident response workflow — triage, communicate, and write postmortem. Trigger with "we have an incident", "production is down", an alert that needs severity assessment, a status update mid-incident, or when writing a blameless postmortem after resolution.
Also in 7 projects that use this
OpenAISkillobservability
Vercel Observability expert guidance — Drains (logs, traces, speed insights, web analytics), Web Analytics, Speed Insights, runtime logs, custom events, OpenTelemetry integration, and monitoring dashboards. Use when instrumenting, debugging, or optimizing application performance and user experience on Vercel.
Also in 6 projects that use this
AnthropicSkilldocumentation
Write and maintain technical documentation. Trigger with "write docs for", "document this", "create a README", "write a runbook", "onboarding guide", or when the user needs help with any form of technical writing — API docs, architecture docs, or operational runbooks.
Also in 3 projects that use this
Microsoft AzureSkillgit-workflow
Squad branching model: dev-first workflow with insiders preview channel
Also in 3 projects that use this
Other versions16 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 16
- microsoft/rayfin★ 646same name
- microsoft/awesome-rayfin★ 108same name
- nimadorostkar/Claude-Skills-collection★ 33same name
- jdm4pku/RE-Skills★ 26same name
- khuynh22/agent-dev-team★ 9same name
- getditto/agent-skills★ 4same name
- MartinOlivero/saas-builder★ 4same name
- bjornjee/skills★ 2same name
- mmondora/claude-skills★ 1same name
- nisarth/claude-skills-store★ 1same name
6 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository45 other files it gives its agents.
- AGENTS.mdAGENTS.md
- accessibilitySkill
- ai-interface-designSkill
- ai-native-architectureSkill
- android-designSkill
- api-designSkill
- application-securitySkill
- app-screen-patternsSkill
- auth-flowsSkill
- clean-codeSkill
- codebase-organizationSkill
- code-reviewSkill
- conversion-uxSkill
- dashboard-designSkill
- data-dense-uiSkill
- data-infrastructureSkill
- dependency-managementSkill
- deployment-and-infrastructureSkill
- design-foundationsSkill
- design-patternsSkill
- design-resourcesSkill
- design-reviewSkill
- design-systemsSkill
- design-tasteSkill
- environments-and-configSkill
- frontend-architectureSkill
- how-to-build-softwareSkill
- interaction-designSkill
- ios-designSkill
- lessons-from-failuresSkill
- mobile-architectureSkill
- mobile-designSkill
- motion-designSkill
- object-oriented-designSkill
- onboarding-designSkill
- project-bootstrapSkill
- project-contextSkill
- reliabilitySkill
- scalabilitySkill
- software-architectureSkill
- system-designSkill
- tech-stack-selectionSkill
- testing-strategySkill
- ux-principlesSkill
- web-platformSkill
The file
We split this skill into the rules it gives an agent
7 rules. 0 are common, 2 are distinctive, and 5 are very specific to this project.
0
Common
Most of the 15 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 25
Normalize by default; denormalize on purpose: Every copied value names its source of truth and its sync mechanism — or is a deliberate …
- Very specific: line 55
Default: the two-ID model — bigint identity for internal joins plus a UUIDv7 public_id for URLs and APIs.
- Very specific: line 104
text+ CHECK on length: instead of arbitrary varchar(n) in Postgres. - Very specific: line 118
Money: never float. Store amount_minor bigint + currency char(3) (exponent per currency in a currencies table — JPY 0, USD 2, some …
- Very specific: line 146
Default: shared tables with tenant_id (details and alternatives in scalability).
- Distinctive: line 230
Analytics: keep OLTP normalized; move analytics to a replica short-term, then change data capture into a warehouse (Notion: Postgres → CDC …
- Very specific: line 260
Soft delete bolted on everywhere: Every query must filter; unique constraints break.
“Common” is measured: kept by at least 6 in 10 of the 15 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Data Modeling
The schema outlives every service, framework, and team that touches it, so it is where correctness is cheapest to buy and most expensive to fix later. This skill designs Postgres-first relational schemas from access patterns and invariants, puts the rules in the database where every client must obey them, and changes schemas only through migrations that never lock a live table by surprise. Examples use PostgreSQL (16–18); MySQL and SQLite differences are called out. The outcome it protects: data that stays correct, answerable, and deletable as the product and its load grow.
Before you start
If .agents/project-context.md exists (or .claude/project-context.md), read it first and treat it as ground truth for stack, platforms, scale, design system, and constraints. Only ask for what it does not cover. If it does not exist and the task is larger than a quick fix, suggest running the project-context skill — but never block on it.
Also read the existing schema (migrations folder, ORM models, schema.sql/structure.sql) and follow its conventions for keys, naming, and timestamps unless the user asks to change them.
Core principles
- Access patterns and invariants before DDL. List the top reads and writes and the rules that must never break; the schema is derived from them, not from the UI's form fields.
- Constraints in the database are the last line of defense. App validation is for UX; NOT NULL, CHECK, UNIQUE, FK, and EXCLUDE are for correctness against every client, script, and future service.
- Normalize by default; denormalize on purpose. Every copied value names its source of truth and its sync mechanism — or is a deliberate historical snapshot.
- Choose keys once. 64-bit internal keys, non-sequential public IDs, and authorization on every access; an ID's opacity is never the access control.
- Model money, time, and people as the world is. Integer minor units + currency; instants as
timestamptz; future local events as local time + IANA zone; names and addresses without false assumptions. - Decide history, deletion, and retention up front. You cannot recover history you never recorded, and you cannot delete personal data you cannot find.
- Every schema change is expand → migrate → contract with a known lock level, a
lock_timeout, and a rollback. - Relational by default. Choose a key-value or document model only when access patterns are few, known, and proven too big for a relational database — and budget for losing ad-hoc queries.
Workflow
- Read what exists: current schema, key and naming conventions, migration tool, database version. Gate: you can state the PK type, timestamp type, and naming style in use.
- List entities and lifecycles: who creates, mutates, and deletes each; which are tenant-owned.
- List invariants and assign each an enforcement: constraint, single transaction, or eventual + reconciliation job. Gate: no invariant is "enforced by the service checking first".
- List access patterns with frequency and latency need; mark analytics patterns (they go to a replica or warehouse, not the OLTP schema).
- Write the normalized DDL: keys, NOT NULL by default, CHECKs, UNIQUEs (tenant-scoped, case-insensitive where needed), FKs with a deliberate
ON DELETE. - Add indexes for the access patterns (and every FK column); verify with
EXPLAIN (ANALYZE, BUFFERS)on realistic data. - Decide cross-cutting data: tenancy, history/audit, deletion and retention, PII inventory, concurrency control on contested rows.
- Write the migration as expand/contract steps with lock levels,
lock_timeout, batched backfill, and rollback. - Validate against the review checklist and Gotchas; fix and repeat until clean.
Primary keys
| Option | Use for | Watch out |
bigint GENERATED ALWAYS AS IDENTITY | Default internal key in one database; FKs and joins | Leaks volume and eases enumeration if exposed; needs a DB round trip to know the ID |
| UUIDv7 (RFC 9562, 2024) | Public IDs; IDs generated outside the DB (offline clients, multi-region, event-sourced aggregates) | 16 bytes; embeds creation time (ms) |
| UUIDv4 | Unguessable tokens; small tables | Random inserts scatter across the B-tree (page splits, cache misses); worst as a MySQL/InnoDB clustered PK |
| ULID | Same niche as UUIDv7 in systems that already use it | Not an IETF standard; store as uuid/binary, not text |
| Snowflake-style 64-bit | Very high write rates from many writers needing 64-bit sortable IDs | Unique worker-ID assignment; clock regression; send as strings in JSON (JS loses precision above 2^53) |
| Natural key (email, SKU) | Small, truly immutable reference data (ISO country or currency codes) | Real-world "immutable" values change; cascading key updates |
Default: the two-ID model — bigint identity for internal joins plus a UUIDv7 public_id for URLs and APIs. Use UUIDv7 as the single PK when IDs must be minted outside the database. Pick one per system and document it.
CREATE TABLE customers (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
public_id uuid NOT NULL DEFAULT uuidv7() UNIQUE, -- built into PostgreSQL 18 (as of 2026-09); earlier: extension or app-generated
tenant_id bigint NOT NULL REFERENCES tenants(id),
email text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);Never serial or 32-bit keys on growing tables: GitHub (2021) lost Actions and Pages for hours when a foreign key hit the INT32 maximum. Non-sequential public IDs reduce leaks, but authorization on every access is the control (see application-security). Password-reset and invite tokens are long random secrets, never UUIDv7.
Decision details, UUIDv7 generation before Postgres 18, and ID exposure: references/keys-and-constraints.md.
Constraints in the database
CREATE TABLE orders (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tenant_id bigint NOT NULL REFERENCES tenants(id),
customer_id bigint NOT NULL REFERENCES customers(id),
status text NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','paid','shipped','cancelled','refunded')),
currency char(3) NOT NULL REFERENCES currencies(code),
total_minor bigint NOT NULL CHECK (total_minor >= 0),
placed_at timestamptz NOT NULL DEFAULT now(),
shipped_at timestamptz,
CHECK (status <> 'shipped' OR shipped_at IS NOT NULL) -- shipped implies a ship time
);
CREATE INDEX orders_customer_id_idx ON orders (customer_id); -- Postgres does not index FK columns for you
CREATE INDEX orders_tenant_placed_idx ON orders (tenant_id, placed_at DESC);
CREATE UNIQUE INDEX users_tenant_email_uniq ON users (tenant_id, lower(email));
-- Non-overlap (bookings) with an exclusion constraint
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE TABLE room_bookings (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
room_id bigint NOT NULL REFERENCES rooms(id),
during tstzrange NOT NULL,
EXCLUDE USING gist (room_id WITH =, during WITH &&)
);- NOT NULL by default. Nullable only when "unknown / not applicable" is a real state.
NOT IN (subquery)returns no rows if the subquery has a NULL. - FKs everywhere within one database.
ON DELETE RESTRICTby default;CASCADEonly for true composition (order → order lines);SET NULLfor optional links. - UNIQUE for business keys, scoped by tenant and case-insensitive where people type the value.
- CHECK for row-level rules; cross-row rules go in a transaction or an exclusion constraint.
text+ CHECK on length instead of arbitraryvarchar(n)in Postgres; changing a CHECK is cheaper than altering a type.- SQLite: foreign keys are off until
PRAGMA foreign_keys = ONon each connection.
Enums, lookup tables, or CHECK
| Option | Default for | Avoid when |
text + CHECK (x IN (…)) | Small, code-owned state machines (order status) | Admins or users manage the values |
| Lookup table + FK | Values managed as data, needing labels, ordering, i18n, active flags | — |
Postgres ENUM | Stable sets where 4-byte storage matters | Values may be removed or renamed (no DROP VALUE) |
| Integer codes checked only in app code | Never | Always — the database cannot validate them and SQL output is unreadable |
Money, time, and people
- Money: never float. Store
amount_minor bigint+currency char(3)(exponent per currency in acurrenciestable — JPY 0, USD 2, some currencies 3), ornumeric(p,s)+ currency for accounting and fractional unit prices. Avoid Postgresmoney. Snapshot prices onto order lines. Ledgers are double-entry and append-only; corrections are reversing entries. - Instants:
timestamptz(stores a UTC instant; does not keep the original zone). Nevertimestampwithout time zone for things that happened. - Future local events (meetings, classes, store hours): store local date-time + IANA zone (
Europe/Berlin); derive the UTC instant and recompute when time-zone rules change. Storing only UTC breaks when a government changes DST rules. - Dates (birthdays, due dates, billing periods):
date. Durations:intervalor integers with the unit in the name (timeout_ms). - User time zone: IANA ID, never a fixed offset or an abbreviation like
EST. - Names: one
full_name(plus optionaldisplay_name), any characters. Emails: store as entered, unique onlower(email), verify by sending. Phones: E.164 + raw input, parsed with libphonenumber. Addresses: country code + free-form lines + structured fields only where tax or shipping needs them; snapshot onto orders.
SQL and the failure stories behind each rule: references/time-money-people.md.
Deletion, history, and retention
Deletes must be recoverable for a window — Atlassian (2022) deleted 883 customer sites by script and needed up to two weeks to restore — but deleted_at on every table leaks into every query. Default:
| Need | Default |
| Ordinary rows, no product "trash" | Hard delete from the live table + copy the row as JSON into deleted_records (retention window, then purge) |
| Product has trash/restore | deleted_at + partial unique indexes WHERE deleted_at IS NULL + a live_ view the app uses |
| "Deleted" is really a business state | A status (closed, archived) — it is not deletion |
| Tenant/account deletion, bulk deletes | Scheduled with a grace period, dry-run output first, one identifier type per API, audited |
- Audit log (who did what, when): append-only table written in the same transaction as the change (or by trigger) with
actor_id,action,entity_type,entity_id,diff jsonb,occurred_at,request_id. - History ("what did it look like at time T"): a
<table>_historyfilled by trigger withvalid_from/valid_to; bitemporal (valid time + recorded time) for finance, insurance, HR. Decide now — history not recorded is gone. - PII: inventory it per column, keep it in few tables keyed by user, give every table a retention rule and a purge job, and design an erasure path that reaches search indexes, warehouses, caches, logs, and processors. Legal specifics (e.g., GDPR erasure) need counsel; this is not legal advice.
Patterns and SQL: references/patterns-and-antipatterns.md (deletion, audit, history) and references/time-money-people.md (PII, retention).
Multi-tenancy
Default: shared tables with tenant_id (details and alternatives in scalability). Make cross-tenant references impossible, not just unlikely:
CREATE TABLE projects (
tenant_id bigint NOT NULL REFERENCES tenants(id),
id bigint GENERATED ALWAYS AS IDENTITY,
name text NOT NULL,
PRIMARY KEY (tenant_id, id)
);
CREATE TABLE tasks (
tenant_id bigint NOT NULL,
id bigint GENERATED ALWAYS AS IDENTITY,
project_id bigint NOT NULL,
PRIMARY KEY (tenant_id, id),
FOREIGN KEY (tenant_id, project_id) REFERENCES projects (tenant_id, id)
);
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE tasks FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON tasks
USING (tenant_id = current_setting('app.tenant_id')::bigint)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::bigint);
-- per request: BEGIN; SET LOCAL app.tenant_id = '42'; … COMMIT;tenant_id NOT NULL on every tenant-owned table, leading every composite index and unique constraint. The app role must not be superuser or BYPASSRLS; use SET LOCAL with transaction poolers. The tenant is also your future shard key (Notion, Shopify).
JSONB
Use for sparse or heterogeneous attributes rarely filtered (per-integration settings), verbatim external payloads (webhook bodies), and data always read and written whole with its parent. Make it a column when you filter, join, sort, aggregate, or constrain on it, or when it is really a list of child rows updated independently.
ALTER TABLE integrations ADD COLUMN config jsonb NOT NULL DEFAULT '{}'
CHECK (jsonb_typeof(config) = 'object');
ALTER TABLE integrations ADD COLUMN region text GENERATED ALWAYS AS (config->>'region') STORED;Validate shape at the app boundary (JSON Schema, zod, pydantic) and store a version field.
Relationships and anti-patterns
| Situation | Default | Never |
| Many-to-many | Join table with composite PK + reverse index; name it after the relationship (memberships) | Arrays of IDs you need to join or constrain |
| One child, several parent types | Exclusive arc (one nullable FK per parent + CHECK (num_nonnulls(a_id, b_id) = 1)), per-parent join tables, or a common supertype | Polymorphic (commentable_type, commentable_id) — no FK possible |
| User-defined fields | Typed custom_field_definitions + custom_field_values, or JSONB | EAV attributes(entity_id, name, value text) for known attributes |
| Trees | Adjacency list (parent_id) + recursive CTE; closure table for heavy subtree/ancestor queries; ltree for path-like data | Nested sets for data that changes |
| Counters on hot rows | Derive with count(*), slotted counters, or buffered increments; conditional decrement for stock | UPDATE … SET n = n + 1 on one row from every request |
| Concurrent edits | version column; UPDATE … WHERE id = $1 AND version = $2; 0 rows → 409; expose as ETag | Last write silently wins on user-edited records |
| External side effects and events | idempotency_keys table and transactional outbox; consumers dedupe with an inbox table | Save, then publish from app code |
SQL for each: references/patterns-and-antipatterns.md.
Safe migrations
Most ALTER TABLE forms take an ACCESS EXCLUSIVE lock, and a DDL statement waiting behind a long query blocks every query after it — an outage even when the change itself is instant. Always:
SET lock_timeout = '2s'; -- fail fast and retry with backoff instead of queueing everyone behind you| Change | Safe recipe |
| Add nullable column / constant default | Direct (metadata-only since PG11); volatile defaults rewrite the table |
| Add NOT NULL | CHECK (col IS NOT NULL) NOT VALID → VALIDATE → SET NOT NULL (PG12+ skips the scan) → drop the check |
| Add FK or CHECK | ADD CONSTRAINT … NOT VALID, then VALIDATE CONSTRAINT |
| Add index / unique | CREATE [UNIQUE] INDEX CONCURRENTLY outside a transaction; attach unique with USING INDEX |
| Change type, rename, drop | Expand/contract: new column, dual-write, batched backfill, switch reads, drop later |
Backfills run as resumable jobs in batches of ~1k–10k rows with a keyset cursor, throttled on replication lag — never one giant UPDATE. Before any destructive step, confirm a recently restore-tested backup and print the target database (GitLab 2017: several backup mechanisms, none working when needed). Lint migrations in CI (strong_migrations, Squawk, Django's concurrent index operations); use pgroll or gh-ost where they fit.
Lock table, worked example, tools, and the migration PR checklist: references/migrations.md.
Indexing
Index for the queries you have: equality columns first, then the range/sort column ((tenant_id, status, created_at DESC)); every FK column; partial indexes for skewed predicates (WHERE status = 'pending'); expression indexes matching the query exactly (lower(email)); INCLUDE for index-only scans; GIN for JSONB/arrays/full-text, GiST for ranges and exclusion, BRIN for huge time-ordered tables. Drop indexes with no scans. Verify with EXPLAIN (ANALYZE, BUFFERS). Details: references/indexing.md; slow-query triage in production: scalability.
Naming conventions
snake_case, unquoted, one plural-or-singular table style per codebase; PK id; FK <entity>_id; instants <verb>_at (paid_at), dates <noun>_on or _date; timestamps over booleans when "when" matters (verified_at over is_verified); units as suffixes (_ms, _bytes, _minor); constraints and indexes named <table>_<cols>_<type> (orders_customer_id_fk); avoid reserved words (user, order, group) and type prefixes.
Beyond one relational database
- DynamoDB single-table design fits when access patterns are few, known, and latency-critical at scale; each pattern becomes one
Queryon PK/SK or a GSI. It is inflexible to new patterns and hard to analyze — often not worth it when product queries change often. - Event sourcing:
events(stream_id, version, …)withPRIMARY KEY (stream_id, version)for optimistic concurrency; rebuildable projections; keep PII out of events or crypto-shred it. Only where history is the requirement. - Analytics: keep OLTP normalized; move analytics to a replica short-term, then change data capture into a warehouse (Notion: Postgres → CDC → lake). Treat CDC topics as a public contract.
- SQLite in production is legitimate for single-node, per-tenant, or edge databases with WAL,
busy_timeout,foreign_keys = ON, and continuous off-host backup.
Details: references/nosql-and-analytics.md.
Review checklist
- Access patterns and invariants listed; each invariant mapped to a constraint or transaction.
- NOT NULL by default; CHECKs for domains; UNIQUE for business keys (tenant-scoped, case-insensitive).
- FKs declared, FK columns indexed,
ON DELETEchosen deliberately. - 64-bit keys; public IDs non-sequential; authorization still enforced.
- Money = integer minor units or numeric + currency; no floats; prices and addresses snapshotted.
timestamptzfor instants; local time + IANA zone for future events;datefor dates.- Deletion recoverable for a window; soft delete justified; retention and purge defined.
- Tenant isolation:
tenant_ideverywhere, composite FKs, RLS as defense in depth. - No polymorphic FKs, no EAV for known attributes, JSONB only for sparse/opaque data.
- Hot rows and contested edits have concurrency control; side effects use idempotency and outbox tables.
- Migration states lock level,
lock_timeout, backfill plan, rollback; backup restore-tested. - Indexes match top queries (EXPLAIN verified); no redundant or unused indexes.
- PII inventoried; history/audit decided; analytics off the primary.
Gotchas
- Designing tables from the UI form. Forms change monthly; start from entities, invariants, and access patterns.
- "The service validates it." Two concurrent requests both pass the check; only a constraint sees all writers. Handle the violation error (map to 409/422).
- Forgetting FK indexes in Postgres. Deletes on the parent scan the child table; joins slow down. MySQL/InnoDB creates them; Postgres does not.
- UUIDv4 as the PK of a large, write-heavy table. Random inserts bloat indexes and WAL; use identity or UUIDv7.
- Floats or
/100for money. Minor-unit exponents vary by currency; mixing major and minor units has caused 100× charges. timestampwithout time zone for events, or UTC-only for future meetings. The first loses the instant; the second breaks when DST rules change.first_name/last_namerequired, ASCII-only. Many people have one name, several family names, or non-Latin scripts.- Soft delete bolted on everywhere. Every query must filter; unique constraints break; deleted PII lingers. Archive table by default;
deleted_atonly for real trash/restore. - Unique constraints without
tenant_id. Tenant A's user blocks tenant B from using the same email. - Polymorphic associations from ORM generators. The database cannot enforce them; orphans accumulate.
ALTER TABLEwithoutlock_timeout, orCREATE INDEXwithoutCONCURRENTLY, on a live table. A blocked lock queue takes the app down.- Schema and code change in one deploy. Old and new code run side by side; each step must be compatible with both.
- One-shot backfill
UPDATEon millions of rows. Long locks, bloat, replica lag. Batch it. - Enum values stored as bare integers. Unreadable in SQL and silently reusable; use text + CHECK or a lookup table.
- Analytics queries on the primary. Move them to a replica or warehouse before they cause an incident.
Output format
For schema design or review, reply with:
Assumptions: <DB + version, existing conventions (Observed/Inferred)>
Access patterns: AP1 … (frequency, latency) Invariants: I1 … → enforced by <constraint/txn/reconciliation>
DDL: <complete CREATE TABLE / INDEX statements>
Decisions: <keys, tenancy, deletion/history, money/time choices — one line each with why>
Migration (if changing a live schema): <ordered steps, lock level, lock_timeout, backfill, rollback>
Not checked: <e.g., production row counts, EXPLAIN on real data>References
| File | Read when |
references/keys-and-constraints.md | choosing primary/public IDs, writing constraints (CHECK, UNIQUE, FK, EXCLUDE, deferrable), enums vs lookup tables, or naming |
references/time-money-people.md | storing money, currencies, ledgers, timestamps, time zones, recurring events, names, addresses, emails, phones, or designing PII retention and erasure |
references/patterns-and-antipatterns.md | soft delete, audit/history, multi-tenancy, JSONB, polymorphic associations, EAV, many-to-many, trees, counters, optimistic locking, idempotency/outbox tables, or denormalization |
references/migrations.md | changing a live schema: adding columns/constraints/indexes, changing types, renaming, backfilling, or reviewing a migration PR |
references/indexing.md | deciding which indexes a new table needs, composite order, partial/expression/covering indexes, index types, or removing indexes |
references/nosql-and-analytics.md | DynamoDB or Cassandra modeling, document stores, event-sourcing storage, CDC to a warehouse, star schemas, or SQLite in production |
Related skills
system-design— the end-to-end method this schema serves (requirements, estimates, rollout).data-infrastructure— which database, cache, queue, or warehouse to use.scalability— slow queries, replicas, partitioning, sharding, pooling, and RLS with poolers.api-design— exposing IDs, pagination cursors, ETags, and idempotency keys over HTTP.application-security— authorization (IDOR/BOLA), encryption of sensitive fields, and logging without PII.reliability— rolling out migrations safely, backups, and restore drills.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

