这次问题不是价格索引算错,而是同一个GraphQL查询被缓存后跨客户组复用了。登录批发账号先请求,随后普通客户执行完全相同的query和variables,响应里的自定义price字段仍是批发价。清缓存后顺序反过来,两个账号又都看到零售价,这基本已经说明缓存身份缺少客户上下文。

先确认哪一层在缓存

分别绕过CDN、关闭浏览器缓存,并在应用节点直连请求。给请求加唯一的调试Header,同时观察响应中的 Age、X-Cache、Surrogate-Key 或 X-Magento-Tags。不同环境Header名称不一样,但目的都是区分CDN命中和Magento内部命中。

curl -sS -D /tmp/h1.txt https://example.com/graphql \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer RETAIL_TOKEN' \
  --data '{"query":"query($sku:String!){products(filter:{sku:{eq:$sku}}){items{sku custom_price}}}","variables":{"sku":"SKU-1"}}'

如果带Authorization的响应被CDN公开缓存,这是最高优先级的问题:先调整VCL或CDN规则,确保私有请求不进入共享缓存。不要只在Resolver里补Identity,因为外层已经把完整响应共享了。

检查Resolver的缓存设计

自定义字段如果实现 IdentityInterface,只返回商品Tag往往不够。客户组价格会随customer_group_id、website_id、currency变化;这些维度不能简单塞进公共商品Tag后继续共享。更稳妥的做法是让包含个性化价格的查询不可被共享缓存,或者将这些维度明确纳入应用缓存键。

public function getIdentities(array $resolvedData): array
{
    return [Product::CACHE_TAG . "_" . $resolvedData["entity_id"]];
}
// 仅有商品Tag不能隔离客户组;需要调整缓存策略,而不是伪造一个Tag。

Magento版本、GraphQL模块和前置缓存方案不同,具体接口不完全一致。不要复制一个“customer_group_3”Tag就认为完成隔离:Tag主要用于失效,不一定参与缓存键。要确认实际cache key生成逻辑。

可执行的修复顺序

  • CDN层:Authorization或客户Cookie存在时pass,不写入公共cache
  • 应用层:个性化字段避免进入共享查询结果缓存,或把客户组、网站、币种加入真正的cache key
  • 价格读取:使用当前客户组与网站上下文的价格服务,不要直接读全局registry
  • 失效:商品价格、共享目录或客户组规则变化后,清理相应Tag

用矩阵验证,不要只测两个账号

准备游客、零售组、批发组各一个请求,再分别切换两个币种。每组连续请求两次看缓存命中,然后打乱请求顺序。

guest/CNY -> retail/CNY -> wholesale/CNY
wholesale/USD -> guest/USD -> retail/USD
再重复一次并记录响应Header
价格串号属于数据泄露风险。修复完成前宁愿让个性化GraphQL请求暂时不缓存,也不要继续用不完整的共享缓存键。

最后再看索引中的组价是否正确。若直连Magento、禁用缓存后仍返回错价,那才继续查 catalog_product_index_price 和Shared Catalog;如果直连正确、只在命中缓存时错误,就不要在价格索引上浪费时间。