Skip to content

perf(db): add quiz_attempts indexes + fix advanced HTML question#114

Merged
ViktorSvertoka merged 7 commits into
developfrom
sl/feat/quiz
Jan 8, 2026
Merged

perf(db): add quiz_attempts indexes + fix advanced HTML question#114
ViktorSvertoka merged 7 commits into
developfrom
sl/feat/quiz

Conversation

@LesiaUKR
Copy link
Copy Markdown
Collaborator

@LesiaUKR LesiaUKR commented Jan 8, 2026

Closes #102

Week 4

Database Performance Optimization

Reduces Neon CU-hours usage for production workloads.

Changes

  • Added B-tree indexes for quiz_attempts table:
    • quiz_attempts_user_id_idx - optimizes Dashboard queries
    • quiz_attempts_quiz_id_idx - optimizes quiz statistics queries
  • Updated HTML advanced quiz h1-h6 question for technical accuracy

Performance Impact

Query Type Before After
Dashboard (user attempts) Full table scan Index scan
Quiz statistics Full table scan Index scan
At scale (10k+ rows) ~10-50x faster -

Migration

0013_warm_dexter_bennett.sql - applies indexes via Drizzle

Summary by CodeRabbit

  • Documentation
    • Updated HTML quiz explanations to reflect current SEO best practices for heading usage, clarifying that one h1 per page is recommended while acknowledging HTML5's flexibility for multiple headings within sections.

✏️ Tip: You can customize this high-level summary in your review settings.

• Quiz performance:
  - Eliminate N+1 queries in getQuizQuestions by batch-loading answers
  - Optimize submitQuizAttempt with inArray-based batch verification
  - Reduce quiz page load from 21 queries to 2
  - Add seed to randomizeQuizQuestions for stable randomization
  - Handle empty answers array and validate answer–question matching

• Points system refactor (ledger-only):
  - Migrate to ledger-based points model (point_transactions as source of truth)
  - Remove users.points column and db.transaction() usage (neon-http compatible)
  - Store full attempt score in quiz_attempts.pointsEarned
  - Award only delta vs previous best attempt
  - Compute total points via SUM(point_transactions.points)
  - Optimize previous best lookup using MAX(pointsEarned)

• Quiz content & seeding:
  - Add seed scripts for HTML, CSS, Git, JavaScript, TypeScript quizzes
  - Introduce advanced and fundamentals quiz trackse

• UI & logic fixes:
  - Add pointsAwarded prop to QuizResult
  - Show "+N points added" or "no points (result not improved)"
  - Fix Dashboard to rely on ledger total

• Leaderboard & DB performance:
  - Update leaderboard queries to LEFT JOIN + GROUP BY + SUM
  - Add index for point_transactions (user_id)
  - Apply drizzle migrations and snapshots

Benefits:
- Faster quiz loads and lower DB pressure
- Neon-compatible (no transactions)
• Add quiz_attempts_user_id_idx for Dashboard queries optimization
• Add quiz_attempts_quiz_id_idx for quiz statistics queries
• Generate migration 0013_warm_dexter_bennett.sql
• Reduces full table scans, improves Neon CU-hours usage

• Fix HTML advanced quiz h1-h6 SEO question for technical accuracy
  - Clarify HTML5 allows multiple h1 in sections (one recommended for SEO)

Performance impact: Dashboard queries use index scan instead of full table scan
@netlify
Copy link
Copy Markdown

netlify Bot commented Jan 8, 2026

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit 0e5802e
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/695fbbb55df3670008008344
😎 Deploy Preview https://deploy-preview-114--develop-devlovers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

This PR adds two database indexes (userIdIdx and quizIdIdx) to the quiz_attempts table to optimize query filtering by user and quiz ID. It includes the TypeScript schema definition, SQL migration file, and corresponding metadata updates. Additionally, quiz content is updated to revise h1 SEO guidance, and the order_items schema is enhanced with variant selection fields.

Changes

Cohort / File(s) Summary
Quiz Attempts Indexes
frontend/db/schema/quiz.ts, frontend/drizzle/0013_warm_dexter_bennett.sql, frontend/drizzle/meta/0013_snapshot.json, frontend/drizzle/meta/_journal.json
Added two new B-tree indexes on quiz_attempts table: one on user_id and one on quiz_id. Schema definition, SQL migration, snapshot metadata, and journal entry all updated to reflect new indexes.
Order Items Schema Enhancement
frontend/drizzle/meta/0013_snapshot.json
Added selected_size and selected_color columns to order_items table; renamed unique index from order_items_order_product_uq to order_items_order_variant_uq to include the new variant fields.
Quiz Content Update
json/quizzes/html/advanced/html-advanced-quiz-part3.json
Revised h1-h6 SEO guidance explanations (en, uk, pl locales) to recommend one h1 per page while acknowledging HTML5 allows multiple within sections; adjusted answer option text to align with updated guidance.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • AM1007
  • ViktorSvertoka

Poem

🐰 Hopping through queries so fast and so free,
New indexes on quiz_attempts—indexed with glee!
User and quiz lookups now swift as can be,
CU-hours saved, databases happy, hooray! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The pull request includes an out-of-scope change: updates to HTML quiz content about h1-h6 guidance are not related to the database optimization objectives in issue #102. Separate the HTML quiz content update into a distinct pull request, keeping this PR focused on the database performance optimizations specified in issue #102.
Title check ❓ Inconclusive The pull request title 'Sl/feat/quiz' is vague and generic, using a branch name format rather than describing the actual changes (database indexes and quiz content updates). Revise the title to clearly describe the main changes, such as 'Add database indexes on quiz_attempts table' or 'Optimize quiz queries with indexes and update h1-h6 guidance'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The pull request successfully adds missing B-tree indexes on quiz_attempts (user_id and quiz_id) to optimize frequently-used queries, directly addressing the scope of issue #102.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @frontend/drizzle/meta/0013_snapshot.json:
- Around line 1371-1384: The snapshot shows order_items additions (columns
selected_size and selected_color and updated unique index
order_items_order_variant_uq) but the migration file
0013_warm_dexter_bennett.sql only contains quiz_attempts index DDL; reconcile by
either (A) adding the missing ALTER TABLE ... ADD COLUMN ... NOT NULL DEFAULT ''
statements and the ALTER INDEX/CREATE UNIQUE INDEX/update for
order_items_order_variant_uq into 0013_warm_dexter_bennett.sql (ensuring SQL
matches the snapshot column types/defaults and index definition), or (B) if
those order_items changes belong to a different migration/branch, regenerate or
revert frontend/drizzle/meta/0013_snapshot.json so it matches the actual
migrations (remove selected_size/selected_color and index changes), and run the
ripgrep checks suggested (search for selected_size/selected_color and ALTER
TABLE order_items) to confirm no stray DDL is missing or duplicated; update the
migration or snapshot accordingly and re-run schema generation/tests.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe5ae23 and 0e5802e.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • frontend/db/schema/quiz.ts
  • frontend/drizzle/0013_warm_dexter_bennett.sql
  • frontend/drizzle/meta/0013_snapshot.json
  • frontend/drizzle/meta/_journal.json
  • json/quizzes/html/advanced/html-advanced-quiz-part3.json
🔇 Additional comments (5)
json/quizzes/html/advanced/html-advanced-quiz-part3.json (1)

224-259: LGTM! Technically accurate SEO guidance update.

The revised h1-h6 guidance correctly balances HTML5 specifications (which allow multiple h1 elements within sectioning elements) with SEO best practices (recommending one h1 per page). The updated explanations and answer options across all three locales are consistent and accurate.

frontend/drizzle/meta/_journal.json (1)

96-102: LGTM! Standard migration journal entry.

The journal entry is correctly formatted and properly sequenced after migration 0012.

frontend/db/schema/quiz.ts (1)

152-153: Confirm necessity of single-column indexes given existing composite indexes.

The quizAttempts table already has composite indexes with these columns as leading keys:

  • Line 154: userCompletedAtIdx on (userId, completedAt)
  • Line 158: quizPercentageCompletedAtIdx on (quizId, percentage, completedAt)
  • Line 161: quizIntegrityScoreIdx on (quizId, integrityScore)

PostgreSQL B-tree indexes can efficiently handle queries filtering only on the leading column of a composite index. Therefore, queries filtering solely on userId or quizId should already benefit from the existing composite indexes without needing dedicated single-column indexes.

While there are edge cases where single-column indexes may offer marginal benefits, they come with tradeoffs:

  • Increased storage and memory footprint
  • Additional maintenance overhead on INSERT/UPDATE operations
  • Higher Neon CU consumption (counterproductive to PR goal of reducing CU-hours)

Please verify that actual query patterns require these dedicated indexes and that the performance gains outweigh the costs.

frontend/drizzle/meta/0013_snapshot.json (1)

576-605: Approve quiz_attempts indexes in snapshot.

The snapshot correctly reflects the two new B-tree indexes on quiz_attempts (user_id and quiz_id) that match the migration SQL file and schema declarations.

Note: Please see separate comment regarding potential index redundancy with existing composite indexes.

frontend/drizzle/0013_warm_dexter_bennett.sql (1)

1-2: Verify these indexes aren't redundant with existing composite indexes before deploying.

The quiz_attempts table already has composite indexes with these leading columns:

  • userCompletedAtIdx on (user_id, completed_at)
  • quizPercentageCompletedAtIdx on (quiz_id, percentage, completed_at)
  • quizIntegrityScoreIdx on (quiz_id, integrity_score)

In PostgreSQL, B-tree composite indexes efficiently serve queries filtering only on the leading column. Queries like getUserQuizStats() and getUserQuizzesProgress() filter solely on user_id and should already avoid full table scans using userCompletedAtIdx. Similarly, getQuizLeaderboard() filters on quiz_id with integrity_score and can use quizIntegrityScoreIdx.

Adding dedicated single-column indexes introduces storage overhead, insert/update maintenance cost, and increased Neon CU consumption—contrary to the PR's goal to reduce CU-hours. Verify with PostgreSQL query plans that these new indexes provide measurable benefit over the existing composite indexes.

Comment on lines +1371 to +1384
"selected_size": {
"name": "selected_size",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"selected_color": {
"name": "selected_color",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "''"
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Snapshot includes order_items schema changes not present in migration SQL.

This snapshot shows new columns in the order_items table:

  • selected_size (text, not null, default '')
  • selected_color (text, not null, default '')

And an updated unique index order_items_order_variant_uq that includes these new columns (lines 1450-1476).

However, the migration file frontend/drizzle/0013_warm_dexter_bennett.sql contains ONLY the quiz_attempts index creation statements—no ALTER TABLE commands for order_items.

This inconsistency means:

  • Applying migration 0013 will create quiz indexes but NOT the order_items columns
  • The schema/snapshot will be out of sync with the actual database state
  • Subsequent migrations or schema introspection may fail

Please verify:

  1. Are the order_items changes supposed to be in this migration? If so, the SQL file is incomplete.
  2. Are they from a different migration? If so, the snapshot may be incorrectly generated.
  3. Was this snapshot accidentally created from a different branch or state?
#!/bin/bash
# Check if order_items schema changes exist in any migration files

echo "=== Searching for order_items ALTER statements in migration files ==="
rg -n "ALTER TABLE.*order_items|selected_size|selected_color" frontend/drizzle/*.sql

echo -e "\n=== Check if order_items schema in TypeScript includes these columns ==="
rg -n "selected_size|selected_color" frontend/db/schema/ -A 2 -B 2

echo -e "\n=== List all migration files to identify where order_items changes belong ==="
ls -la frontend/drizzle/*.sql

Also applies to: 1450-1476

🤖 Prompt for AI Agents
In @frontend/drizzle/meta/0013_snapshot.json around lines 1371 - 1384, The
snapshot shows order_items additions (columns selected_size and selected_color
and updated unique index order_items_order_variant_uq) but the migration file
0013_warm_dexter_bennett.sql only contains quiz_attempts index DDL; reconcile by
either (A) adding the missing ALTER TABLE ... ADD COLUMN ... NOT NULL DEFAULT ''
statements and the ALTER INDEX/CREATE UNIQUE INDEX/update for
order_items_order_variant_uq into 0013_warm_dexter_bennett.sql (ensuring SQL
matches the snapshot column types/defaults and index definition), or (B) if
those order_items changes belong to a different migration/branch, regenerate or
revert frontend/drizzle/meta/0013_snapshot.json so it matches the actual
migrations (remove selected_size/selected_color and index changes), and run the
ripgrep checks suggested (search for selected_size/selected_color and ALTER
TABLE order_items) to confirm no stray DDL is missing or duplicated; update the
migration or snapshot accordingly and re-run schema generation/tests.

Copy link
Copy Markdown
Member

@ViktorSvertoka ViktorSvertoka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job!

@ViktorSvertoka ViktorSvertoka merged commit 9bbcd82 into develop Jan 8, 2026
9 checks passed
@ViktorSvertoka ViktorSvertoka deleted the sl/feat/quiz branch January 8, 2026 14:21
@LesiaUKR LesiaUKR restored the sl/feat/quiz branch January 8, 2026 15:08
@LesiaUKR LesiaUKR changed the title Sl/feat/quiz perf(db): add quiz_attempts indexes + fix advanced HTML question Jan 8, 2026
liudmylasovetovs pushed a commit that referenced this pull request Jan 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(SP: 1) [Database] Optimize Neon usage to reduce CU-hours

2 participants