fix(appsync): avoid N+1 DynamoDB queries in campaign total resolvers - #173
Merged
Conversation
Campaign.totalOrders now uses a DynamoDB Query with Select=COUNT instead of loading every order item just to count them, and Campaign.totalRevenue projects only totalAmount instead of fetching full order items before summing in VTL. This removes the large per-campaign item payloads behind the N+1 field resolvers. Denormalizing totals onto the campaign item was considered and deliberately ruled out as too broad for this fix. Closes #144
dmeiser
force-pushed
the
fix/144-campaign-totals-count
branch
from
August 24, 2026 09:44
286afd1 to
333b2cf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Resolve kernelworx issue #144: Campaign.totalOrders and Campaign.totalRevenue AppSync field resolvers issued N+1 DynamoDB queries that loaded all order items per campaign (20 campaigns could trigger 40 large queries). Fix: totalOrders uses Select: COUNT; totalRevenue projects only totalAmount. Smallest correct fix per the issue's recommendation; denormalizing totals onto the campaign item was explicitly considered and ruled out as too broad a refactor for this PR. Closes #144.
What Changed
Campaign.totalOrdersAppSync resolver to requestSelect: COUNTand return$ctx.result.count, eliminating the need to load every order item just to count them.Campaign.totalRevenueAppSync resolver to project onlytotalAmount, so DynamoDB returns the minimal attributes needed to sum revenue instead of full order records.docs/SCHEMA.mdto documentCampaign.totalOrdersandCampaign.totalRevenueas computed on-demand from theORDERtable rather than denormalized fields.Risk Assessment
✅ Low: Minimal, behavior-preserving fix that correctly uses DynamoDB COUNT and projection with no new defects introduced.
Testing
Validated the fix by rendering the real AppSync VTL templates and confirming totalOrders uses Select: COUNT + ctx.result.count and totalRevenue projects totalAmount + sums it, with a baseline comparison showing the pre-fix templates did neither. Existing JS resolver unit tests pass. Could not run the live AppSync integration test because AWS credentials are expired.
Evidence: Focused VTL verification script
Evidence: Focused VTL verification log
=== Current totalOrders request template === ✓ operation is Query ✓ query expression references campaignId ✓ expressionValues contain typed campaignId ✓ select is COUNT === Current totalOrders response template === ✓ returns ctx.result.count when count is provided ✓ ignores items and returns 0 when count is 0 ✓ propagates errors === Current totalRevenue request template === ✓ operation is Query ✓ query expression references campaignId ✓ expressionValues contain typed campaignId ✓ projection expression is totalAmount only === Current totalRevenue response template === ✓ sums totalAmount from projected items ✓ returns 0 for no items === Baseline (pre-fix) templates from base commit === ✓ baseline totalOrders request did NOT use COUNT ✓ baseline totalOrders response used items.size() (2) ✓ baseline totalRevenue request did NOT project totalAmount All focused VTL checks passed.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed ✅
tofu/application/appsync/mapping-templates/campaign_total_orders_response.vtl:4- campaign_total_orders_response.vtl uses$ctx.result.scannedCountinstead of$ctx.result.count. The sibling JS resolvercount_user_shared_campaigns_fn.jsusesctx.result.countfor the sameselect: 'COUNT'pattern. DynamoDB'sscannedCountcounts items evaluated before any filter expression, whilecountcounts items matching the query condition. With no filter expression they are equal today, so behavior is currently correct, butscannedCountis semantically wrong and will return inflated totals if a filter expression is added later. Change line 4 to$ctx.result.count.tofu/application/appsync/mapping-templates/campaign_total_orders_request.vtl:1- Both totalOrders and totalRevenue resolvers still evaluate only the first page of DynamoDB Query results (nonextTokenhandling). Campaigns with more than 1 MB of orders will continue to return truncated totals. This is the pre-existing pagination gap the issue explicitly accepts by ruling out denormalization onto the campaign item, so it is authorized containment rather than a new defect.🔧 Fix: Use ctx.result.count for totalOrders COUNT query
✅ Re-checked - no issues remain.
tests/integration/resolvers/campaignQueries.integration.test.ts:1092- The live AppSync/DynamoDB integration test that exercises Campaign.totalOrders and Campaign.totalRevenue end-to-end (tests/integration/resolvers/campaignQueries.integration.test.ts) could not be run because the AWS session has expired (aws sts get-caller-identity failed). The focused VTL verification below validates the actual resolver templates, but it does not exercise the deployed AppSync API or real DynamoDB behavior. If you want full end-to-end confirmation, re-authenticate AWS and run the integration test.node /tmp/no-mistakes-evidence/01M0RHMVFJR56J0KV6D59A5577/test_campaign_totals_vtl.mjs (focused VTL verification of the changed templates and baseline comparison)npm run test:js-resolvers (baseline JS resolver unit suite)aws sts get-caller-identity (environment check; failed due to expired session)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.