本文へ移動
cccskills
無料GitHub で公開

denormalized-priority-column-staleness

Fix 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.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md4.8 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Denormalized Priority Column Staleness

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 DESC doesn'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:

  1. Initial data migration didn't populate them
  2. Backfill scripts only ran for some records
  3. New source records were added without updating the denormalized column
  4. 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 LAST in 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

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Fix ArgoCD ExternalSecret deployment failing with "namespace X is not permitted in project Y". Use when: (1) ExternalSecret shows OutOfSync in ArgoCD but won't sync, (2) ArgoCD application status shows "namespace X is not permitted in project 'infrastructure'", (3) ExternalSecret targets a namespace managed by a different ArgoCD project, (4) Using apps-of-apps pattern with separate infrastructure and application projects.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

Art direction for any content — reads text, PDF, Word, HTML, PPT, then proposes 2-3 creative directions with photography style, mood, and visual language. After selection, generates AI image prompts and visual briefs section-by-section. Use when the user shares content and needs visual direction, image sourcing, or creative direction for any material.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

Fix "Null check operator used on a null value" errors when an object is set to null during an async await. Use when: (1) Object reference is nullified while awaiting, (2) Code accesses object with ! after await returns, (3) Cancel/dispose operations run concurrently with async operations on same object. Solution: capture local reference before await.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

Add custom metadata headers (x-amz-meta-*) to AWS v4 signed requests for GCS S3-compatible API. Use when: (1) Adding custom metadata to GCS uploads via S3 API, (2) Getting signature mismatch errors after adding new headers, (3) x-amz-meta-* headers being ignored or causing 403 errors. Custom headers MUST be included in canonical headers and signed headers list.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

Fix password/secret authentication failures caused by trailing newlines when creating Google Cloud secrets (or similar) with bash here-strings. Use when: (1) Password authentication fails with correct password, (2) Secret created with `<<< "value"` syntax, (3) Error like "password authentication failed" or "invalid token" despite correct value. Bash here-strings (`<<<`) add a trailing newline that corrupts secrets.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

Fix silent video/media processing failures caused by URL extraction code that filters on file extensions (.mp4, .webm, .webp). Use when: (1) Media moderation, transcoding, or analysis silently skips files from Blossom or content-addressed storage servers, (2) URL extraction from Nostr event tags (imeta, r tags) drops URLs without recognized extensions, (3) CDN fallback URLs append .mp4 but the actual server uses extensionless content-addressed paths like /{sha256}. Common in Nostr video events (kind 34236) where different clients use different URL formats.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

divinevideo のスキルをすべて見る

このスキルの問題を報告する