Skip to content

[UPSTREAM CHANGES] latest changes as of Mon Mar 30 2026 00:52:45 GMT+0000 (Coordinated Universal Time) - #222

Open
github-actions[bot] wants to merge 5276 commits into
masterfrom
upstream-changes-2026-03-30
Open

[UPSTREAM CHANGES] latest changes as of Mon Mar 30 2026 00:52:45 GMT+0000 (Coordinated Universal Time)#222
github-actions[bot] wants to merge 5276 commits into
masterfrom
upstream-changes-2026-03-30

Conversation

@github-actions

Copy link
Copy Markdown

This PR is auto-generated by
actions/github-script.

bufke and others added 30 commits March 5, 2026 14:36
fix: use DETACH PARTITION CONCURRENTLY to avoid deadlocks

See merge request glitchtip/glitchtip-backend!2151
Issue search direct-hit only checked issueevent.id (UUIDv7), so
searching by client-provided sentry SDK event_id (UUIDv4) returned
no results. Now checks both id and event_id via is_uuid7() dispatch:
UUIDv7 queries prune to a single time-range partition, while UUIDv4
queries scan the event_id index with organization_id to prune hash
sub-partitions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix: search issues by sentry SDK event_id

See merge request glitchtip/glitchtip-backend!2152
The event detail endpoints only looked up by `issueevent.id` (UUIDv7),
so when the frontend redirects with a client-provided sentry SDK
event_id (UUIDv4), the lookup 404'd.

Now sniffs for UUIDv7 first (fast partition-pruned lookup), and falls
back to `event_id` field with `organization_id` for hash sub-partition
pruning when the UUID is not v7. Applies to all three event detail
endpoints: get_issue_event, get_project_issue_event, get_event_json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix: event detail endpoints fall back to sentry SDK event_id

See merge request glitchtip/glitchtip-backend!2153
- Skip model instantiation: use values_list("id", "blob") +
  default_storage.delete() instead of loading full FileBlob instances.
  Cuts one query per batch.
- Move count() to --dry-run only — avoids a full-table anti-join that
  serves no purpose during actual cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Vulture flags code after `while True:` as unreachable (doesn't
understand `break`). Refactor to `while batch:` pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The event detail endpoints only looked up by `issueevent.id` (UUIDv7),
so when the frontend redirects with a client-provided sentry SDK
event_id (UUIDv4), the lookup 404'd.

Now sniffs for UUIDv7 first (fast partition-pruned lookup), and falls
back to `event_id` field with `organization_id` for hash sub-partition
pruning when the UUID is not v7. Applies to all three event detail
endpoints: get_issue_event, get_project_issue_event, get_event_json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When events have been archived to parquet, MCP get_latest_event() and
get_event() now fall back to DuckDB cold storage instead of returning
None. Mirrors the pattern already used in the REST API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Accept optional organization_slug for partition pruning in both
  Postgres and cold storage queries
- Split Postgres query into separate id/event_id branches (avoids
  OR across partitions when type is known)
- Add UUIDv4 cold storage fallback via query_cold_events with
  event_id filter and 90-day time window
- Extract _attach_issue helper for defense-in-depth access check
- Add event_id filter support to query_cold_events

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously the memory limit was only applied when DUCKDB_TEMP_DIRECTORY
was explicitly set and writable. Without it, DuckDB ran with unbounded
memory, causing OOM kills during cold storage archival of large partitions.

Changes:
- Always set memory_limit (default 128MB) regardless of temp directory
- Auto-detect writable temp directory via tempfile.gettempdir()
- DUCKDB_TEMP_DIRECTORY env var still works as an explicit override

This prevents OOM kills in workers during archival. Without a temp dir,
DuckDB queries exceeding the memory limit will error instead of silently
consuming all available memory.
Previously the memory limit was only applied when DUCKDB_TEMP_DIRECTORY
was explicitly set and writable. Without it, DuckDB ran with unbounded
memory, causing OOM kills during cold storage archival of large partitions.

Changes:
- Always set memory_limit (default 128MB) regardless of temp directory
- Auto-detect writable temp directory via tempfile.gettempdir()
- DUCKDB_TEMP_DIRECTORY env var still works as an explicit override

This prevents OOM kills in workers during archival. Without a temp dir,
DuckDB queries exceeding the memory limit will error instead of silently
consuming all available memory.
CSV archival of issue events fails when data::text contains JSON with
backslash-escaped quotes (\") that conflict with standard CSV
double-quote escaping (""). DuckDB's CSV parser can't resolve the
ambiguous \\"" sequences.

Fix by using SOH (\x01) as the CSV quote/escape character for both
Postgres COPY TO STDOUT and DuckDB read_csv. This control character
never appears in real text/JSON data, making parsing unambiguous.

Also fix deadlocks during DETACH PARTITION CONCURRENTLY by:
- Retrying up to 3 times with exponential backoff on deadlock
- Falling back to non-concurrent DETACH if all retries fail
- Properly distinguishing "already detached" from real errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix: always set DuckDB memory limit and auto-detect temp directory

See merge request glitchtip/glitchtip-backend!2156
fix: use SOH quote char for CSV archival and retry deadlocked detach

See merge request glitchtip/glitchtip-backend!2157
Uses `uv run` so hooks use the same tool versions from pyproject.toml
that CI uses. No extra installs needed beyond `uv sync`.

Setup: `pre-commit install` (requires pre-commit, e.g. `uv tool install pre-commit`)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix: add cold storage fallback to MCP event lookups

See merge request glitchtip/glitchtip-backend!2155
Replace DuckDB's CSV→Parquet write path with arro3 (Rust Arrow/Parquet
via PyO3). DuckDB remains for all read/query operations.

Key changes:
- _flush_csv_to_parquet now uses arro3.io.read_csv + write_parquet
  instead of DuckDB's read_csv → COPY TO PARQUET
- No temp files needed — reads CSV from BytesIO, writes Parquet directly
- Removed DuckDB write connection from archive_partition_per_org
- DELTA_BYTE_ARRAY encoding for high-cardinality string columns (IDs,
  body, JSON data) with dictionary encoding for low-cardinality columns
  (service, environment, host)
- Per-table dictionary_columns config in logs and issue_events wrappers

Performance (50K rows, realistic JSON):
- Write speed: ~12x faster than DuckDB
- Peak memory: ~0.1 MB vs ~4.5 MB (Python-tracked)
- File size: 35% smaller than DuckDB (170 KB vs 262 KB) thanks to
  DELTA_BYTE_ARRAY + ZSTD compression

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
django-storages' save() appends random suffixes when a file exists,
which would make archived Parquet files unreadable by the cold storage
query path. Also update stale docstrings referencing DuckDB writes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
chore(deps): update dependency ruff to v0.15.5

See merge request glitchtip/glitchtip-backend!2160
chore(deps): update dependency boto3 to v1.42.62

See merge request glitchtip/glitchtip-backend!2148
- Tighten arro3 dependency pins from >=0.8 to ~=0.8 (compatible release)
- Update settings.py comment: DuckDB controls reads, arro3 handles writes
- Fix _duckdb_type_to_arrow docstring (arro3 is now a hard dependency)
- Log S3 delete failures at debug level instead of silent pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bufke and others added 30 commits March 26, 2026 19:42
fix: auto-set GRANIAN_WORKERS_MAX_RSS to bound memory fragmentation

See merge request glitchtip/glitchtip-backend!2243
perf: convert hot-path ORM queries to raw SQL in event ingest

See merge request glitchtip/glitchtip-backend!2242
check_all_organizations_throttle() used .aiterator() which creates a
server-side cursor, incompatible with PgBouncer transaction pooling.
Replace with keyset pagination (id__gt) in batches of 500 to keep
memory bounded without requiring server-side cursors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- wizard: setup_wizard and setup_wizard_delete now use async cache ops
- api fallback 404: async to avoid unnecessary thread in ASGI
- prometheus metrics: inline generate_latest instead of sync_to_async
  wrapping the django-prometheus view (no I/O, just CPU formatting)

django-ninja uses sync Operation for sync views, which Django's ASGI
handler wraps in sync_to_async — creating a thread for no reason.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: convert remaining sync views to async

See merge request glitchtip/glitchtip-backend!2244
fix: replace server-side cursor with batched iteration in throttle check

See merge request glitchtip/glitchtip-backend!2246
Oversized payloads on the /store/ endpoint raised RequestDataTooBig
during ninja's parameter parsing, which bubbled up as an unhandled
error event. The middleware already logs a warning with diagnostic
details. Adding a ninja exception handler returns a clean 413 response
without generating noise in error tracking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
chore(deps): update dependency boto3 to v1.42.77

See merge request glitchtip/glitchtip-backend!2247
chore(deps): update dependency ruff to v0.15.8

See merge request glitchtip/glitchtip-backend!2248
Concurrent INSERT ... ON CONFLICT DO UPDATE statements on
uptime_uptimecheckhourlystatistic could deadlock when acquiring row
locks in different orders. Sorting the batch by organization_id
ensures consistent lock ordering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DuckDB defaults to one thread per HOST CPU core. In Kubernetes, it reads
the node's cores (e.g. 64) rather than the pod's CPU limit (e.g. 2).
Each thread allocates scan buffers outside the memory_limit setting,
causing VmPeak to hit 8+ GB and triggering OOM pod recycling.

Changes:
- Set threads on ALL connections (was only set on write connections).
  Default: 2, configurable via DUCKDB_THREADS env var.
- Lower default memory_limit cap from 1024 MB to 256 MB. The memory_limit
  only bounds DuckDB's internal buffer pool — thread stacks, mmap'd
  regions, and jemalloc overhead are additional. A 256 MB pool with
  2 threads typically peaks at ~400-500 MB total process impact.
- Both settings are configurable for dedicated analytics workloads.

Benchmark proof (8-core machine, 500k rows across 10 Parquet files):
  BEFORE (8 threads, 1024MB): VmPeak 985 MB, RSS +147 MB
  AFTER  (2 threads, 256MB):  VmPeak 320 MB, RSS +45 MB
  Query time impact: +4% (negligible)

On a 64-core Kubernetes node the VmPeak reduction would be proportionally
larger — consistent with the 8.1 GB VmPeak observed in production.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DUCKDB_THREADS now reads cgroup v2 cpu.max (Kubernetes/Docker) to use
the pod's actual CPU limit instead of a hardcoded default. Falls back
to min(os.cpu_count(), 4) on bare-metal.

Also refactors the cgroup reading into shared helpers:
- _get_cgroup_memory_bytes(): reads /sys/fs/cgroup/memory.max
- _get_cgroup_cpu_count(): reads /sys/fs/cgroup/cpu.max

Both settings auto-detect but accept env var overrides:
- DUCKDB_MEMORY_LIMIT: auto from cgroup memory (25%, cap 256MB)
- DUCKDB_THREADS: auto from cgroup CPU quota (or cap at 4)
- DUCKDB_TEMP_DIRECTORY: defaults to /tmp

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add billing cycle dates to created subscriptions

See merge request glitchtip/glitchtip-backend!2253
fix: cap DuckDB threads and memory to prevent VmPeak explosion

See merge request glitchtip/glitchtip-backend!2254
fix: sort uptime stats upsert by org_id to prevent deadlock

See merge request glitchtip/glitchtip-backend!2251
fix: handle RequestDataTooBig with 413 instead of unhandled exception

See merge request glitchtip/glitchtip-backend!2250
chore(deps): update dependency google-cloud-logging to v3.15.0

See merge request glitchtip/glitchtip-backend!2249
chore(deps): update dependency model-bakery to v1.23.4

See merge request glitchtip/glitchtip-backend!2256
chore(deps): update dependency boto3 to v1.42.78

See merge request glitchtip/glitchtip-backend!2255
chore(deps): update dependency aiohttp to v3.13.4

See merge request glitchtip/glitchtip-backend!2258
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.

5 participants