Context & Purpose
Generate a large volume of realistic check-in, registration, small group attendance, and event data over a 4-month historical period (October 2025 – January 2026). The purpose is to demonstrate the JSONB archival system's efficiency by comparing table sizes before and after running the weekly archival script.
The data is pushed to production for a live demo of database size reduction — showing how the automated archival system compresses tens of thousands of relational rows into compact JSONB arrays.
archive_old_data.sql script deletes registrations (no JSONB archival) —
it treats them as transient intent data. For the demo, this is fine: we show registrations being cleaned
up alongside checkins being archived to JSONB. The size reduction from registration deletion is part of the story.
Current State (Pre-Generation)
- Children's ministry groups exist (K-5th grade, EC rooms, High School, Middle School) but have 0 member rosters
- Only 7 children identified (relationship_id=2) — insufficient for realistic demo data
- All data generation uses existing people from the database; no new people or families created
ID Ranges (Collision Avoidance)
All generated data uses reserved ID ranges to avoid collisions with existing production data.
The script is idempotent — it includes DELETE WHERE id BETWEEN X AND Y cleanup at
the top for safe re-runs.
| Entity | ID Range | Notes |
|---|---|---|
| Events | 10001 – 11000 | Well above existing sequence max of 205 |
| Group memberships | 800001 – 899999 | Below existing sequence at 900045 |
| Church groups (new) | 6100 – 6130 | Above existing utility group IDs at 6001-6002 |
| Small groups (new) | 100 – 130 | Above existing max of 6 |
Step 1: Populate Children's Ministry Groups
Assign existing people as "children" into grade-level groups. Since we cannot identify real children (no DOB data), we pick people who aren't already in many groups and distribute them across 19 ministry groups.
| Group | Group ID | Target Members |
|---|---|---|
| Elementary - Kindergarten | 57 | 80 |
| Elementary - 1st Grade | 58 | 80 |
| Elementary - 2nd Grade | 60 | 80 |
| Elementary - 3rd Grade | 61 | 80 |
| Elementary - 4th Grade | 62 | 80 |
| Elementary - 5th Grade | 63 | 80 |
| Early Childhood - Ages 0-18mo | 409 | 40 |
| Early Childhood - Ages 19-25mo | 55 | 40 |
| Early Childhood - Ages 26-30mo | 59 | 40 |
| Early Childhood - Ages 3yr | 64 | 40 |
| Early Childhood - Ages 4-5yr | 66 | 40 |
| Early Childhood - Ages 31-35mo | 67 | 40 |
| Middle School - 6th Grade | 305 | 60 |
| Middle School - 7th Grade | 306 | 60 |
| Middle School - 8th Grade | 307 | 60 |
| High School - 9th Grade | 312 | 70 |
| High School - 10th Grade | 311 | 70 |
| High School - 11th Grade | 313 | 70 |
| High School - 12th Grade | 314 | 70 |
Total: ~1,260 memberships across children's ministry groups.
Step 2: Create Sunday Events (~102 events)
For each Sunday from October 5, 2025 through January 25, 2026 (17 Sundays), create 6 event types:
| Event Type | Time | Group ID | Location | Events |
|---|---|---|---|---|
| Children | 9:00 AM | 5003 (Children's Dept) | Main Sanctuary | 17 |
| Children | 11:00 AM | 5003 (Children's Dept) | Main Sanctuary | 17 |
| High School Sunday | 9:00 AM | 81 (High School) | High School Room | 17 |
| High School Sunday | 11:00 AM | 81 (High School) | High School Room | 17 |
| General Service | 9:00 AM | none | Main Sanctuary | 17 |
| General Service | 11:00 AM | none | Main Sanctuary | 17 |
Total: 102 Sunday events
Step 3: Create Weekday Events (~56 events)
| Event Type | Day | Time | Group ID | Events |
|---|---|---|---|---|
| Wednesday Middle School | Wednesday | 6:30 PM | 5012 (Middle School Dept) | 17 |
| Wednesday High School | Wednesday | 7:00 PM | 81 (High School) | 17 |
| Friday Youth | Friday | 7:00 PM | 81 (High School) | 17 |
| Middle School Winter Camp | — | — | 5012 (Middle School Dept) | 1 |
| High School Winter Retreat | — | — | 81 (High School) | 1 |
| VBS Day 1-3 | — | — | 5003 (Children's Dept) | 3 |
Total: 56 weekday/special events
Step 4: Create Check-In Records (~52,000+)
Check-in records are the primary bulk data generator. Each record includes a security code
(random Letter-Number-Letter-Number pattern) and a checked_in_at timestamp offset
randomly from the event start time (0–45 minutes).
| Event Category | Events | Attendance Rate | Est. Checkins |
|---|---|---|---|
| Sunday Children's (9am + 11am) | 34 | ~90% of K-5/EC members | ~30,600 |
| Sunday High School (9am + 11am) | 34 | ~80% of High School members | ~6,800 |
| Wednesday Middle School | 17 | ~78% of Middle School members | ~2,380 |
| Wednesday + Friday High School | 34 | ~72-85% of High School members | ~6,120 |
| Sunday General Service | 34 | ~200 per service | ~6,800 |
Estimated total: ~52,700 checkins
random() function ensures natural attendance variation between events. No two services
have identical attendance, mimicking real-world patterns where attendance fluctuates week to week.
Step 5: Create Registrations (~8,000)
Registrations represent intent to attend, created for various event categories:
- Middle School Winter Camp — all Middle School 6th/7th/8th grade members (~180 registrations)
- High School Winter Retreat — all High School 9th-12th grade members (~280 registrations)
- Sunday Children's events — K-5 members for 4 selected Sundays (~1,920 registrations)
- Sunday High School events — High School members for 8 selected Sundays (~2,240 registrations)
- Wednesday Middle School events — Middle School members for 8 selected weeks (~1,440 registrations)
- Miscellaneous — additional event registrations (~1,940 registrations)
Estimated total: ~8,000 registrations
Step 6: Create Small Groups + Attendance (~3,500 records)
Expand Existing Small Groups
Add ~12 more members to each of the 4 existing small groups (IDs 3051, 3105, 3075, 3090) and create 17 weeks of attendance at 95% rate. Total: 68 attendance records.
15 New Small Groups
| # | Name | Members | Meeting Day |
|---|---|---|---|
| 1 | Men's Thursday AM | 18 | Thursday |
| 2 | Women's Tuesday PM | 16 | Tuesday |
| 3 | Couples - Friday Night | 14 | Friday |
| 4 | Men's Iron Sharpens Iron | 12 | Wednesday |
| 5 | Women's Heart to Heart | 15 | Monday |
| 6 | Mixed - Sunday Night | 20 | Sunday |
| 7 | Men's Breakfast Club | 14 | Saturday |
| 8 | Women's Prayer Warriors | 12 | Wednesday |
| 9 | Couples - Date Night | 16 | Friday |
| 10 | Young Adults Bible Study | 18 | Tuesday |
| 11 | Empty Nesters | 10 | Thursday |
| 12 | Men's Adventure Group | 14 | Saturday |
| 13 | Women's Book Club | 12 | Monday |
| 14 | Neighborhood Connect | 16 | Wednesday |
| 15 | Faith & Finance | 10 | Thursday |
15 groups × ~15 avg members × 17 weeks × 85% attendance = ~3,250 attendance records
Plus 4 existing groups × 17 weeks = 68 records
Total: ~3,318 small_group_attendance records
Step 7: Baseline JSONB Event Attendance
Create a small number of event_attendance JSONB records for a handful of older events.
These represent "already archived" data that serves as a comparison baseline, showing the JSONB format
alongside the relational data before archival runs.
Expected Record Counts
| Table | Records | Type | Purpose |
|---|---|---|---|
checkins | ~52,000 | Hot | Relational data → archived to event_attendance JSONB |
registrations | ~8,000 | Hot | Relational data → deleted by archival |
small_group_attendance | ~3,300 | JSONB | Already in JSONB format (archival comparison) |
event_attendance | ~50 | JSONB | Pre-seeded JSONB cold storage baseline |
group_membership | ~4,500 | New | Children's ministry + small group members |
events | ~170 | New | Sunday + weekday events over 4 months |
Total new records: ~68,000+
Actual Results
The script was executed on the local database on February 23, 2026. The actual results exceeded the planned targets:
Archival Demo Results
After generating the data, the archive_old_data.sql script was run with a 2-month cutoff.
Events from October and November 2025 (older than 2 months from the script's run date) were archived.
Archival Operation Counts
Table Size Comparison
Before Archival
| Table | Size | Rows |
|---|---|---|
checkins | 5,528 KB | 61,927 |
registrations | 896 KB | 9,584 |
event_attendance | 112 KB | 3 |
small_group_attendance | 200 KB | 323 |
group_membership | 2,472 KB | 18,395 |
After Archival
| Table | Size | Rows |
|---|---|---|
checkins | 1,624 KB | 18,703 |
registrations | 120 KB | 460 |
event_attendance | 1,728 KB | 108 |
small_group_attendance | 200 KB | 323 |
group_membership | 2,472 KB | 18,395 |
Size Reduction Summary
| Table | Before | After | Reduction |
|---|---|---|---|
checkins |
5,528 KB | 1,624 KB | -71% (3,904 KB saved) |
registrations |
896 KB | 120 KB | -87% (776 KB saved) |
event_attendance rows.
That's a 412:1 row reduction ratio. The JSONB format stores only the essential data
(which people attended which event) without the per-row overhead of security codes, timestamps, and index entries.
What Happened to the Data
Oct-Nov events
event_attendance
attendee_ids arrays
Oct-Nov events
No archival needed
Demo Workflow
Steps to reproduce the archival demonstration:
1. Snapshot Pre-Archival Sizes
SELECT relname, pg_size_pretty(pg_total_relation_size(relid))
FROM pg_stat_user_tables
WHERE relname IN ('checkins', 'registrations', 'event_attendance',
'small_group_attendance', 'group_membership');
2. Run Archival Script
# On production (Linux, cron would run this automatically)
sudo -u postgres psql -d myapp -f scripts/archive_old_data.sql
# On local Windows dev machine
PGPASSWORD=nbka4ko "/c/Program Files/PostgreSQL/18/bin/psql.exe" \
-U postgres -d myapp -f scripts/archive_old_data.sql
3. Snapshot Post-Archival Sizes
Run the same size query again and compare the results.
4. Count Records Moved
SELECT COUNT(*) FROM event_attendance; -- new JSONB rows
SELECT COUNT(*) FROM checkins; -- should be reduced
SELECT COUNT(*) FROM registrations; -- should be reduced
Verification Steps
- Run the data generation script on local DB
- Query record counts for each table
- Run
archive_old_data.sqland verify checkins/registrations from Oct-Nov are cleaned up - Verify event_attendance JSONB rows were created with correct
attendee_idsarrays - Compare table sizes before/after with
pg_total_relation_size() - Push to production, repeat size comparison there
Files Modified
| File | Status | Description |
|---|---|---|
scripts/generate_checkin_demo_data.sql |
New | Idempotent SQL script — generates all demo data in a single transaction |
scripts/archive_old_data.sql |
Existing | Weekly archival script — used as-is for the demo |