Skip to content

Fix bugs and optimize performance#71

Merged
zas merged 13 commits into
masterfrom
fix/audit-bugs
May 19, 2026
Merged

Fix bugs and optimize performance#71
zas merged 13 commits into
masterfrom
fix/audit-bugs

Conversation

@zas
Copy link
Copy Markdown
Contributor

@zas zas commented May 15, 2026

Summary

Bug fixes, performance optimizations, and cleanup for the artwork redirect service.

Changes

Bug fixes:

  • Fix handle_redirect returning a list instead of a Response on empty filename
  • Remove unused statuscode import (dead after the above fix)
  • Fix test_all.py importing load_config from wrong module
  • Clean up handle_options: remove dead guard, redundant None check, limit split; remove spurious Sentry capture

Performance:

  • Pre-compile regexes (thumbnail, service name, image ID, MBID validation)
  • Merge resolve_mbid into each query via COALESCE subquery (eliminates 1 DB round-trip per request, ~40% less DB time)
  • Cache static files with 5-minute mtime revalidation (63x speedup for static endpoints)
  • Make connection pool configurable, defaulting to NullPool (opt-in QueuePool gives 4-5x speedup)

Security / hardening:

  • Quote table names in dynamic SQL via identifier_preparer

Cleanup:

  • Remove unused LocalSysLogHandler, statuscode(), dead attributes
  • Rename type parameter to cover_type (shadows builtin)
  • Delete empty utils.py

Tooling:

  • Add benchmarks/ with auto-serve and status code reporting

Testing

All 36 unit tests pass. Integration tests require the Docker-based database.

@zas zas force-pushed the fix/audit-bugs branch from f045da5 to 8895202 Compare May 15, 2026 15:24
@zas zas requested a review from mwiencek May 15, 2026 15:26
@zas zas force-pushed the fix/audit-bugs branch 4 times, most recently from 84dee5f to c4d2f81 Compare May 15, 2026 16:17
Comment thread artwork_redirect/request.py Outdated
Comment thread artwork_redirect/server.py Outdated
Comment on lines +60 to +59
self.engine = sqlalchemy.create_engine(self.config.database.create_url(), poolclass=NullPool)
self.engine = sqlalchemy.create_engine(self.config.database.create_url())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It should improve app performance though at the cost of reducing the available pgbouncer pool size for musicbrainz-server. However, we do have two pgbouncer-slave containers, so it might be fine. We agreed to test this in production on one of the containers.

Comment thread artwork_redirect/request.py
Comment thread artwork_redirect/request.py Outdated
Comment thread artwork_redirect/request.py
@zas zas force-pushed the fix/audit-bugs branch from 1776855 to ae3a4fa Compare May 15, 2026 19:02
zas added 10 commits May 16, 2026 12:42
…it split

Remove spurious Sentry capture for expected ValueError in handle_options.
Eliminates one DB round-trip per request by inlining the GID redirect
resolution into each resolve method's WHERE clause. Also removes the
now-unused resolve_mbid() method and its separate call in handle().

Benchmark (10000 iterations, QueuePool, local PostgreSQL):

  2 queries:  0.317 ms/req (baseline)
  1 merged:   0.191 ms/req (1.66x faster, 40% less DB time)
Adds StaticCache that stores file content + mtime. Hot path is a dict
lookup + time.monotonic() comparison (no syscalls). Re-checks mtime
every 5 minutes; only re-reads on change.

All static endpoints (HTML index pages, SVG images) use the shared
_serve_static helper.

Benchmark (50000 iterations):

  File type   Disk read    Cached     Speedup
  HTML        10.1 µs/req  0.2 µs/req  63x
  SVG         10.2 µs/req  0.2 µs/req  64x
Defense-in-depth: use SQLAlchemy's identifier_preparer.quote() for
interpolated table names in _resolve_entity_id_sql, as suggested in
PR review.
…ibutes

Remove unused LocalSysLogHandler

Remove unused statuscode()

Replace unused id with _ in thumbnail()

Rename type parameter to cover_type (type is a builtin)
Set pool_mode=queue in config.ini to use QueuePool with persistent
connections. Defaults to NullPool (no persistent connections).

QueuePool benchmark (1000 iterations, local PostgreSQL):

  Endpoint              NullPool    QueuePool   Speedup
  release/front         6.08 ms     1.22 ms     5.0x
  release/index         4.68 ms     0.94 ms     5.0x
  release-group/front   8.38 ms     2.29 ms     3.7x
  release/front (404)   7.04 ms     1.81 ms     3.9x

QueuePool defaults: pool_size=2, pool_max_overflow=3, pool_recycle=300.
@zas zas force-pushed the fix/audit-bugs branch from ae3a4fa to 708194d Compare May 16, 2026 11:17
@zas
Copy link
Copy Markdown
Contributor Author

zas commented May 16, 2026

@mwiencek I reworked the PR, kept the default NullPool but added a config way to change it to QueuePool if we want to evaluate, dropped useless changes, and grouped commits when it was making sense.

@zas zas requested a review from mwiencek May 18, 2026 17:10
Comment thread artwork_redirect/request.py Outdated
…uery

redir.new_id already holds the entity id, so the JOIN to the entity
table was unnecessary.

Suggested-by: Michael Wiencek <mwiencek@users.noreply.github.com>
See: #71 (comment)
Copy link
Copy Markdown
Member

@mwiencek mwiencek left a comment

Choose a reason for hiding this comment

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

Thanks!

@zas zas merged commit c7e3dc0 into master May 19, 2026
4 checks passed
@zas zas deleted the fix/audit-bugs branch May 19, 2026 11:54
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