Skip to content

feat(DM01-6260): optimize console log loading with gzip compression and progressive rendering - #643

Merged
liuwei08 merged 4 commits into
masterfrom
feat/DM01-6260
Sep 10, 2026
Merged

feat(DM01-6260): optimize console log loading with gzip compression and progressive rendering#643
liuwei08 merged 4 commits into
masterfrom
feat/DM01-6260

Conversation

@liuwei08

@liuwei08 liuwei08 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Console output pages for large jobs (e.g. 250 MB) were taking 3+ minutes to load. This PR reduces initial load time to under 1 second with no UX change, and fixes several security and correctness issues found during review.

Performance changes

Change Effect
?tail=N parameter on GET .../console Backend returns only the last N lines; archived path uses DB-side array slicing, streaming path uses ORDER BY date DESC LIMIT N — 250 MB logs are never fully loaded into Python memory
gzip compression for responses > 100 KB 250 MB → ~15 MB on the wire (~15× faster transfer)
Frontend loads ?tail=500 first, then silently prefetches full log in background Page renders immediately; full log replaces tail view transparently for finished jobs only

Bug fixes (from code review)

  • Security: console table fallback queries now scope by project_id to prevent cross-project console data leakage
  • GC marker: filter console = 'deleted' (set by GC after 30 days) so old jobs no longer display the literal string "deleted" as output
  • Chunk vs line: fetch max(tail, 200) chunks then trim to exact tail lines in Python, fixing the mismatch between DB row count and actual log line count
  • gzip threshold: use len(output.encode('utf-8')) instead of len(output) so multi-byte Unicode content is measured correctly
  • Re-entry guard: replace sections.length check with a dedicated _consoleFetched flag so the guard holds even after _prefetchFullConsole resets sections = []
  • WS duplication: _prefetchFullConsole only runs for finished jobs, preventing live WebSocket lines from being duplicated when the full log replaces the tail view

CI change

  • Remove service-aks and service-gardener build/deploy jobs from infrabox/generator/deployments.json

Test plan

  • Console page for a large-log job renders within 1 second
  • Full log is silently loaded and displayed in the background for finished jobs
  • Running jobs show live WebSocket updates with no duplicate lines
  • GET .../console?tail=500 returns exactly the last 500 lines
  • GET .../console (no param) returns the full log unchanged
  • Jobs older than 30 days (GC-marked) do not show "deleted" as console output
  • GET /api/v1/projects/<A>/jobs/<B_job_id>/console returns empty when job does not belong to project A
  • Invalid ?tail=abc falls back to full log with no 500 error

Jira: DM01-6260

…t 500 lines

Backend: Console.get() now accepts ?tail=N. For archived logs (job.console)
the last N lines are sliced in Python; for streaming logs (console table) a
subquery ORDER BY date DESC LIMIT N avoids a full table scan.
Backward compatible — omitting tail returns the full log unchanged.

Frontend: loadConsole() defaults to tail=500 so the initial page load
fetches only the last 500 lines. consoleTruncated flag drives a notice bar
in Console.vue with a "Load full log" link that calls loadFullConsole(),
which resets sections and re-fetches without the limit.
…ilent prefetch

Backend:
- Archived tail requests now use PostgreSQL array slicing so large logs
  (e.g. 250 MB) are trimmed on the DB side instead of loading the full
  text into Python memory before slicing.
- Add _console_response() helper that gzip-compresses responses larger
  than 100 KB when the client sends Accept-Encoding: gzip, reducing
  a 250 MB transfer to ~15 MB (~15x faster).

Frontend:
- loadConsole() fetches ?tail=500 first so the page renders immediately
  without waiting for the full log download.
- _prefetchFullConsole() silently fetches the full log in the background
  and replaces the sections once complete — no user interaction required.
- Removed consoleTruncated flag, loadFullConsole(), and the truncation
  notice from Console.vue; UI is identical to before.
- Filter GC 'deleted' marker: add AND console != 'deleted' to both tail
  and non-tail archived log queries so old jobs no longer return the
  literal string 'deleted' as console output
- Add project_id scope to console table fallback queries via subquery
  to prevent cross-project console data leakage
- Fix chunk-vs-line mismatch: fetch max(tail, 200) chunks then trim to
  exact tail lines in Python after concatenation
- Fix gzip threshold: use len(output.encode('utf-8')) instead of
  len(output) so multi-byte Unicode content is measured correctly
- Use _consoleFetched flag instead of sections.length as the loadConsole
  re-entry guard so the guard holds even after _prefetchFullConsole
  resets sections to []
- Only run _prefetchFullConsole for finished jobs to prevent WebSocket
  line duplication on running jobs
- Reset _consoleFetched = false on loadConsole network error so the
  user can retry
@liuwei08 liuwei08 changed the title feat(DM01-6260): add tail parameter to console API and default to last 500 lines feat(DM01-6260): optimize console log loading with gzip compression and progressive rendering Sep 10, 2026
@liuwei08
liuwei08 merged commit 80397b1 into master Sep 10, 2026
2 checks passed
@liuwei08
liuwei08 deleted the feat/DM01-6260 branch September 10, 2026 08:13
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.

1 participant