Denormalized Priority Column Staleness
SkillMonitoring & opsFix incorrect priority ordering when using denormalized aggregate columns. Use when: (1) Records are processed in wrong order despite ORDER BY on count/sum columns, (2) Top items by some metric aren't being selected first, (3) Aggregate columns show 0 or NULL for records that should have high values, (4) Priority queue processes low-value items before high-value ones. The root cause is often that denormalized columns (vine_count, loop_count, total_orders, etc.) weren't backfilled or maintained properly. Solution: JOIN with source tables to compute actual aggregates at query time.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Denormalized Priority Column Staleness skill
What this skill tells your AI
The instructions your AI receives, as published by divinevideo/divine-mobile in .agents/skills/denormalized-priority-column-staleness/SKILL.md and read by ahel’s review.
Problem
When ordering records by denormalized aggregate columns (like total_loops, vine_count,
order_count), the query returns items in the wrong priority order because the
denormalized values are stale, unpopulated, or incorrect.
Context / Trigger Conditions
- A batch job processes items in unexpected order
- Top items (by some aggregate metric) are processed last or skipped
ORDER BY aggregate_column DESCdoesn't return expected results- Aggregate columns show 0 or NULL for records that should have high values
- Only a subset of records have the aggregate column populated
- Backfill scripts may have run for some records but not others
Root Cause
Denormalized columns (copies of aggregated data stored for query performance) can become stale when:
- Initial data migration didn't populate them
- Backfill scripts only ran for some records
- New source records were added without updating the denormalized column
- The aggregation logic changed but the column wasn't recalculated
Solution
Option 1: Compute at Query Time (Immediate Fix)
Join with the source table to compute actual aggregates:
-- BEFORE (broken): Uses potentially stale denormalized column
SELECT user_id, username
FROM users
WHERE status = 'pending'
ORDER BY total_loops DESC NULLS LAST;
-- AFTER (fixed): Computes actual aggregate from source
SELECT u.user_id, u.username,
COALESCE(SUM(vm.loops), 0) as actual_total_loops
FROM users u
LEFT JOIN vine_metadata vm ON u.user_id = vm.user_id
WHERE u.status = 'pending'
GROUP BY u.user_id, u.username
ORDER BY actual_total_loops DESC;
Option 2: Backfill the Denormalized Column (Permanent Fix)
Update the denormalized column from the source data:
UPDATE users u
SET total_loops = subq.actual_loops
FROM (
SELECT user_id, COALESCE(SUM(loops), 0) as actual_loops
FROM vine_metadata
GROUP BY user_id
) subq
WHERE u.user_id = subq.user_id;
Option 3: Use Materialized Views (Best of Both)
Create a materialized view for the aggregates:
CREATE MATERIALIZED VIEW user_stats AS
SELECT user_id,
COUNT(*) as item_count,
SUM(loops) as total_loops
FROM vine_metadata
GROUP BY user_id;
-- Refresh periodically
REFRESH MATERIALIZED VIEW user_stats;
Verification
After applying the fix, verify the query returns expected results:
-- Check that top items are actually top items
SELECT user_id, username, actual_total_loops
FROM (your_fixed_query)
LIMIT 10;
-- Compare against direct aggregate
SELECT user_id, SUM(loops) as loops
FROM source_table
GROUP BY user_id
ORDER BY loops DESC
LIMIT 10;
Example
Scenario: Avatar fetcher should process top Viners first (by total loops), but instead processes users with ID prefix "10" (effectively random order).
Investigation:
-- Check if denormalized column is populated
SELECT
COUNT(*) as total,
SUM(CASE WHEN loop_count > 0 THEN 1 ELSE 0 END) as has_loop_count
FROM users;
-- Result: Only 29,878 of 119,785 users have loop_count populated
-- Check top users by denormalized vs actual
SELECT u.user_id, u.username, u.loop_count as denormalized,
SUM(vm.loops) as actual
FROM users u
JOIN vine_metadata vm ON u.user_id = vm.user_id
GROUP BY u.user_id, u.username, u.loop_count
ORDER BY SUM(vm.loops) DESC
LIMIT 5;
-- Result: Top creators show loop_count=0 but actual=1,281,730,353
Fix: Changed the query to JOIN with vine_metadata and ORDER BY the computed sum.
Notes
- This is a classic denormalization trade-off: faster reads vs. stale data
- When denormalizing, always implement triggers or application-level updates to keep in sync
- Consider whether the aggregate query is fast enough to compute at runtime
- LEFT JOIN ensures records without source data still appear (with 0 values)
- Use
COALESCE(SUM(...), 0)to handle NULL aggregates properly NULLS LASTin ORDER BY prevents NULL values from sorting first in DESC order
Related Patterns
- Event sourcing: Keep source events, compute aggregates as needed
- CQRS: Separate read models that are explicitly updated
- Triggers: Automatically update denormalized columns on source changes
Signals
- GitHub stars
- 265
- Forks
- 55
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
denormalized-priority-column-staleness- Source
- github.com/divinevideo/divine-mobile