Skip to content

feat(upsampling) - Support upsampled error count with performance optimizations - #1

Open
linxia0415 wants to merge 2 commits into
masterfrom
pr-3
Open

feat(upsampling) - Support upsampled error count with performance optimizations#1
linxia0415 wants to merge 2 commits into
masterfrom
pr-3

Conversation

@linxia0415

Copy link
Copy Markdown
Contributor

Test 3

yuvmen and others added 2 commits July 25, 2025 09:48
…(#94376)

Part of the Error Upsampling project:
https://www.notion.so/sentry/Tech-Spec-Error-Up-Sampling-1e58b10e4b5d80af855cf3b992f75894?source=copy_link

Events-stats API will now check if all projects in the query are
allowlisted for upsampling, and convert the count query to a sum over
`sample_weight` in Snuba, this is done by defining a new SnQL function
`upsampled_count()`.

I noticed there are also eps() and epm() functions in use in this
endpoint. I considered (and even worked on) also supporting
swapping eps() and epm() which for correctness should probably also not
count naively and use `sample_weight`, but this
caused some complications and since they are only in use by specific
dashboard widgets and not available in discover
I decided to defer changing them until we realize it is needed.
- Add 60-second cache for upsampling eligibility checks to improve performance
- Separate upsampling eligibility check from query transformation for better optimization
- Remove unnecessary null checks in upsampled_count() function per schema requirements
- Add cache invalidation utilities for configuration management

This improves performance during high-traffic periods by avoiding repeated
expensive allowlist lookups while maintaining data consistency.
@LX-CodeReview LX-CodeReview deleted a comment from code-hawk-sit Bot Apr 11, 2026
@linxia0415 linxia0415 closed this Apr 11, 2026
@linxia0415 linxia0415 reopened this Apr 11, 2026

@code-hawk-sit code-hawk-sit Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI代码审查报告

变更概览

本次 PR 涉及 8 个文件,新增 +480 行,删除 -6 行。

功能变更摘要

本 PR 新增错误上采样功能,用于在查询事件统计时自动补充低采样率的错误数据。主要实现包括核心上采样逻辑、API 端点集成、搜索数据集扩展及完整的单元测试和集成测试覆盖。

文件变更摘要

文件 变更 行数 摘要 发现问题
src/sentry/api/helpers/error_upsampling.py 新增 +140/-0 实现错误上采样的核心逻辑 3 个
src/sentry/api/endpoints/organization_events_stats.py 修改 +33/-5 集成错误上采样到组织事件统计 API 端点 1 个
tests/snuba/api/endpoints/test_organization_events_stats.py 修改 +171/-0 事件统计 API 的集成测试用例
tests/sentry/api/helpers/test_error_upsampling.py 新增 +101/-0 错误上采样功能的单元测试
src/sentry/testutils/factories.py 修改 +20/-1 增加测试数据工厂方法以支持上采样测试
src/sentry/search/events/datasets/discover.py 修改 +12/-0 扩展搜索数据集以支持上采样查询
pyproject.toml 修改 +2/-0 添加新依赖或配置项以支持错误上采样功能
sentry-repo 新增 +1/-0 新增项目配置文件

问题严重级别分布

级别 数量 占比
🟡 中危 3 75%
🟢 低危 1 25%

代表性问题(至多 10 条,按严重级别优先)

  1. 🟡 中危 src/sentry/api/endpoints/organization_events_stats.py L232: 未验证的函数返回值赋值

line 232 处调用 transform_query_columns_for_error_upsampling() 并将返回值赋给 final_columns,但未验证返…
2. 🟡 中危 src/sentry/api/helpers/error_upsampling.py L26: 缓存键哈希不稳定

line 26 处使用 Python 内置 hash() 函数生成缓存键,但 hash() 默认启用哈希随机化(PYTHONHASHSEED=random),导致相同输入在…
3. 🟡 中危 src/sentry/api/helpers/error_upsampling.py L131: 字符串子串匹配导致误判

line 131 处使用 "event.type:error" in query 进行简单子串匹配,会错误匹配到 "event.type:error_transacti… 4. 🟢 **低危** src/sentry/api/helpers/error_upsampling.py` L104: 函数内延迟导入

line 104 和 line 110 在函数内部进行模块导入。虽然这可能是为了避免循环导入,但延迟导入会隐藏依赖关系,增加代码维护难度,且每次函数调用都增加导入开销(尽管 Pyth…

未发帖的低优先级项

当前策略为 balanced:以下 1 条未发成行评,以免掩盖安全与正确性问题;若团队关注代码风格与微优化,可在此浏览或改为 full

  • 低危 src/sentry/api/helpers/error_upsampling.py L104:函数内延迟导入 line 104 和 line 110 在函数内部进行模块导入。虽然这可能是为了避免循环导入,但延迟导入会隐藏依赖关系,增加代码维护难度,且每次函数调用都增加导入开销(尽管 Python 会缓存已导入模块)。 如果确实存…

行评发帖:共检出 4 处,本次策略 balanced 下发 3 处行评;其余见上表或未发帖列表。


CodeHawk 提供支持 · nuwa

if top_events > 0:
# Apply upsampling transformation just before query execution
# This late transformation ensures we use the most current schema assumptions
if upsampling_enabled:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 AI 代码审查发现问题

📋 问题概述

未验证的函数返回值赋值

line 232 处调用 transform_query_columns_for_error_upsampling() 并将返回值赋给 final_columns,但未验证返回值是否为 None。该函数在 line 276 和 line 295 处也被同样方式调用。如果 transform_query_columns_for_error_upsampling() 在任何路径下返回 None(如输入验证失败、异常处理不当或配置错误),final_columns 将被设为 None,导致后续 line 235-251、line 278-291 或 line 297-323 中的查询执行代码在使用 final_columns 时触发 AttributeError 或 TypeError,使请求失败。

📍 问题详情

🟡 问题 1 | 严重程度: MEDIUM | 行号: 232

💬 详细说明:

  • 未验证的函数返回值赋值
  • line 232 处调用 transform_query_columns_for_error_upsampling() 并将返回值赋给 final_columns,但未验证返回值是否为 None。该函数在 line 276 和 line 295 处也被同样方式调用。如果 transform_query_columns_for_error_upsampling() 在任何路径下返回 None(如输入验证失败、异常处理不当或配置错误),final_columns 将被设为 None,导致后续 line 235-251、line 278-291 或 line 297-323 中的查询执行代码在使用 final_columns 时触发 AttributeError 或 TypeError,使请求失败。

📝 问题代码:

                    final_columns = transform_query_columns_for_error_upsampling(query_columns)

💡 修复建议:

使用短路求值保护赋值操作,确保 transform_query_columns_for_error_upsampling() 返回 None 时回退到原始 query_columns。需要在三处调用点(line 232、276、295)统一应用此修复。

✅ 修复示例:

                if upsampling_enabled:
                    final_columns = transform_query_columns_for_error_upsampling(query_columns) or query_columns

🔗 参考链接

Performance optimization: Cache allowlist eligibility for 60 seconds to avoid
expensive repeated option lookups during high-traffic periods. This is safe
because allowlist changes are infrequent and eventual consistency is acceptable.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 AI 代码审查发现问题

📋 问题概述

缓存键哈希不稳定

line 26 处使用 Python 内置 hash() 函数生成缓存键,但 hash() 默认启用哈希随机化(PYTHONHASHSEED=random),导致相同输入在不同进程或应用重启后产生不同的哈希值。在分布式部署或多 worker 环境中,这会造成缓存命中率骤降——一个进程设置的缓存,其他进程无法命中。

line 70 的 invalidate_upsampling_cache 函数使用相同的哈希逻辑,同样受此问题影响。

📍 问题详情

🟡 问题 1 | 严重程度: MEDIUM | 行号: 26

💬 详细说明:

  • 缓存键哈希不稳定
  • line 26 处使用 Python 内置 hash() 函数生成缓存键,但 hash() 默认启用哈希随机化(PYTHONHASHSEED=random),导致相同输入在不同进程或应用重启后产生不同的哈希值。在分布式部署或多 worker 环境中,这会造成缓存命中率骤降——一个进程设置的缓存,其他进程无法命中。
  • line 70 的 invalidate_upsampling_cache 函数使用相同的哈希逻辑,同样受此问题影响。

📝 问题代码:

    cache_key = f"error_upsampling_eligible:{organization.id}:{hash(tuple(sorted(snuba_params.project_ids)))}"

💡 修复建议:

使用确定性哈希算法替代 hash()。推荐使用 hashlib.md5hashlib.sha256 对排序后的项目 ID 元组进行哈希,确保跨进程缓存键一致。

操作步骤:

  1. 在文件顶部导入 hashlib
  2. hash(tuple(sorted(...))) 替换为 hashlib.md5(str(tuple(sorted(...))).encode()).hexdigest()
  3. 同步修改 line 70 的 invalidate_upsampling_cache 函数使用相同的哈希逻辑

✅ 修复示例:

import hashlib

def is_errors_query_for_error_upsampled_projects(
    snuba_params: SnubaParams,
    organization: Organization,
    dataset: ModuleType,
    request: Request,
) -> bool:
    """
    Determine if this query should use error upsampling transformations.
    Only applies when ALL projects are allowlisted and we're querying error events.
    
    Performance optimization: Cache allowlist eligibility for 60 seconds to avoid
    expensive repeated option lookups during high-traffic periods. This is safe
    because allowlist changes are infrequent and eventual consistency is acceptable.
    """
    project_ids_hash = hashlib.md5(
        str(tuple(sorted(snuba_params.project_ids))).encode()
    ).hexdigest()
    cache_key = f"error_upsampling_eligible:{organization.id}:{project_ids_hash}"
    
    # Check cache first for performance optimization
    cached_result = cache.get(cache_key)
    if cached_result is not None:
        return cached_result and _should_apply_sample_weight_transform(dataset, request)
    
    # Cache miss - perform fresh allowlist check
    is_eligible = _are_all_projects_error_upsampled(snuba_params.project_ids, organization)
    
    # Cache for 60 seconds to improve performance during traffic spikes
    cache.set(cache_key, is_eligible, 60)
    
    return is_eligible and _should_apply_sample_weight_transform(dataset, request)

🔗 参考链接



def _is_error_focused_query(request: Request) -> bool:
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 AI 代码审查发现问题

📋 问题概述

字符串子串匹配导致误判

line 131 处使用 "event.type:error" in query 进行简单子串匹配,会错误匹配到 "event.type:error_transaction""event.type:error_sample" 等非 error 类型的查询条件。虽然注释说明"err on the side of caution",但过度宽泛的匹配会导致不必要的 upsampled_count() 转换,可能产生错误的聚合结果或性能开销。

触发条件:当查询字符串包含 event.type:error 作为子串但并非精确匹配时(如 event.type:error_rate),函数会误判为 error 查询。

📍 问题详情

🟡 问题 1 | 严重程度: MEDIUM | 行号: 131

💬 详细说明:

  • 字符串子串匹配导致误判
  • line 131 处使用 "event.type:error" in query 进行简单子串匹配,会错误匹配到 "event.type:error_transaction""event.type:error_sample" 等非 error 类型的查询条件。虽然注释说明"err on the side of caution",但过度宽泛的匹配会导致不必要的 upsampled_count() 转换,可能产生错误的聚合结果或性能开销。
  • 触发条件:当查询字符串包含 event.type:error 作为子串但并非精确匹配时(如 event.type:error_rate),函数会误判为 error 查询。

📝 问题代码:

    if "event.type:error" in query:

💡 修复建议:

使用更精确的模式匹配来识别 event.type:error。建议采用正则表达式或词法分析,确保匹配的是完整的 token 而非子串。

操作步骤:

  1. 导入 re 模块
  2. 使用正则表达式 r'event\.type\s*:\s*error\b' 匹配完整的 event.type:error,其中 \b 确保单词边界
  3. 考虑处理引号包裹的情况如 event.type:"error"

✅ 修复示例:

import re

def _is_error_focused_query(request: Request) -> bool:
    """
    Check if a query is focused on error events.
    Reduced to only check for event.type:error to err on the side of caution.
    """
    query = request.GET.get("query", "").lower()

    # Match event.type:error as a complete token, not substring
    # Handles: event.type:error, event.type: error, event.type:"error"
    if re.search(r'event\.type\s*[=:]\s*["\']?error["\']?\b', query):
        return True

    return False

🔗 参考链接

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.

2 participants