---
title: "Data checklist: all 53 rules · Agent Wranglers"
url: https://agentwranglers.se/playbook/data/checklist
updated: 2026-10-20
licence: "All rights reserved"
---

# Data checklist

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

Every rule in the data playbook, by chapter, required rules first. Each links to its own page with how we built it and how to check it.

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers data playbook: https://agentwranglers.se/playbook/data/checklist`.

## Schema design

- [ ] **Required** [Enforce rules with constraints](https://agentwranglers.se/playbook/data/schema-design/constraints): Put every rule the data must always follow into the database as a constraint. App checks alone fail when a second app, a script or a bug writes directly.
- [ ] **Required** [Choose exact types](https://agentwranglers.se/playbook/data/schema-design/exact-types): Store each value in the type that means it. Money is an exact number, time is a time with a zone, and true or false is a boolean.
- [ ] **Required** [Give every row a stable key](https://agentwranglers.se/playbook/data/schema-design/stable-keys): Give every table a primary key that never changes. Never use a value people can edit, such as an email or a name, as the key.
- [ ] **Recommended** [Name things the same way everywhere](https://agentwranglers.se/playbook/data/schema-design/schema-names): Name tables, columns and constraints by one convention, so people and agents can guess a name without looking it up.
- [ ] **Recommended** [Keep one fact in one place](https://agentwranglers.se/playbook/data/schema-design/normalise): Store each fact once, and derive the rest. Copies of a fact drift apart sooner or later.
- [ ] **Recommended** [Plan how rows are removed](https://agentwranglers.se/playbook/data/schema-design/deletion-design): Decide for each table what deleting means, and make the foreign keys say it.
- [ ] **Recommended** [Keep each customer's data apart](https://agentwranglers.se/playbook/data/schema-design/tenant-isolation): When one database holds data for many customer accounts, tag every row with its account and let the database refuse rows from other accounts.

## Queries and indexes

- [ ] **Required** [Pass values as parameters](https://agentwranglers.se/playbook/data/queries-and-indexes/parameters): Send every value as a query parameter. Never build SQL by joining strings with input.
- [ ] **Required** [Index the queries you run](https://agentwranglers.se/playbook/data/queries-and-indexes/index-the-queries-you-run): Add an index for each frequent query's filter, join and sort, and no index that no query uses.
- [ ] **Required** [Load related rows in one query](https://agentwranglers.se/playbook/data/queries-and-indexes/no-n-plus-one): Never run one query per item in a list. Load related rows together with a join or one `IN` query.
- [ ] **Recommended** [Read the query plan](https://agentwranglers.se/playbook/data/queries-and-indexes/query-plans): Before you change a slow query or add an index, read its plan. Check again after the change.
- [ ] **Recommended** [Select only the columns you use](https://agentwranglers.se/playbook/data/queries-and-indexes/select-columns): Name the columns a query needs. Avoid `SELECT *` in app code.
- [ ] **Recommended** [Page through long lists by key](https://agentwranglers.se/playbook/data/queries-and-indexes/keyset-pagination): For long lists, page by the last key seen instead of `OFFSET`. Offsets get slower with every page and skip or repeat rows when data changes.

## Transactions and concurrency

- [ ] **Required** [Wrap multi-step changes in a transaction](https://agentwranglers.se/playbook/data/transactions-and-concurrency/transactions): When one change writes several rows or tables, do it in one transaction, so it fully happens or not at all.
- [ ] **Required** [Make conditional writes](https://agentwranglers.se/playbook/data/transactions-and-concurrency/conditional-writes): Put the condition into the write itself, so the database decides atomically. Never read a value, decide in the app and write it back.
- [ ] **Required** [Make writes safe to repeat](https://agentwranglers.se/playbook/data/transactions-and-concurrency/idempotent-writes): Let clients retry a write without doing it twice. Networks fail after the server has acted, so retries will happen.
- [ ] **Recommended** [Know your isolation level](https://agentwranglers.se/playbook/data/transactions-and-concurrency/isolation-levels): Know which isolation level each database uses by default, and raise it where a check and a write must see the same data.
- [ ] **Recommended** [Send side effects through an outbox](https://agentwranglers.se/playbook/data/transactions-and-concurrency/outbox): When a change must also send an email, a webhook or a message, write that task to a table in the same transaction. Send it afterwards from that table.
- [ ] **Recommended** [Set aside messages that keep failing](https://agentwranglers.se/playbook/data/transactions-and-concurrency/dead-letter-queue): After a set number of failed attempts, move a message or job to a separate dead letter queue and alert. Never retry it forever or drop it.

## Migrations

- [ ] **Required** [Change the schema only with migrations](https://agentwranglers.se/playbook/data/migrations/versioned-migrations): Every schema change is a numbered migration file in version control. Nobody changes a shared database by hand.
- [ ] **Required** [Never edit an applied migration](https://agentwranglers.se/playbook/data/migrations/immutable-migrations): Once a migration has run anywhere outside your own machine, never change it. Fix mistakes with a new migration.
- [ ] **Required** [Expand first and contract later](https://agentwranglers.se/playbook/data/migrations/expand-and-contract): Make every schema change work with both the old and the new code, so deploys and rollbacks never break.
- [ ] **Required** [Test migrations on a copy of production data](https://agentwranglers.se/playbook/data/migrations/test-migrations): Run each migration on a recent copy of production data before it reaches production, and time it.
- [ ] **Required** [Fix production data with reviewed scripts](https://agentwranglers.se/playbook/data/migrations/data-fixes): Change or delete production data only with a reviewed script in version control. The script checks how many rows it touched before it commits.
- [ ] **Recommended** [Keep migrations from locking tables](https://agentwranglers.se/playbook/data/migrations/lock-safe-migrations): Write migrations that never block a busy table for long. A long lock stops every request that touches it.

## Backups and recovery

- [ ] **Required** [Encrypt backups](https://agentwranglers.se/playbook/data/backups-and-recovery/encrypted-backups): Encrypt every backup before it leaves the server, and keep the key apart from the backups.
- [ ] **Required** [Prove restores work](https://agentwranglers.se/playbook/data/backups-and-recovery/restore-tests): Restore a backup on a schedule and check the result. An untested backup is a hope, not a backup.
- [ ] **Recommended** [Keep point-in-time recovery for busy databases](https://agentwranglers.se/playbook/data/backups-and-recovery/point-in-time-recovery): Where losing a day of writes is not acceptable, keep the database's change log, so you can restore to any moment.

## Personal data

- [ ] **Required** [Store only the personal data you need](https://agentwranglers.se/playbook/data/personal-data/minimal-columns): Add a personal data column only when a feature needs it, and write down why. GDPR Article 5 asks for data that is limited to its purpose.
- [ ] **Required** [Delete old data automatically](https://agentwranglers.se/playbook/data/personal-data/retention-jobs): Give each kind of personal data a retention period, and let a scheduled job delete or anonymise rows when it ends.
- [ ] **Required** [Keep personal data out of test copies](https://agentwranglers.se/playbook/data/personal-data/masked-copies): Never copy personal data into development, test or staging databases. Mask or replace it while copying.
- [ ] **Required** [Log who reads personal data](https://agentwranglers.se/playbook/data/personal-data/access-logging): Record when staff or support tools read or change a person's data, so misuse can be found and proved.
- [ ] **Recommended** [Encrypt the most sensitive fields](https://agentwranglers.se/playbook/data/personal-data/field-encryption): Encrypt fields whose leak would cause serious harm, such as ID numbers or health data, in the app before they reach the database.

## Operations

- [ ] **Required** [Set time limits on every query](https://agentwranglers.se/playbook/data/operations/query-timeouts): Give every query and transaction a time limit, so one slow query cannot hold locks and connections forever.
- [ ] **Required** [Watch slow queries](https://agentwranglers.se/playbook/data/operations/slow-query-log): Record queries that take longer than a set time, and fix or explain the five worst every week.
- [ ] **Required** [Let routine maintenance run](https://agentwranglers.se/playbook/data/operations/maintenance): Keep the database's own maintenance running, so tables don't bloat and the planner has fresh statistics.
- [ ] **Required** [Give the app a least-privilege database user](https://agentwranglers.se/playbook/data/operations/app-database-role): Connect the app with a user that can read and write its tables, but cannot change the schema or read other databases.
- [ ] **Recommended** [Pool database connections](https://agentwranglers.se/playbook/data/operations/connection-pooling): Reuse a small, fixed pool of connections instead of opening one per request.
- [ ] **Recommended** [Watch size and growth](https://agentwranglers.se/playbook/data/operations/capacity): Track how fast the database and its biggest tables grow, and alert well before disk or plan limits are reached.

## Caches and copies

- [ ] **Required** [Name the source of truth](https://agentwranglers.se/playbook/data/caches-and-copies/source-of-truth): For every piece of data, one store is the source of truth. Caches, search indexes and replicas are copies that can be rebuilt from it.
- [ ] **Required** [Decide how stale a cache may be](https://agentwranglers.se/playbook/data/caches-and-copies/cache-freshness): Give every cached value an expiry or an invalidation rule, chosen for how stale it may safely be.
- [ ] **Recommended** [Read from replicas only what may lag](https://agentwranglers.se/playbook/data/caches-and-copies/replica-lag): Send reads to a replica only when a few seconds of delay is acceptable. Read your own writes from the primary.
- [ ] **Recommended** [Keep search indexes in step](https://agentwranglers.se/playbook/data/caches-and-copies/search-index-sync): Update the search index from the source of truth through a reliable queue, and rebuild it fully on a schedule.

## Product analytics

- [ ] **Required** [Keep a tracking plan in the code](https://agentwranglers.se/playbook/data/product-analytics/tracking-plan): List every analytics event in one file in the repository, with its name, purpose, owner and properties. Name events as an object and a past-tense action.
- [ ] **Required** [Validate every event before you store it](https://agentwranglers.se/playbook/data/product-analytics/event-validation): Check each incoming event against its schema at the collector. Store valid events, and set invalid ones aside with the reason.
- [ ] **Required** [Track only after consent](https://agentwranglers.se/playbook/data/product-analytics/analytics-consent): Send no analytics event that uses device storage or identifies a person until that person has agreed. Read the stored choice in the tracking module itself.
- [ ] **Required** [Keep personal data out of events](https://agentwranglers.se/playbook/data/product-analytics/events-without-personal-data): Never put names, email addresses, phone numbers, free text or full URLs with query strings in event properties. Use an internal ID that analytics cannot turn back into a person.
- [ ] **Required** [Record business facts on the server](https://agentwranglers.se/playbook/data/product-analytics/server-side-business-events): Record events that count money, sign-ups or other business facts on the server, at the moment the fact is committed. Use browser events only for behaviour in the page.
- [ ] **Required** [Write the experiment plan before it starts](https://agentwranglers.se/playbook/data/product-analytics/experiment-plan): Before an A/B test starts, write down its one primary metric, guardrail metrics, minimum effect, sample size and stop date. Decide only at that sample size.
- [ ] **Required** [Assign each user to one variant for good](https://agentwranglers.se/playbook/data/product-analytics/experiment-assignment): Assign variants by hashing a stable user ID with the experiment name, so a person sees the same variant on every visit and device. Log an exposure when the variant is shown.
- [ ] **Required** [Delete raw events on a schedule](https://agentwranglers.se/playbook/data/product-analytics/analytics-retention): Keep raw events with an analytics ID for a fixed time only, such as 14 months. Then delete them or roll them up into counts without IDs.
- [ ] **Recommended** [Define each metric once](https://agentwranglers.se/playbook/data/product-analytics/metric-definitions): Define every business metric once, in version control, and build every dashboard and report from that definition.
- [ ] **Recommended** [Keep test and bot traffic out of the numbers](https://agentwranglers.se/playbook/data/product-analytics/exclude-test-traffic): Mark every event from staff, test accounts, automated tests and known bots at the collector, and leave marked events out of every metric.
