Skip to content

[UPSTREAM CHANGES] latest changes as of Mon Jul 13 2026 03:40:08 GMT+0000 (Coordinated Universal Time) - #237

Open
github-actions[bot] wants to merge 5726 commits into
masterfrom
upstream-changes-2026-07-13
Open

[UPSTREAM CHANGES] latest changes as of Mon Jul 13 2026 03:40:08 GMT+0000 (Coordinated Universal Time)#237
github-actions[bot] wants to merge 5726 commits into
masterfrom
upstream-changes-2026-07-13

Conversation

@github-actions

Copy link
Copy Markdown

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

bufke and others added 30 commits May 1, 2026 11:29
The lock file kept the workspace member entry after pyproject.toml lost
it; CI failed with "Distribution not found at .../gt_rust". Regenerated
with ``uv lock``.

Disclosure: this MR was prepared with assistance from Claude.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ingest hot path was a mix of native async cursors (raw SQL via
async-backend) and Django ORM convenience methods (abulk_create,
aupdate, aexists, async-iter on a sync QuerySet) that thread-pool
through the sync connection. The latter group made the hot path
double-counted across two pools and opaque to async-aware test
assertions.

Convert every remaining sync_to_async-wrapped DB call in
process_issue_events / process_transaction_events to either raw async
SQL via async_db.fetchall/execute helpers or async-backend's
AsyncQuerySet (debug bundles, where ORM model construction with
select_related is the right tool). Issue + IssueHash creation now
runs inside async_atomic with raw INSERTs; the IntegrityError
race-recovery path reads issue_id back via a SELECT.

Replace assertNumQueries on the affected hot-path tests with a new
AsyncQueryCounter (glitchtip/test_utils/async_query_counter.py) that
patches AsyncCursorWrapper at the class level, so it observes queries
across whatever task the sync test client dispatches into. Sync
queries from setUp fixtures are now ignored by design.

test_store_api.setUp gains cache.clear() so TransactionTestCase's
RESTART IDENTITY truncation can't race the auth/throttle cache between
tests in the class.
Project baseline is Python 3.12+; PEP 604 / PEP 585 are native and
PEP 563 deferred evaluation isn't relied on. Use a forward-reference
string for the self-referential return annotation instead.
Vulture flagged exc_type/exc_value/traceback as unused. Replace with
*_exc_info — the leading underscore tells vulture the variable is
intentionally unused.

Also expand the module docstring to spell out why AsyncQueryCounter
exists and what would have to change before AsyncCaptureQueriesContext
from django-async-backend can replace it (every middleware in
MIDDLEWARE has to be async_capable, and a recalibration pass to absorb
the BEGIN/COMMIT delta in queries_log).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a prominent docstring banner and an import-time DeprecationWarning to
glitchtip/wsgi.py. GlitchTip is async-first now: WSGI bridges every async
view (including the entire ingest hot path) through async_to_sync, leaks
a task-local async-backend DB connection per request, and bypasses the
glitchtip.ingest_asgi fast-path dispatcher entirely.

Custom deployments that bypass our scripts and import glitchtip.wsgi
directly will now see the warning in their logs and have a clear pointer
to glitchtip.asgi:application under Granian.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(ingest): native async DB cursors via django-async-backend

See merge request glitchtip/glitchtip-backend!2343
The release Docker build previously ran on a single amd64 runner and
emulated arm64 layers via QEMU (`multiarch/qemu-user-static`). Emulated
ARM builds are 5-10x slower than native, dominating the wall time of
every release tag.

GitLab now exposes native aarch64 SaaS runners (tag
`saas-linux-medium-arm64`), so split the multi-arch build into two
parallel native jobs (one per arch) that push arch-suffixed tags, plus a
small `release_manifest_merge` job that uses
`docker buildx imagetools create` to publish the user-facing tags
(`latest`, `X.Y.Z`, `X.Y`, `X`) as a manifest list referencing both
arches. A `release_prepare_assets` job downloads the frontend
`dist/` artifact once and shares it with both arch builds.

Also add a `validate_build_{amd64,arm64}` matrix that runs on MRs into
the default branch and on master pushes. It builds the production
Dockerfile natively for each arch (no push) and runs a cheap
`python -c "import django, glitchtip"` smoke test against the resulting
image. This catches "build succeeds but image won't run on this arch"
regressions before they reach a release tag, where the QEMU build was
the first time we discovered them.

Triggers for the release jobs are unchanged: protected vX.Y.Z tags
only, never on schedules or MRs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop verbose intros and the QEMU/legacy-job references; keep only the
non-obvious "why" (smoke test purpose, no-DinD note for imagetools).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ci: native multi-arch builds + per-MR build validation

See merge request glitchtip/glitchtip-backend!2349
Bulk INSERT/UPDATE in event ingest, log ingest, and the transaction
pipeline previously expanded one ``(%s, %s, ...)`` tuple per row into a
``VALUES`` list via ``execute_mogrified_values``. That has three costs
that grow with batch size: a per-row mogrify round-trip in Python, a
distinct statement shape per batch length (poor plan-cache reuse), and
the 65535 bind-parameter ceiling Postgres enforces, which clamps batches
on wide schemas (the 13-column ``issue_events_issueevent`` insert tops
out around 5000 rows).

Pass per-column arrays to ``unnest(%s::T[], %s::T[], ...)`` instead. One
parameter per column regardless of batch size; one statement shape; no
bind-param ceiling.

Synthetic benchmark (Postgres 18, unlogged tables, 30 rounds, local docker):

  issue_events_issueevent  13 cols  500 rows: 26% faster (45ms -> 33ms median)
  issue_events_issuetag    6  cols  2000 rows: 52% faster (70ms -> 34ms median)

Converted call sites:
- apps/event_ingest/process_event.py: issueevent INSERT, issuetag INSERT,
  update_issues UPDATE, update_statistics, update_org_statistics,
  transactiongroup INSERT, spanstaging INSERT,
  _update_transaction_group_stats Phase 2, and the two IN(VALUES)
  lookups (_fetch_issue_hashes_raw, _fetch_transaction_groups), now
  rewritten as JOIN unnest().
- apps/logs/process_logs.py: logevent INSERT, log_statistics upsert,
  resource_lookup upsert.

Left as VALUES (not worth converting): the small release/environment
fanout (tiny batches), and ``_update_transaction_group_stats`` Phase 1
which carries an int[] histogram per row that doesn't trivially unnest.

Adds two thin helpers in apps/shared/async_db.py — ``execute_unnest``
and ``fetchall_unnest`` — that transpose row-major data to per-column
lists before executing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
perf(ingest): switch hot-path bulk writes from VALUES to UNNEST

See merge request glitchtip/glitchtip-backend!2350
Same conversion as the prior commit on this branch but for the uptime
hot path: the upsert previously mogrified one ``(%s,%s,%s)`` tuple per
organization in a Python loop, joined them into a ``VALUES`` list, and
ran the whole thing through ``sync_to_async``. Switch to async-native
``execute_unnest`` with ``async_atomic`` so we drop both the per-row
Python work and the sync_to_async thread hop on every check flush
(every 1 s or 100 checks, whichever comes first).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
perf(uptime): switch update_uptime_statistics from VALUES to UNNEST

See merge request glitchtip/glitchtip-backend!2351
The Stripe webhook handler was hitting unrecoverable failures on transient
429 lock_timeout responses from the Stripe API, which Stripe itself flags
as retryable via the Stripe-Should-Retry header.

Two related issues:

1. stripe_get/stripe_post had no retry logic — any non-200 response
   immediately raised. Now they share a retry helper that backs off
   exponentially on 429/5xx, honoring the Stripe-Should-Retry header
   when present.

2. The webhook view marked event IDs as processed via cache.aadd before
   actually processing them. If processing raised, Stripe's webhook retry
   would arrive, hit the dedup cache, and be silently dropped. The mark
   is now released on failure so retries can reprocess.

🤖 Generated with Claude Code
fix(stripe): retry transient API errors and release dedup key on failure

See merge request glitchtip/glitchtip-backend!2353
`unix_to_datetime` used `datetime.fromtimestamp(ts)` (no `tz` arg), which
returns a naive datetime in the process's local time, then attached
`settings.TIME_ZONE` via `make_aware`. This only produced correct UTC
values because the standard container TZ is UTC — naive UTC tagged as
UTC happens to round-trip correctly.

If a deployment sets `TZ` to a non-UTC zone (e.g. for log readability),
`fromtimestamp` would return a naive local datetime and `make_aware`
would mislabel it as UTC, silently shifting every Stripe timestamp into
the database by the local offset.

Pass `tz=timezone.utc` explicitly so the conversion no longer depends on
process-local time. No behavior change on UTC hosts.

Regression test forces `TZ=America/New_York` and `TZ=Asia/Tokyo` via
`time.tzset()` to confirm the result is the same in any local timezone.

🤖 Generated with Claude Code
fix(stripe): redirect cancel_url to subscription page instead of home

See merge request glitchtip/glitchtip-backend!2355
fix(stripe): make unix_to_datetime timezone-independent

See merge request glitchtip/glitchtip-backend!2354
The HTTP path returns django-async-backend's per-asyncio-Task connection
wrapper to the pool via the close_async_connections middleware. The task
worker has no equivalent. When an async task uses async_connections
(e.g. an ingest task reading the read replica), the wrapper's close()
is never called -- the asyncio.Task ends, the wrapper is garbage
collected, and psycopg.AsyncConnection.__del__ runs synchronously, which
can't perform an async close handshake cleanly.

Wire receivers on django_vtasks.signals.task_finished and task_failure
that call async_connections.close_all() inside the same asyncio.Task
that ran the user task. Mirrors the HTTP middleware semantics, including
the asyncio.shield to keep cleanup running through cancellation.

vtasks 2.1.1 emits exactly one of task_finished/task_failure per task
(success, exception, or cancellation), so the two receivers cover every
completion path. Bump the dependency floor to require it.

Disclosure: prepared with assistance from Claude.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Django signal receivers receive sender/signal/extra kwargs they don't
necessarily use. Rename **kwargs to **_kwargs so vulture's 100%
confidence sweep skips it.
fix: close async DB connections after each background task

See merge request glitchtip/glitchtip-backend!2356
django.contrib.postgres wires register_type_handlers to the
connection_created signal so psycopg can auto-decode hstore columns.
That handler runs `SELECT ... FROM pg_type WHERE typname = 'hstore'`
on every new connection. GlitchTip uses jsonb instead of hstore, so the
extra round-trip is wasted work — and the receiver uses the sync ORM,
which raises SynchronousOnlyOperation when fired from inside an asyncio
task. The result is that async DB connections in worker tasks raise on
their first open after !2356 returned the connection-wrapper lifecycle
to a healthy state.

django.contrib.postgres has to stay in INSTALLED_APPS because Django's
postgres.E005 check requires it for ArrayField and SearchVectorField,
both of which we use. The fix is to disconnect the single offending
receiver after PostgresConfig.ready() runs.

Also swap the e2e test from sentry-cli to glitchtip-cli. sentry-cli is
no longer open source and pulling from sentry.io is incompatible with
the clean-room rules in CLAUDE.md. glitchtip-cli is a drop-in for the
send-event command we use, accepts SENTRY_DSN for compatibility, and
prints the event id as the last token on stdout, so the existing parser
keeps working.

🤖 Generated with Claude Opus 4.7. AI-assisted; please review carefully.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix: skip hstore OID lookup on every connection_created

See merge request glitchtip/glitchtip-backend!2357
fix: make_sample_* failing because partitions in the past didn't exist

See merge request glitchtip/glitchtip-backend!2358
The transaction-group spans endpoint (and other span cold queries) took
~7s. Root cause: the analytical read connection forced threads=1, so a
multi-day query read hundreds of tiny per-day chunk Parquet files from S3
one HTTP round-trip at a time, and compaction only collapsed whole days
once daily — so the recent window dashboards actually query was always
hundreds of files.

Reads:
- get_duckdb_read_connection no longer clamps to threads=1; it uses the
  cgroup-aware DUCKDB_THREADS. Multi-file span aggregations are
  S3-latency-bound and parallelism overlaps the round-trips.
- New process-wide duckdb_slot() semaphore bounds *all* DuckDB work
  (reads, compaction, rewrite). memory_limit is per-connection, so an
  unbounded request burst otherwise multiplies peak RSS. Reads degrade to
  empty when saturated; must-complete deletion work blocks.

Storage tiers (spans only; logs/issue-events keep the flat layout):
- Promotion writes hour-bucketed chunks (org/{date}/{HH}/chunk_*).
- Compaction seals a completed hour into one file, lazily rolls a fully
  sealed day, and is idempotent + keyed off ingestion time: a sealed file
  is never rebuilt, so there is no writer race and no conservative
  multi-day skip. It uses a memory-bounded DuckDB COPY (ROW_GROUP_SIZE +
  memory_limit + the slot); all promotion writes stay on arro3.
- New performance_spans_rollup tier: small per-(project,txn,op,desc)
  hourly aggregates emitted at hour seal. Trend queries read these
  instead of raw spans, so they stay cheap over long ranges.
- Split retention: raw spans dropped at GLITCHTIP_SPAN_RAW_RETENTION_DAYS
  (default 30); rollups kept for the long transaction retention.
- The transaction ingest timestamp check is back to retention + a
  future-skew guard (idempotent compaction no longer needs a tight
  freshness window).

Deletion (self-hosters without object-store lifecycle rules rely on it):
- Org deletion recursively purges the raw subtree (now 3 levels deep)
  and the rollup tree.
- Project deletion's parquet rewrite is hour-tier aware and also strips
  the project from the rollup tree.

Full performance / event_ingest / projects / organizations_ext / logs /
issue_events suites pass. The DuckDB-COPY vs arro3 compaction choice was
made by benchmarking both for speed and peak RSS under injected S3
latency (a tie; DuckDB COPY kept for far less code).

Developed with AI assistance (Claude Code). Human review required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
So contributors adding periodic tasks (cold-storage compaction, retention
sweeps, etc.) don't add an external lock to keep them from running on
every pod — django-vtasks already gives single-runner semantics for
scheduled entries. Manual ``aenqueue()`` calls still race and need their
own guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mike and others added 30 commits July 2, 2026 19:04
- Merge orphan cleanup into cleanup_old_files instead of a separate
  maintenance step
- Use aiterator() instead of sync_to_async(list) for real async
- Delete redundant cleanup_orphaned_fileblobs management command
fix: fix support for mattermost webhooks glitchtip-backend#485

See merge request glitchtip/glitchtip-backend!2434
Accept Breakpad-format debug_id in NativeDebugImage

See merge request glitchtip/glitchtip-backend!2420
feat(files): periodic cleanup of orphaned FileBlobs

See merge request glitchtip/glitchtip-backend!2432
Follow-ups to the orphaned-FileBlob cleanup:

- Delete DB rows before storage, filtering through the original
  queryset so the pass conditions are re-evaluated inside the delete.
  Uploads dedupe on checksum via get_or_create, so a blob fetched as
  orphaned can gain a File reference before the batch delete runs;
  previously that File would be cascaded away. Row-first ordering
  means a skipped blob keeps both its row and its storage bytes — a
  crash mid-batch can only leak an unreferenced storage object, never
  leave a File whose backing bytes are gone.
- Pass save=False to blob.delete() so it no longer issues an UPDATE
  blanking the blob field on an already-deleted row, halving
  sync-connection writes per blob.
- Count only FileBlob rows against MAX_DELETIONS_PER_RUN instead of
  the cascade total, so the cap is a blob budget.
- Log storage-delete failures with exc_info so a missing file is
  distinguishable from a storage misconfiguration.
- Assert storage-file existence in the cleanup tests.

The ORM stays on the async API throughout; only the storage delete
hops threads, since django-storages has no async API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
perf(files): streamline FileBlob cleanup deletes

See merge request glitchtip/glitchtip-backend!2439
chore(deps): update dependency symbolic to ~=13.8.0

See merge request glitchtip/glitchtip-backend!2437
django-organizations 2.7.0 ships async versions of the core membership
utilities. Bump to ~=2.7 and drop GlitchTip's local reimplementation of
the create + user_added signal mechanics:

- Organization.add_user/aadd_user now delegate org-user creation and the
  user_added signal to upstream via super(), keeping only the role-based
  first-user-becomes-owner logic (upstream's abstract variant is
  is_admin-based, which GlitchTip does not use).
- The create-organization API calls aadd_user instead of hand-rolling
  the org user and owner records and sending the sync user_added signal
  from an async view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Assert the creating user gets the OWNER role and an OrganizationOwner
record, since that behavior now lives in aadd_user's first-user branch
rather than inline in the endpoint. Also document that user_added fires
before the owner record exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
refactor(organizations): use django-organizations 2.7 async core utils

See merge request glitchtip/glitchtip-backend!2440
…endpoint

Mirrors django-organizations 2.7's AbstractOrganization.achange_owner,
which GlitchTip cannot inherit because Organization extends the base
(not abstract) hierarchy. The set_organization_owner endpoint previously
hand-rolled the owner swap and called the sync owner_changed.send() from
an async view; it now delegates to achange_owner, which uses asend.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The permission fast path compared an OrganizationUser pk against a User
id using identity semantics (`old_owner.pk is user_id`), so the clause
that lets the current designated owner transfer ownership effectively
never matched — and could coincidentally match for unrelated small ids.
Compare the owner's user_id with equality instead, and pin the intended
grant with a test.

Also strengthen test_achange_owner to assert the owner row is updated
in place rather than replaced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace django-allauth with django-allauth-async, a fork-hosted async
extension that ships pristine upstream allauth plus allauth_async — async
twins of the account, headless, socialaccount and MFA flows (async ORM and
sessions, aiohttp for provider HTTP, no sync_to_async thread offloads on
the request path).

- settings: AsyncAccountMiddleware, AsyncAuthenticationBackend, async
  default MFA/headless adapters
- adapters: custom account/socialaccount adapters derive from the fork's
  async defaults; every customized sync hook now has an async twin
  (send_mail, is_open_for_signup, save_user), enforced at startup by the
  fork's twin-override guard. open_http_session applies AIOHTTP_CONFIG so
  provider HTTP honors PROXY_ENV and our User-Agent, as the sync stack got
  implicitly from requests
- urls: mount allauth_async.urls and allauth_async.headless.urls (same
  routes/names; async views for all providers GlitchTip enables)
- users: email confirmation sends via the fork's asend_confirmation;
  user_logged_in receiver is now natively async
- recovery codes: rebuild the preview/confirm endpoints on the fork's
  AsyncRecoveryCodes primitives. This also fixes a latent bug where the
  previewed codes only matched the persisted ones when the MFA adapter's
  encrypt() was the default identity function, plus hardens the confirm
  against a concurrent-activation race
- OIDC: drop glitchtip/oidc_discovery.py in favor of the fork's cached
  discovery helper; the settings endpoint now uses the provider's
  normalized well-known URL (shared cache key with the login flow) and
  degrades authorize_url to None on any discovery failure

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- catch ValueError alongside IntegrityError: AsyncRecoveryCodes.aactivate
  raises it when a concurrent confirm's row lands before the insert -- the
  other half of the same race, previously a 500
- namespace the cached-seed key (users:recovery-seed:<id>) instead of the
  bare seed<id> in the shared cache
- bump django-allauth-async to 65.16.1.6, which routes the social
  adapter's default ais_open_for_signup through the account async twin
  (mirrors upstream's delegation chain; no glitchtip behavior change since
  our social adapter overrides both halves)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fork release defers jwtkit's module-scope jwt/cryptography imports to
the token-verification call sites, keeping both libraries out of every
web and worker process at boot (~8 MiB RSS per process measured here;
they only load if a JWT-verifying social provider is actually used).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(organizations): add async achange_owner and use it in set_owner endpoint

See merge request glitchtip/glitchtip-backend!2442
Django 6.0's ModelBackend.aauthenticate runs the user-enumeration timing
mitigation (a full ~200ms password hash) synchronously on the event
loop when the email doesn't exist, stalling every in-flight request.
Django 6.1 fixes this (ticket #36901) but the fix is not backported, so
subclass ModelBackend to offload the dummy hash to a thread, matching
both Django 6.1's helper and how acheck_password offloads internally.
Revert to the stock backend once the Django pin moves past 6.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(auth): switch to django-allauth-async for native-asyncio auth flows

See merge request glitchtip/glitchtip-backend!2443
Long-running processes accumulate freed-but-unreturned glibc heap pages
(tens of MB over hours of steady ingest). The maintenance task already
runs gc.collect() + malloc_trim(0), but scheduled tasks execute once
cluster-wide per interval, so only the pod that picks the task up ever
gets trimmed — every other pod keeps the pages until its worker recycles.

Add a small ASGI wrapper that runs the same trim on a conservative
in-process timer (hourly by default, GLITCHTIP_MALLOC_TRIM_INTERVAL to
tune, 0 disables). A trim costs milliseconds; a worker recycle costs a
full process restart, so trimming every pod makes recycling rarer for
negligible overhead. The malloc_trim helper moves to glitchtip/
memory_trim.py and the maintenance task imports it from there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Read the interval via settings/env.int like every other knob, instead
  of a bare int(os.environ...) at ASGI import that would crash-loop all
  workers on a malformed value.
- Jitter each cycle +/-50% so a fleet rolled out together doesn't run
  its gc pause in lockstep at the top of every hour.
- Run malloc_trim in an executor: the ctypes call releases the GIL, so
  that half of the pause comes off the event loop for free. gc.collect()
  stays inline — it holds the GIL for its whole pass either way.
- Move the wrapper outermost in asgi.py: the vtasks embed wrapper
  consumes lifespan without forwarding it, so an inner placement never
  starts the timer on a pod receiving no HTTP traffic.
- Cancel the timer on lifespan shutdown (no pending-task destruction
  noise on worker recycles), name the task, and restart it if a prior
  event loop died. Scope the docstring honestly to ASGI processes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A postgres client connection retains buffer memory sized to the largest
protocol message it ever carried — libpq grows its wire buffers to fit
and never shrinks them — so long-lived pooled connections ratchet RSS
upward under large-payload traffic (event and log ingest, big JSONB
reads). Retiring connections periodically bounds that retention.

Expose the pool's max_lifetime via DATABASE_POOL_MAX_LIFETIME (seconds,
default 1800). Both database drivers accept the same pool option key and
jitter the recycle deadline to avoid reconnect stampedes; setting 0
leaves the driver's own default lifetime in place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measurements showed the lifetime lever saturates — cutting the window
12x or 30x below the driver default landed on the same growth floor —
while more frequent recycling measurably increases allocator churn
(evicted connection buffers become freed-but-binned heap pages). With
the bulk writes bounded at the source, there is no data supporting one
pinned default over another, so leave each driver's own default in
place and set max_lifetime only when DATABASE_POOL_MAX_LIFETIME is
explicitly configured.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
perf(db): expose a pooled-connection max_lifetime knob

See merge request glitchtip/glitchtip-backend!2450
chore(deps): update dependency symbolic to ~=13.9.0

See merge request glitchtip/glitchtip-backend!2448
perf: periodically return freed memory to the OS in every server process

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

6 participants