Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions invenio.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ from zenodo_rdm.custom_fields import (
NAMESPACES,
)
from zenodo_rdm.custom_schemes import is_edmo
from zenodo_rdm.db import routed_bind
from zenodo_rdm.files import storage_factory
from zenodo_rdm.github.schemas import CitationMetadataSchema
from zenodo_rdm.legacy.resources import record_serializers
Expand Down Expand Up @@ -149,6 +150,114 @@ if _parse_env_bool("INVENIO_PGBOUNCER_ENABLED", False):
else:
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://zenodo:zenodo@localhost/zenodo"

# fmt: off
ZENODO_UI_READ_ONLY_ENDPOINTS = [
Copy link
Member Author

Choose a reason for hiding this comment

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

These need a second look. I removed from the initiali list all the POST/PUT/DELETE routes and did a manual pass to make sure none of these routes have hidden DB writes (like e.g. the /confirm route).

Copy link
Contributor

Choose a reason for hiding this comment

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

Are you observing high traffic in all these endpoints? Otherwise you can evaluate adding only a subset of those and then progressively expanding it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not really, but I thought narrowing down all the read endpoints and filtering afterwards will make sure we don't miss anything (like e.g. the legacy redirect endpoints, which are linked to around the web already).

"invenio_app_rdm.frontpage_view_function", # /
"invenio_app_rdm_records.record_from_pid", # /<any(doi,oai):pid_scheme>/<path:pid_value>
"invenio_redirector.redirect_about", # /about
"invenio_formatter_badges.badge", # png):ext>
"invenio_github_badge.index", # /badge/<int:repo_github_id>.svg
"invenio_github_badge.index_old", # /badge/<int:user_id>/<path:repo_name>.svg
"invenio_github_badge.latest_doi", # /badge/latestdoi/<int:github_id>
"invenio_github_badge.latest_doi_old", # /badge/latestdoi/<int:user_id>/<path:repo_name>
"invenio_redirector.redirect_communities_search_legacy", # /collection/<type>
"invenio_redirector.redirect_collections_about", # /collection/user-<id>
"invenio_communities.communities_frontpage", # /communities
"invenio_communities.communities_search", # /communities-search
"invenio_redirector.redirect_communities_curate", # /communities/<community_id>/curate
"invenio_redirector.redirect_communities_edt", # /communities/<community_id>/edit
"invenio_redirector.redirect_communities_search", # /communities/<community_id>/search
"invenio_app_rdm_communities.communities_home", # /communities/<pid_value>/
"invenio_communities.communities_about", # /communities/<pid_value>/about
"invenio_app_rdm_communities.communities_browse", # /communities/<pid_value>/browse
"invenio_communities.communities_subcommunities", # /communities/<pid_value>/browse/subcommunities
"invenio_app_rdm_communities.community_collection", # /communities/<pid_value>/collections/<tree_slug>/<collection_slug>
"invenio_communities.community_theme_css_config", # /communities/<pid_value>/community-theme-<revision>.css
"invenio_communities.communities_curation_policy", # /communities/<pid_value>/curation-policy
"invenio_communities.members", # /communities/<pid_value>/members
"invenio_app_rdm_communities.community_static_page", # /communities/<pid_value>/pages/<path:page_slug>
"invenio_app_rdm_communities.communities_detail", # /communities/<pid_value>/records
"invenio_redirector.redirect_communities_about_legacy", # /communities/about/<id>
"invenio_communities.deprecated_communities_search", # /communities/search
"invenio_redirector.redirect_contact", # /contact
"invenio_redirector.redirect_dev", # /dev
"invenio_redirector.redirect_donate", # /donate
"invenio_redirector.redirect_faq", # /faq
"invenio_redirector.redirect_features", # /features
"invenio_app_rdm.help_search", # /help/search
"invenio_app_rdm.help_statistics", # /help/statistics
"invenio_app_rdm.help_versioning", # /help/versioning
"invenio_redirector.redirect_policies", # /policies
"invenio_redirector.redirect_privacy-policy", # /privacy-policy
"invenio_redirector.redirect_record_detail", # /record/<pid_value>
"invenio_redirector.redirect_record_export", # /record/<pid_value>/export/<export_format>
"invenio_redirector.redirect_record_file_download", # /record/<pid_value>/files/<path:filename>
"invenio_redirector.redirect_formats_to_media_files", # /record/<pid_value>/formats
"invenio_redirector.redirect_record_file_preview", # /record/<pid_value>/preview/<path:filename>
"invenio_redirector.redirect_record_thumbnail", # /record/<pid_value>/thumb<size>
"invenio_app_rdm_records.record_detail", # /records/<pid_value>
"invenio_app_rdm_records.record_export", # /records/<pid_value>/export/<export_format>
"invenio_redirector.redirect_legacy_record_export_view_dcat", # /records/<pid_value>/export/dcat
"invenio_redirector.redirect_legacy_record_export_view_dcite4", # /records/<pid_value>/export/dcite4
"invenio_redirector.redirect_legacy_record_export_view_hx", # /records/<pid_value>/export/hx
"invenio_redirector.redirect_legacy_record_export_view_xd", # /records/<pid_value>/export/xd
"invenio_redirector.redirect_legacy_record_export_view_xm", # /records/<pid_value>/export/xm
"invenio_app_rdm_records.record_file_download", # /records/<pid_value>/files/<path:filename>
"invenio_app_rdm_records.record_latest", # /records/<pid_value>/latest
"invenio_app_rdm_records.record_media_file_download", # /records/<pid_value>/media-files/<path:filename>
"invenio_app_rdm_records.record_file_preview", # /records/<pid_value>/preview/<path:filename>
"invenio_app_rdm_records.record_thumbnail", # /records/<pid_value>/thumb<int:size>
"invenio_search_ui.search", # /search
"invenio_redirector.redirect_terms", # /terms
]

ZENODO_API_READ_ONLY_ENDPOINTS = [
"collections.search_records", # /collections/<id>/records
"communities.search", # /communities
"communities.read", # /communities/<pid_value>
"communities.featured_list", # /communities/<pid_value>/featured
"communities.read_logo", # /communities/<pid_value>/logo
"community_members.search", # /communities/<pid_value>/members
"community_members.search_public", # /communities/<pid_value>/members/public
"community-records.search", # /communities/<pid_value>/records
"communities.search_subcommunities", # /communities/<pid_value>/subcommunities
"communities.featured_communities_search", # /communities/featured
"exporter.list_object_versions", # /exporter
"exporter.get_object_version_content", # /exporter/<path:key>
"exporter.get_object_version_content", # /exporter/<path:key>/<uuid:version_id>
"iiif.base", # /iiif/<path:uuid>
"iiif.image_api", # /iiif/<path:uuid>/<region>/<size>/<rotation>/<quality>.<image_format>
"iiif.canvas", # /iiif/<path:uuid>/canvas/<path:file_name>
"iiif.info", # /iiif/<path:uuid>/info.json
"iiif.manifest", # /iiif/<path:uuid>/manifest
"iiif.sequence", # /iiif/<path:uuid>/sequence/default
"records.search", # /records
"zenodo_api_redirector.redirect_records_search_slash", # /records/
"records.read", # /records/<pid_value>
"record_files.search", # /records/<pid_value>/files
"record_files.read_archive", # /records/<pid_value>/files-archive
"record_files.read", # /records/<pid_value>/files/<path:key>
"record_files.read_content", # /records/<pid_value>/files/<path:key>/content
"record_media_files.search", # /records/<pid_value>/media-files
"record_media_files.read_archive", # /records/<pid_value>/media-files-archive
"record_media_files.read", # /records/<pid_value>/media-files/<key>
"record_media_files.read_content", # /records/<pid_value>/media-files/<key>/content
"records.search_versions", # /records/<pid_value>/versions
"records.read_latest", # /records/<pid_value>/versions/latest
]
# fmt: on

ZENODO_READ_REPLICA_ENDPOINTS = (
ZENODO_UI_READ_ONLY_ENDPOINTS + ZENODO_API_READ_ONLY_ENDPOINTS
)
if IS_LOCAL_DEV:
SQLALCHEMY_BINDS = {
"read_replica": "postgresql+psycopg2://zenodo_ro:zenodo_ro@localhost/zenodo"
}

DB_SESSION_BIND_FUNC = routed_bind
Comment on lines +253 to +258
Copy link
Member Author

Choose a reason for hiding this comment

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

This needs some additional thought regarding how it will be rolled out for QA/Production via env config



# Invenio-App
# ===========
# See https://invenio-app.readthedocs.io/en/latest/configuration.html
Expand Down
22 changes: 22 additions & 0 deletions site/zenodo_rdm/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 CERN.
#
# ZenodoRDM is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Database helpers."""

from flask import current_app, request
from flask_login import current_user


def routed_bind(session, *args, **kwargs):
"""Route session to the appropriate database depending on the request context.

Routes unauthenticated/anonymous GET and HEAD requests of configured endpoints to
the configured read replica SQLAlchemy bind.
"""
if request and request.method in ["GET", "HEAD"]:
read_endpoints = current_app.config.get("ZENODO_READ_REPLICA_ENDPOINTS", [])
if current_user.is_anonymous and request.endpoint in read_endpoints:
return session._db.engines["read_replica"]