Blog
PostgreSQLTestingSynthetic Data

How to generate realistic test data for PostgreSQL

Learn how to create production-quality synthetic data that preserves referential integrity, matches your column distributions, and respects foreign key constraints | all with a single command.

The Weavori TeamAugust 12, 20268 min read

Realistic test data is synthetic data that behaves like production: referentially intact across foreign keys, shaped like your real column distributions, and coherent across related columns. The fastest way to get it is to read the schema itself — not to configure generators by hand. This guide shows the five things that separate realistic data from random rows, and how Weavori automates each one.

Why hand-written seed scripts fail

Most teams have a seed script that generates mock data for local and staging databases. It works at first — and then production data gets messy in ways the script never modeled.

"The problem is that production data is messy and it's very difficult to replicate that with mock data." — Neosync founders, Show HN: Neosync, May 2024

The failure modes are consistent: dangling foreign keys when fixtures drift, uniform random values that don't exercise real query plans, and broken rows (a customer predating their own signup, a paid order with no paid_at). Each one is a bug that ships to production because the test environment lied.

Step 1: Start from the schema, not your fixtures

The schema is the source of truth. Tables, column types, foreign keys, check constraints, enums, defaults — everything a realistic generator needs is already declared there.

Weavori connects to any PostgreSQL database (or accepts pasted CREATE TABLE statements via DDL paste mode), introspects the schema, and infers a generator for every column from ~230 semantic rules: first_name becomes a name, zip becomes a ZIP, a status enum samples its own values. No YAML, no DSL, nothing to maintain when the schema changes.

Zero configuration is the point

The instant your schema changes, the generated data changes with it. Config-driven tools drift silently; schema-driven generation can't.

Step 2: Keep every foreign key intact

Referential integrity is the first thing tests notice. A broken FK produces false failures — or worse, false passes when a join silently returns nothing.

Weavori resolves the foreign-key dependency graph and generates parents before children, streaming rows with a constant-memory FK cache. Every generated row references a real parent row: guaranteed by construction, not checked after the fact. Composite primary keys and multi-level chains (users → orders → order_items) are handled in dependency order.

Step 3: Match production distributions

Uniform random data is unrealistic data. If 70% of your real status values are active, a flat random generator gives you ~33% each — and the query planner picks different indexes, different joins, different plans.

Weavori samples PostgreSQL's column statistics (pg_stats — most-common values and frequencies) and weights generated values accordingly. A status column that is 70% active stays 70% active. Numeric columns follow their histogram shape, not a flat range. This is the difference between data that looks real in a screenshot and data that behaves like production under load.

i

Why distributions matter for performance testing

Load and performance tests only exercise your real query plans if the data shape matches production. Distribution-aware generation is what makes a 1 GB replica behave like a 1 TB database.

Step 4: Enforce cross-column coherence

Realistic data is internally consistent. Customers don't predate their own signups. Orders placed in January aren't shipped in the previous year. A paid_at timestamp exists exactly when status = 'paid' — and is NULL otherwise.

Weavori detects these patterns from your schema — temporal ordering between timestamp columns, conditional nullability, CHECK constraints — and enforces them per row. This is the layer that catches the bugs seed scripts and naive generators never see, because the generated data honors the relationships between columns, not just each column in isolation.

Step 5: Automate it in CI

Manual test data is data that rots. The realistic-data workflow ends in a pipeline: every push regenerates a clean, production-shaped database for tests.

Weavori is built for this: API keys for headless auth, plain/json output for logs, standardized exit codes for pipeline branching, and a fingerprint-based schema cache so repeat runs skip introspection. Licenses are Ed25519-signed and validate offline — CI runners on restricted networks don't phone home. See the CI/CD integration guide for GitHub Actions, GitLab, CircleCI, and Jenkins examples.

Synthetic generation vs anonymization

Two jobs get confused: generating fresh data, and masking existing data.

Fresh synthetic generation (Weavori)Anonymization (Greenmask, PostgreSQL Anonymizer)
InputYour schema (and optionally pg_stats distributions)Real production rows
OutputNew rows — no production value ever appearsTransformed real rows
PII riskNone by constructionDepends on transformation coverage
ConfigZero — inferred from the schemaPer-column rules (DDL labels or transformation configs)
SetupClient-side CLI, no DB changesExtension install / dump pipelines, often superuser
FK integrityBy constructionN/A — data already exists

If your constraint is "no real data can leave our network", fresh generation is the stronger position: nothing is copied or scrambled because nothing real is touched.

The tool landscape in 2026

Different tools fit different workflows. Honest breakdown of the options teams evaluate alongside Weavori:

ToolTypePricingBest for
WeavoriCLI (npx)Free 2K rows/mo; Pro $15/mo flatZero-config generation from your own schema, CI, air-gapped
Tonic.aiEnterprise SaaSToken-metered + custom contractsEnterprise compliance, 16+ data sources, agentic AI
MockarooWeb SaaS$60–$7,500/yr by row capsInstant browser demos, flat files, API mocking

For full pricing, feature-by-feature comparisons, and who each tool is honestly best for, see the comparison hub.

How fast is it?

Realistic doesn't have to mean slow. Weavori streams generation with constant memory, so scale doesn't trade off against correctness:

  • 100,000 rows across 14 tables in 12.4 seconds
  • 25,000 rows in 2.1 seconds; 2,500 rows in 384 ms
  • Typical schemas (under 200 tables) complete in under 2 minutes
  • Stress-tested against a 500 GB benchmark database
i

Methodology

Benchmarks measured July 2026 against Weavori's internal benchmark suite (pgbench-derived schema, Dockerized PostgreSQL 17, generation into a target database via COPY). Numbers vary with hardware and schema shape.

Summary

Realistic PostgreSQL test data comes from five practices, in order:

  1. Start from the schema — introspection beats hand-configured fixtures
  2. Guarantee FK integrity by construction — parents before children, always
  3. Sample real distributions — pg_stats keeps 70% active at 70% active
  4. Enforce cross-column coherence — timestamps, conditionals, and CHECKs per row
  5. Automate in CI — API keys, exit codes, offline licensing, cached introspection

Weavori does all five in one command:

$npx --yes @weavori/cli generate postgres://localhost:5432/mydb

The preview shows the full generation plan before a single row is written — estimate rows, spot generator choices, then run. Start with the quickstart, and if you're weighing it against another tool, the comparison hub has the honest trade-offs.