Skip to content

Instantly share code, notes, and snippets.

@Esya
Created April 8, 2026 09:56
Show Gist options
  • Select an option

  • Save Esya/b5cfe0238ce99d0d4e8f9c43c0ed138d to your computer and use it in GitHub Desktop.

Select an option

Save Esya/b5cfe0238ce99d0d4e8f9c43c0ed138d to your computer and use it in GitHub Desktop.
Portfolio snapshot bug investigation - OpenSearch/ProductView price sync gap

Portfolio Snapshot Bug - Investigation Findings

Date: 2026-04-08 Affected user (example): bart6446 (bartoffermans@gmail.com, userId: 6876c28bc2fa8516b1a41cf4) Impact: Portfolio value dropped from ~46.7k to ~41k (~12% loss)


TL;DR

The originally suspected root cause (PriceBulkExtractorService missing fallback) was already fixed on April 3rd (commit beea7c2fa). The actual bug is that OpenSearch inventory documents have stale priceEu values that don't match the (correct) ProductView data. The break is in the ProductView -> OpenSearch inventory sync.


Evidence: Data Comparison Across Layers

Example: "Horizon of Progress" (Standard) - productId 68502df359fa72400955744d

Layer priceEu Source
prices collection marketValue=0, low=8.57 db.prices.find({productId, isLatest: true})
ProductView (MongoDB) 8.57 Fallback correctly applied (low)
OpenSearch (inventory_v5) 0 Stale/incorrect

Example: "Exterminator Magmarch" (Standard) - productId 68502d0459fa72400955081a

Layer priceEu Source
prices collection marketValue=0, low=0.20 db.prices.find({productId, isLatest: true})
ProductView (MongoDB) 0.20 Fallback correctly applied (low)
OpenSearch (inventory_v5) 0 Stale/incorrect

Scale

For bart6446 alone: 247 inventory items have priceEu=0 in OpenSearch while owning quantity > 0. Many of these likely have valid prices in ProductView.


What's Working Correctly

  1. PriceBulkExtractorService (incremental price path) - Fixed April 3rd, uses getEffectivePrice() with full fallback chain (marketValue -> low -> mid -> high)
  2. createProductNexusPriceStages() (full ProductView refresh path) - Always had the fallback via createPriceFallbackExpression() in MongoDB aggregation
  3. ProductView documents - Currently contain correct priceEu values with fallback applied

Where the Break Is

The pipeline: ProductView (MongoDB) -> OpenSearch inventory documents

Three code paths write to OpenSearch inventory:

Path File Trigger
Partial update inventory-product-update.handler.ts product-view.refreshed event
Full reindex inventory-reindex-migration.service.ts Schema version bump
Full indexing inventory-document-assembler.service.ts Initial indexing

All three read productView.pricesByFinish[finish].priceEu and write it to marketData.priceEu in OpenSearch.

Likely Scenario

On April 7th, the inventory schema version was bumped to v3 (commits adding expansionId to inventory index). This triggered a full reindex of all inventory documents. During this reindex:

  • Either the ProductView didn't have the correct fallback values at that moment (timing/race condition with ProductView refresh)
  • Or the reindex completed, and a subsequent price import wrote priceEu: 0 via the partial update path before the ProductView was refreshed with fallback values

After the reindex, the partial update path (product-view.refreshed event) should have corrected the values when ProductView was next refreshed - but apparently it didn't propagate to all 247+ items.


Immediate Fix

Trigger a targeted OpenSearch inventory refresh for affected documents. The ProductView data is already correct - we just need to sync it to OpenSearch.

Options:

  1. Re-run the inventory reindex migration (heavy but thorough)
  2. Trigger product-view.refreshed events for products with pricesByFinish[finish].priceEu > 0 where OpenSearch has marketData.priceEu = 0

Root Cause to Investigate Further

Why didn't the partial update path (inventory-product-update.handler.ts) correct OpenSearch after ProductView was refreshed? Possible causes:

  1. Event not emitted - ProductView refresh didn't fire product-view.refreshed for these products
  2. Event lost - The event was emitted but the handler didn't process it (message queue issue?)
  3. Scroll/pagination gap - The handler uses OpenSearch scroll to find matching inventory docs; some may have been skipped
  4. Race condition - A subsequent operation overwrote the corrected values

Files Referenced

  • packages/backend/domains/pricing/src/price-bulk-extractor.service.ts (lines 128-129) - Price extraction with fallback (already fixed)
  • packages/core/db/src/utils/price-helpers.ts (lines 152-161) - getEffectivePrice() function
  • packages/core/db/src/pipeline-builder/lookup.helper.ts (lines 74-173) - createProductNexusPriceStages()
  • packages/backend/features/search/src/inventory/inventory-partial-assembler.service.ts (lines 102-112) - Partial update builder
  • packages/backend/domains/inventory/src/sync/handlers/inventory-product-update.handler.ts - Event-driven sync
  • packages/backend/domains/inventory/src/sync/inventory-reindex-migration.service.ts (lines 460-519) - Full reindex
  • packages/backend/features/search/src/transforms/inventory-transform.ts (lines 223-233) - OpenSearch document transform
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment