Compare a fictional membership migration

Choose a supplied pair, or two JSON datasets following the method. Checks cover users, purchases, progress and job references. An invariant failure remains unresolved; equal row totals do not prove preservation. Each file is limited to 256 KiB and 1,000 records per table. Files stay local.

Local dataset comparison

Choose both files. JavaScript is required.

Results contain at most 100 validation issues and 500 differences. Any limit or unresolved input is explicit. Reload clears inputs.

Unchanged control

Valid records survive unchanged.

Before JSON · After JSON

{
  "status": "compared",
  "afterIssueCodes": [],
  "summary": {
    "added": 0,
    "removed": 0,
    "changed": 0
  },
  "differences": []
}
Missing job

A valid dataset can still omit a required source record.

Before JSON · After JSON

{
  "status": "compared",
  "afterIssueCodes": [],
  "summary": {
    "added": 0,
    "removed": 1,
    "changed": 0
  },
  "differences": [
    {
      "type": "removed",
      "table": "jobs",
      "id": "j2",
      "before": {
        "id": "j2",
        "userId": "u2",
        "purchaseId": "p2",
        "status": "queued"
      }
    }
  ]
}
Duplicate identifier

Equal counts conceal a duplicate and lost identity.

Before JSON · After JSON

{
  "status": "unresolved",
  "afterIssueCodes": [
    "DUPLICATE_ID"
  ]
}
Broken user join

Equal counts conceal a purchase pointing to an absent user.

Before JSON · After JSON

{
  "status": "unresolved",
  "afterIssueCodes": [
    "MISSING_USER",
    "MISSING_ENTITLEMENT",
    "JOB_OWNER_MISMATCH"
  ]
}
Job owner mismatch

Existing identifiers can still form the wrong relationship.

Before JSON · After JSON

{
  "status": "unresolved",
  "afterIssueCodes": [
    "JOB_OWNER_MISMATCH"
  ]
}
Progress without a purchase

A progress course must match a purchase for the same user.

Before JSON · After JSON

{
  "status": "unresolved",
  "afterIssueCodes": [
    "MISSING_ENTITLEMENT"
  ]
}
Changed price and progress

Exact numeric field differences remain visible.

Before JSON · After JSON

{
  "status": "compared",
  "afterIssueCodes": [],
  "summary": {
    "added": 0,
    "removed": 0,
    "changed": 2
  },
  "differences": [
    {
      "type": "changed",
      "table": "purchases",
      "id": "p1",
      "field": "amountCents",
      "before": 1200,
      "after": 120
    },
    {
      "type": "changed",
      "table": "progress",
      "id": "g1",
      "field": "percent",
      "before": 25,
      "after": 0
    }
  ]
}
Equal-count record replacement

Two job totals match while one id disappears and another appears.

Before JSON · After JSON

{
  "status": "compared",
  "afterIssueCodes": [],
  "summary": {
    "added": 1,
    "removed": 1,
    "changed": 0
  },
  "differences": [
    {
      "type": "removed",
      "table": "jobs",
      "id": "j2",
      "before": {
        "id": "j2",
        "userId": "u2",
        "purchaseId": "p2",
        "status": "queued"
      }
    },
    {
      "type": "added",
      "table": "jobs",
      "id": "j9",
      "after": {
        "id": "j9",
        "userId": "u2",
        "purchaseId": "p2",
        "status": "queued"
      }
    }
  ]
}

Download the standalone practice kit

Original synthetic examples with independently specified outcomes. Not a vendor test, real migration, or verification of a real message. Our editorial method

Can matching totals hide a broken migration?

Try this invented acceptance check: users U-01 and U-02 both exist; purchase P-01 belongs to U-01; a progress record refers to U-02 and P-01. Source and destination each contain two users, one purchase and one progress record. Would a count comparison catch the problem?

No. Every total matches and both user identifiers exist, but the progress record and purchase disagree about their owner. The acceptance report should name that relationship, retain the failing identifiers and leave the record unresolved. A corrected total alone is not evidence of a corrected relationship.

Rehearse the next failure before cutover

Run the relational exercise with a broken dataset, record the expected failure, then run the repaired counterpart. Keep the actual migration paused until your own acceptance criteria are met. If the next change concerns addresses embedded in WordPress content, continue with WordPress URL replacement: rehearse the scope before changing the database; that is a different check from relationship integrity.

What the synthetic kit checks

Each dataset has users, purchases, progress and jobs. Every record has an exact string identifier. Purchases and progress must refer to a known user; progress needs a purchase for the same user and course; job references must agree with their purchase. The downloadable method lists these relationship rules, called invariants.

Two exports with equal row counts can still contain a missing ID or a broken reference. The supplied cases deliberately seed these failures. Unknown or invalid inputs remain unresolved rather than receiving invented matches.

Use it as one layer of an acceptance rehearsal

Keep untouched before and after exports. Run the known cases first, then adapt only the schema and invariants you have explicitly reviewed. The original fixtures are not a real membership platform export. Passing this model does not verify passwords, email, entitlements, schedules, consent, or actual destination behavior.

The standalone practice kit includes a Node runner and expected outcomes. It needs no account or network connection.

Take it with you

Relational cases and expected findings

Download worksheet

Sources & verification

Product details and prices can change. Check the linked provider before buying.

  1. PostgreSQL documentation: constraints and foreign keys Accessed 14 Sep 2026

Sources link directly to providers. Product buttons may use separately labeled affiliate links. Read our disclosure.

Your decision at a glance

Compare the options

Full comparison

Documentation-based guidance. No tested ranking or performance score.

Managed VPS

KnownHost

Consider it for
Consider KnownHost Basic VPS when you need a specific server configuration and clearly defined management support. It was advertised from $43.25 per month; confirm the billing term and full quote.
Starting cost
Basic managed VPS advertised from $43.25/month on 2026-09-15; confirm term and full invoice
Before you choose
Not for undiagnosed application bottlenecks or sites a shared plan already covers. No course-capacity or migration test is claimed here.

Checked 15 Sep 2026 · Pricing source

View KnownHost plans

Direct provider link; no affiliate tracking added by this publication. Link disclosure

Hosting

Liquid Web

Consider it for
Use Liquid Web when the job is a defined managed-server responsibility and the quoted plan lists which server tasks they own versus yours.
Starting cost
Quote-dependent; VPS commonly $10–$100 and managed configurations can exceed $100 per Liquid Web's cost explainer (2026-09-15)
Before you choose
Not a fit until the failing component is identified. Management does not include application debugging unless the written scope says so.

Checked 15 Sep 2026 · Pricing source

View Liquid Web plans

Direct provider link; no affiliate tracking added by this publication. Link disclosure

Managed WordPress

Kinsta

Consider it for
Use Kinsta Single 20GB, listed at $35/month, when a WordPress course site needs published PHP-capacity controls and a staging rehearsal.
Starting cost
Single 20GB listed $35/month on 2026-09-15; first-month promotions and annual billing change the cash invoice
Before you choose
Do not treat visitor allowances as class concurrency. No plan here is verified for a specific learner count.

Checked 15 Sep 2026 · Pricing source

View Kinsta plans

Direct provider link; no affiliate tracking added by this publication. Link disclosure

Managed WordPress

WP Engine

Consider it for
Use WP Engine Essential when the quoted plan covers your plugin stack, membership cache rules, and an isolated staging copy.
Starting cost
Essential 1-site card displayed $336/year on 2026-09-15 with promotional first-year pricing; confirm undiscounted checkout
Before you choose
Skip if you cannot rehearse membership access and payments at the destination. No measured speed advantage is claimed.

Checked 15 Sep 2026 · Pricing source

View WP Engine plans

Direct provider link; no affiliate tracking added by this publication. Link disclosure

Shared hosting

SiteGround

Consider it for
Use SiteGround StartUp, advertised at $2.99/month for a prepaid first 12 months then $17.99/month, when one WordPress site needs shared hosting rather than a server you administer.
Starting cost
StartUp first-term $2.99/mo prepaid 12 months, renews $17.99/mo on 2026-09-15 (world page: ex. VAT); 1 website, 10GB, ~10,000 visits marketing estimate
Before you choose
Visit marketing estimates are not class concurrency. Renewal is $17.99/mo after the prepaid term. No course-capacity test is claimed.

Checked 15 Sep 2026 · Pricing source

View SiteGround plans

Affiliate link: we may earn a commission on an eligible purchase. Link disclosure

Cloud VMs

DigitalOcean

Consider it for
Use a DigitalOcean Droplet when you will administer a cloud VM, not when you need managed WordPress hosting.
Starting cost
Basic Droplet listed from $4.00/mo (512 MiB, 1 vCPU, 10 GiB SSD, 500 GiB transfer) on 2026-09-15; 3 static sites listed free. Confirm live checkout and bandwidth overage.
Before you choose
A listed VM size is not a WordPress or course-capacity claim. You own OS, updates, backups, and mail unless a separate product says otherwise. The static-site free tier is not a course site.

Checked 15 Sep 2026 · Pricing source

View DigitalOcean plans

Direct provider link; no affiliate tracking added by this publication. Link disclosure