diff --git a/dev/build/Dockerfile b/dev/build/Dockerfile index 4aca4c0e1e..72bdb7a7ae 100644 --- a/dev/build/Dockerfile +++ b/dev/build/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/ietf-tools/datatracker-app-base:20260604T1454 +FROM ghcr.io/ietf-tools/datatracker-app-base:20260605T2314 LABEL maintainer="IETF Tools Team " ENV DEBIAN_FRONTEND=noninteractive diff --git a/dev/build/TARGET_BASE b/dev/build/TARGET_BASE index b64be08c8c..7ca47fd197 100644 --- a/dev/build/TARGET_BASE +++ b/dev/build/TARGET_BASE @@ -1 +1 @@ -20260604T1454 +20260605T2314 diff --git a/dev/build/gunicorn.conf.py b/dev/build/gunicorn.conf.py index a1c572a096..03e81eac5e 100644 --- a/dev/build/gunicorn.conf.py +++ b/dev/build/gunicorn.conf.py @@ -7,6 +7,10 @@ from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.instrumentation.django import DjangoInstrumentor +from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor +from opentelemetry.instrumentation.pymemcache import PymemcacheInstrumentor +from opentelemetry.instrumentation.requests import RequestsInstrumentor # Bind all ipv4 interfaces (nginx uses loopback, but k8s health checks don't) _BIND_PORT = os.environ.get("DATATRACKER_GUNICORN_BIND_PORT", "8000") @@ -167,15 +171,11 @@ def post_fork(server, worker): trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(otlp_exporter)) # Instrumentations - if "all" in enabled_telemetry or "django" in enabled_telemetry: - from opentelemetry.instrumentation.django import DjangoInstrumentor + if "all" in enabled_telemetry or "django" in enabled_telemetry: DjangoInstrumentor().instrument() - if "all" in enabled_telemetry or "psycopg2" in enabled_telemetry: - from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor + if "all" in enabled_telemetry or "psycopg2" in enabled_telemetry: Psycopg2Instrumentor().instrument() - if "all" in enabled_telemetry or "redis" in enabled_telemetry: - from opentelemetry.instrumentation.redis import RedisInstrumentor - RedisInstrumentor().instrument() - if "all" in enabled_telemetry or "requests" in enabled_telemetry: - from opentelemetry.instrumentation.requests import RequestsInstrumentor + if "all" in enabled_telemetry or "pymemcache" in enabled_telemetry: + PymemcacheInstrumentor().instrument() + if "all" in enabled_telemetry or "requests" in enabled_telemetry: RequestsInstrumentor().instrument() diff --git a/docker-compose.yml b/docker-compose.yml index f171fb261b..073d04b896 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -90,12 +90,6 @@ services: - .:/workspace - app-assets:/assets - redis: - image: redis:8 - command: ['redis-server', '--save', '10', '1', '--loglevel', 'warning'] - volumes: - - redis:/data - replicator: build: context: . @@ -175,4 +169,3 @@ volumes: app-assets: minio-data: blobdb-data: - redis: diff --git a/docker/base.Dockerfile b/docker/base.Dockerfile index 7bf4263b38..2501636049 100644 --- a/docker/base.Dockerfile +++ b/docker/base.Dockerfile @@ -59,10 +59,12 @@ RUN apt-get update --fix-missing && apt-get install -qy --no-install-recommends libxtst6 \ libmagic-dev \ libmariadb-dev \ + libmemcached-tools \ libyang2-tools \ locales \ make \ mariadb-client \ + memcached \ nano \ netcat-traditional \ nodejs \ diff --git a/ietf/settings.py b/ietf/settings.py index 6d9dc00e0e..95f2ffefd7 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -1363,93 +1363,138 @@ def skip_unreadable_post(record): TEMPLATES[0]['OPTIONS']['context_processors'] += DEV_TEMPLATE_CONTEXT_PROCESSORS if "CACHES" not in locals(): - # Would like to refuse to start when in prod mode, but this currently blocks - # the collectstatics call in the release GHA. We should probably arrange for that - # to have its own caches config, but in the meantime just let this fall back to - # the dev cache configuration. - # - # if SERVER_MODE == "production": - # raise RuntimeError("Must set CACHES in settings_local for production mode") - CACHES = { - "default": { - "BACKEND": "django.core.cache.backends.dummy.DummyCache", - # "BACKEND": "django_redis.cache.RedisCache", - # "LOCATION": "redis://redis:6379/0", - # "OPTIONS": { - # "CLIENT_CLASS": "ietf.utils.cache.SizeLimitingRedisClient", - # }, - # "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", - "VERSION": __version__, - "KEY_PREFIX": "ietf:dt", - }, - "agenda": { - "BACKEND": "django.core.cache.backends.dummy.DummyCache", - # "BACKEND": "django_redis.cache.RedisCache", - # "LOCATION": "redis://redis:6379/0", - # "OPTIONS": { - # "CLIENT_CLASS": "ietf.utils.cache.SizeLimitingRedisClient", - # }, - # No release-specific VERSION setting. - "KEY_PREFIX": "ietf:dt:agenda", - # Key function is default except with sha384-encoded key - "KEY_FUNCTION": lambda key, key_prefix, version: ( - f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}" - ), - }, - "proceedings": { - "BACKEND": "django.core.cache.backends.dummy.DummyCache", - # "BACKEND": "django_redis.cache.RedisCache", - # "LOCATION": "redis://redis:6379/0", - # "OPTIONS": { - # "CLIENT_CLASS": "ietf.utils.cache.SizeLimitingRedisClient", - # }, - # No release-specific VERSION setting. - "KEY_PREFIX": "ietf:dt:proceedings", - # Key function is default except with sha384-encoded key - "KEY_FUNCTION": lambda key, key_prefix, version: ( - f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}" - ), - }, - "sessions": { - "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": "redis://redis:6379/0", - "OPTIONS": { - "CLIENT_CLASS": "ietf.utils.cache.SizeLimitingRedisClient", + if SERVER_MODE == "production": + MEMCACHED_HOST = os.environ.get("MEMCACHED_SERVICE_HOST", "127.0.0.1") + MEMCACHED_PORT = os.environ.get("MEMCACHED_SERVICE_PORT", "11211") + CACHES = { + "default": { + "BACKEND": "ietf.utils.cache.LenientMemcacheCache", + "LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}", + "VERSION": __version__, + "KEY_PREFIX": "ietf:dt", + # Key function is default except with sha384-encoded key + "KEY_FUNCTION": lambda key, key_prefix, version: ( + f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}" + ), }, - }, - "htmlized": { - "BACKEND": "django.core.cache.backends.dummy.DummyCache", - #'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', - "LOCATION": "/var/cache/datatracker/htmlized", - "OPTIONS": { - "MAX_ENTRIES": 1000, + "agenda": { + "BACKEND": "ietf.utils.cache.LenientMemcacheCache", + "LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}", + # No release-specific VERSION setting. + "KEY_PREFIX": "ietf:dt:agenda", + # Key function is default except with sha384-encoded key + "KEY_FUNCTION": lambda key, key_prefix, version: ( + f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}" + ), }, - }, - "pdfized": { - "BACKEND": "django.core.cache.backends.dummy.DummyCache", - #'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', - "LOCATION": "/var/cache/datatracker/pdfized", - "OPTIONS": { - "MAX_ENTRIES": 1000, + "proceedings": { + "BACKEND": "ietf.utils.cache.LenientMemcacheCache", + "LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}", + # No release-specific VERSION setting. + "KEY_PREFIX": "ietf:dt:proceedings", + # Key function is default except with sha384-encoded key + "KEY_FUNCTION": lambda key, key_prefix, version: ( + f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}" + ), }, - }, - "slowpages": { - "BACKEND": "django.core.cache.backends.dummy.DummyCache", - #'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', - "LOCATION": "/var/cache/datatracker/", - "OPTIONS": { - "MAX_ENTRIES": 5000, + "sessions": { + "BACKEND": "ietf.utils.cache.LenientMemcacheCache", + "LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}", + # No release-specific VERSION setting. + "KEY_PREFIX": "ietf:dt", }, - }, - "celery-results": { - "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": "redis://redis:6379/0", - "OPTIONS": { - "CLIENT_CLASS": "django_redis.client.DefaultClient", + "htmlized": { + "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", + "LOCATION": "/a/cache/datatracker/htmlized", + "OPTIONS": { + "MAX_ENTRIES": 100000, # 100,000 + }, }, - "KEY_PREFIX": "ietf:celery", - }, - } + "pdfized": { + "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", + "LOCATION": "/a/cache/datatracker/pdfized", + "OPTIONS": { + "MAX_ENTRIES": 100000, # 100,000 + }, + }, + "slowpages": { + "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", + "LOCATION": "/a/cache/datatracker/slowpages", + "OPTIONS": { + "MAX_ENTRIES": 5000, + }, + }, + "celery-results": { + "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache", + "LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}", + "KEY_PREFIX": "ietf:celery", + }, + } + else: + CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + #'BACKEND': 'ietf.utils.cache.LenientMemcacheCache', + #'LOCATION': '127.0.0.1:11211', + #'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', + "VERSION": __version__, + "KEY_PREFIX": "ietf:dt", + }, + "agenda": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + # "BACKEND": "ietf.utils.cache.LenientMemcacheCache", + # "LOCATION": "127.0.0.1:11211", + # No release-specific VERSION setting. + "KEY_PREFIX": "ietf:dt:agenda", + # Key function is default except with sha384-encoded key + "KEY_FUNCTION": lambda key, key_prefix, version: ( + f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}" + ), + }, + "proceedings": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + # "BACKEND": "ietf.utils.cache.LenientMemcacheCache", + # "LOCATION": "127.0.0.1:11211", + # No release-specific VERSION setting. + "KEY_PREFIX": "ietf:dt:proceedings", + # Key function is default except with sha384-encoded key + "KEY_FUNCTION": lambda key, key_prefix, version: ( + f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}" + ), + }, + "sessions": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + }, + "htmlized": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + #'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', + "LOCATION": "/var/cache/datatracker/htmlized", + "OPTIONS": { + "MAX_ENTRIES": 1000, + }, + }, + "pdfized": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + #'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', + "LOCATION": "/var/cache/datatracker/pdfized", + "OPTIONS": { + "MAX_ENTRIES": 1000, + }, + }, + "slowpages": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + #'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', + "LOCATION": "/var/cache/datatracker/", + "OPTIONS": { + "MAX_ENTRIES": 5000, + }, + }, + "celery-results": { + "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache", + "LOCATION": "app:11211", + "KEY_PREFIX": "ietf:celery", + }, + } PUBLISH_IPR_STATES = ['posted', 'removed', 'removed_objfalse'] @@ -1464,6 +1509,7 @@ def skip_unreadable_post(record): loaders = TEMPLATES[0]['OPTIONS']['loaders'] loaders = tuple(l for e in loaders for l in (e[1] if isinstance(e, tuple) and "cached.Loader" in e[0] else (e,))) TEMPLATES[0]['OPTIONS']['loaders'] = loaders + SESSION_ENGINE = "django.contrib.sessions.backends.db" if 'SECRET_KEY' not in locals(): SECRET_KEY = 'PDwXboUq!=hPjnrtG2=ge#N$Dwy+wn@uivrugwpic8mxyPfHka' diff --git a/ietf/settings_test.py b/ietf/settings_test.py index 883a582a28..e7ebc13eb2 100755 --- a/ietf/settings_test.py +++ b/ietf/settings_test.py @@ -69,9 +69,6 @@ def tempdir_with_cleanup(**kwargs): PHOTOS_DIR = os.path.join(MEDIA_ROOT, PHOTOS_DIRNAME) os.mkdir(PHOTOS_DIR) -# Use database-backed sessions for tests -SESSION_ENGINE = "django.contrib.sessions.backends.db" - # Undo any developer-dependent middleware when running the tests MIDDLEWARE = [ c for c in MIDDLEWARE if not c in DEV_MIDDLEWARE ] # pyflakes:ignore diff --git a/ietf/utils/cache.py b/ietf/utils/cache.py index 60dd2c3ae3..0baa56da2d 100644 --- a/ietf/utils/cache.py +++ b/ietf/utils/cache.py @@ -1,67 +1,20 @@ -# Copyright The IETF Trust 2026, All Rights Reserved -from typing import Optional, Union, Any +# Copyright The IETF Trust 2023, All Rights Reserved +# -*- coding: utf-8 -*- -from django.core.cache import BaseCache from django.core.cache.backends.base import DEFAULT_TIMEOUT -from django_redis.client import DefaultClient, SentinelClient -from redis import Redis -from redis.typing import KeyT, EncodableT +from django.core.cache.backends.memcached import PyMemcacheCache +from pymemcache.exceptions import MemcacheServerError -from ietf.utils.log import log +from .log import log -class EncodedValueTooBig(ValueError): - def __init__(self, *args, value_len): - super().__init__(*args) - self.value_len = value_len - - -class SizeLimitingRedisClient(DefaultClient): - """Redis DefaultClient that refuses to cache large objects - - Size applies to the literal cached value, which is _after_ serialization and - compression. - - Set size limit with MAX_ENCODED_VALUE_LEN in the OPTIONS dict. Defaults to - 1 MB. - """ - def __init__(self, server, params: dict[str, Any], backend: BaseCache) -> None: - super().__init__(server, params, backend) - self.max_encoded_value_len = self._options.get("MAX_ENCODED_VALUE_LEN", 1 << 20) - - def encode(self, value: EncodableT) -> Union[bytes, int]: - encoded = super().encode(value) - if isinstance(encoded, bytes) and len(encoded) > self.max_encoded_value_len: - raise EncodedValueTooBig(value_len=len(encoded)) - return encoded - - def set( - self, - key: KeyT, - value: EncodableT, - timeout: Optional[float] = DEFAULT_TIMEOUT, - version: Optional[int] = None, - client: Optional[Redis] = None, - nx: bool = False, - xx: bool = False, - ) -> bool: +class LenientMemcacheCache(PyMemcacheCache): + """PyMemcacheCache backend that tolerates failed inserts due to object size""" + def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None): try: - return super().set(key, value, timeout, version, client, nx, xx) - except EncodedValueTooBig as err: - log( - f"Refused to cache large object for {key!r} " - f"({err.value_len} > {self.max_encoded_value_len} bytes)" - ) - return False - - -class SizeLimitingSentinelClient(SizeLimitingRedisClient, SentinelClient): - """Redis SentinelClient that refuses to cache large objects - - Size applies to the literal cached value, which is _after_ serialization and - compression. - - Set size limit with MAX_ENCODED_VALUE_LEN in the OPTIONS dict. Defaults to - 1 MB. - """ - pass + super().set(key, value, timeout, version) + except MemcacheServerError as err: + if "object too large for cache" in str(err): + log(f"Memcache failed to cache large object for {key}") + else: + raise diff --git a/k8s/auth.yaml b/k8s/auth.yaml index ef8c259933..6e63001e02 100644 --- a/k8s/auth.yaml +++ b/k8s/auth.yaml @@ -8,6 +8,8 @@ spec: selector: matchLabels: app: auth + strategy: + type: DEPLOY_STRATEGY template: metadata: labels: diff --git a/k8s/datatracker.yaml b/k8s/datatracker.yaml index 5183893bc8..af2bb6295c 100644 --- a/k8s/datatracker.yaml +++ b/k8s/datatracker.yaml @@ -8,6 +8,8 @@ spec: selector: matchLabels: app: datatracker + strategy: + type: DEPLOY_STRATEGY template: metadata: labels: diff --git a/k8s/kustomization.yaml b/k8s/kustomization.yaml index b1e278a914..769cb03517 100644 --- a/k8s/kustomization.yaml +++ b/k8s/kustomization.yaml @@ -12,5 +12,6 @@ resources: - beat.yaml - celery.yaml - datatracker.yaml + - memcached.yaml - rabbitmq.yaml - replicator.yaml diff --git a/k8s/memcached.yaml b/k8s/memcached.yaml new file mode 100644 index 0000000000..68b732d745 --- /dev/null +++ b/k8s/memcached.yaml @@ -0,0 +1,88 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: memcached +spec: + replicas: 1 + revisionHistoryLimit: 2 + selector: + matchLabels: + app: memcached + template: + metadata: + labels: + app: memcached + spec: + securityContext: + runAsNonRoot: true + containers: + # ----------------------------------------------------- + # Memcached + # ----------------------------------------------------- + - image: "memcached:1.6-alpine" + imagePullPolicy: IfNotPresent + args: ["-m", "1024"] + name: memcached + ports: + - name: memcached + containerPort: 11211 + protocol: TCP + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + # memcached image sets up uid/gid 11211 + runAsUser: 11211 + runAsGroup: 11211 + resources: + requests: + cpu: 100m + memory: 100Mi + # ----------------------------------------------------- + # Memcached Exporter for Prometheus + # ----------------------------------------------------- + - image: "quay.io/prometheus/memcached-exporter:v0.14.3" + imagePullPolicy: IfNotPresent + name: memcached-exporter + ports: + - name: metrics + containerPort: 9150 + protocol: TCP + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsUser: 65534 # nobody + runAsGroup: 65534 # nobody + resources: + requests: + cpu: 10m + memory: 20Mi + dnsPolicy: ClusterFirst + restartPolicy: Always + terminationGracePeriodSeconds: 30 +--- +apiVersion: v1 +kind: Service +metadata: + name: memcached + annotations: + k8s.grafana.com/scrape: "true" # this is not a bool + k8s.grafana.com/metrics.portName: "metrics" +spec: + type: ClusterIP + ports: + - port: 11211 + targetPort: memcached + protocol: TCP + name: memcached + - port: 9150 + targetPort: metrics + protocol: TCP + name: metrics + selector: + app: memcached diff --git a/k8s/settings_local.py b/k8s/settings_local.py index 560648c07f..20c5252ff0 100644 --- a/k8s/settings_local.py +++ b/k8s/settings_local.py @@ -315,29 +315,15 @@ def _multiline_to_list(s): DE_GFM_BINARY = "/usr/local/bin/de-gfm" IDSUBMIT_IDNITS_BINARY = "/usr/local/bin/idnits" +# Duplicating production cache from settings.py and using it whether we're in production mode or not +MEMCACHED_HOST = os.environ.get("DT_MEMCACHED_SERVICE_HOST", "127.0.0.1") +MEMCACHED_PORT = os.environ.get("DT_MEMCACHED_SERVICE_PORT", "11211") from ietf import __version__ -# Common config for redis caches -REDIS_SENTINEL_SERVICE = os.environ.get("DATATRACKER_REDIS_SENTINEL_HOST") -REDIS_SENTINEL_PORT = os.environ.get("DATATRACKER_REDIS_SENTINEL_PORT", "26379") -DJANGO_REDIS_CONNECTION_FACTORY = "django_redis.pool.SentinelConnectionFactory" -REDIS_CACHE_CONFIG_COMMON = { - "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": "redis://dt-master/0", - "OPTIONS": { - "CLIENT_CLASS": "ietf.utils.cache.SizeLimitingSentinelClient", - "MAX_ENCODED_VALUE_LEN": int( - os.environ.get("DATATRACKER_REDIS_MAX_ENCODED_VALUE_LEN", 1 << 20) - ), - "SENTINELS": [(REDIS_SENTINEL_SERVICE, REDIS_SENTINEL_PORT)], - "SENTINEL_KWARGS": {}, - "CONNECTION_POOL_CLASS": "redis.sentinel.SentinelConnectionPool", - }, -} - CACHES = { - "default": REDIS_CACHE_CONFIG_COMMON - | { + "default": { + "BACKEND": "ietf.utils.cache.LenientMemcacheCache", + "LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}", "VERSION": __version__, "KEY_PREFIX": "ietf:dt", # Key function is default except with sha384-encoded key @@ -345,8 +331,9 @@ def _multiline_to_list(s): f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}" ), }, - "agenda": REDIS_CACHE_CONFIG_COMMON - | { + "agenda": { + "BACKEND": "ietf.utils.cache.LenientMemcacheCache", + "LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}", # No release-specific VERSION setting. "KEY_PREFIX": "ietf:dt:agenda", # Key function is default except with sha384-encoded key @@ -354,8 +341,9 @@ def _multiline_to_list(s): f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}" ), }, - "proceedings": REDIS_CACHE_CONFIG_COMMON - | { + "proceedings": { + "BACKEND": "ietf.utils.cache.LenientMemcacheCache", + "LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}", # No release-specific VERSION setting. "KEY_PREFIX": "ietf:dt:proceedings", # Key function is default except with sha384-encoded key @@ -363,8 +351,9 @@ def _multiline_to_list(s): f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}" ), }, - "sessions": REDIS_CACHE_CONFIG_COMMON - | { + "sessions": { + "BACKEND": "ietf.utils.cache.LenientMemcacheCache", + "LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}", # No release-specific VERSION setting. "KEY_PREFIX": "ietf:dt", }, @@ -389,8 +378,9 @@ def _multiline_to_list(s): "MAX_ENTRIES": 5000, }, }, - "celery-results": REDIS_CACHE_CONFIG_COMMON - | { + "celery-results": { + "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache", + "LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}", "KEY_PREFIX": "ietf:celery", }, } diff --git a/requirements.txt b/requirements.txt index bf7d1d26e0..31e8ea69d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,6 @@ django-debug-toolbar>=6.0.0 django-filter>=24.3 django-markup>=1.10 # Limited use - need to reconcile against direct use of markdown django-oidc-provider==0.8.2 # 0.8.3 changes logout flow and claim return -django-redis>=6.0.0 django-simple-history>=3.10.1 django-storages>=1.14.6 django-stubs>=4.2.7,<5 # The django-stubs version used determines the the mypy version indicated below @@ -59,8 +58,8 @@ oic>=1.7.0 # Used only by tests opentelemetry-sdk>=1.38.0 opentelemetry-instrumentation-django>=0.59b0 opentelemetry-instrumentation-psycopg2>=0.59b0 +opentelemetry-instrumentation-pymemcache>=0.59b0 opentelemetry-instrumentation-requests>=0.59b0 -opentelemetry-instrumentation-redis>=0.63b1 opentelemetry-exporter-otlp-proto-http>=1.38.0 pillow>=11.3.0 psycopg2>=2.9.10 @@ -73,9 +72,11 @@ python-dateutil>=2.9.0 types-python-dateutil>=2.9.0 python-json-logger>=3.3.0 python-magic==0.4.18 # Versions beyond the yanked .19 and .20 introduce form failures +pymemcache>=4.0.0 # for django.core.cache.backends.memcached.PyMemcacheCache python-mimeparse>=2.0.0 # from TastyPie pytz==2025.2 # Pinned as changes need to be vetted for their effect on Meeting fields types-pytz==2025.2.0.20251108 # match pytz version +typesense>=2.0.0 requests>=2.32.4 types-requests>=2.32.4 requests-mock>=1.12.1 @@ -85,7 +86,6 @@ selenium>=4.34.2 tblib>=3.1.0 # So that the django test runner provides tracebacks tlds>=2022042700 # Used to teach bleach about which TLDs currently exist tqdm>=4.67.1 -typesense>=2.0.0 unidecode>=1.4.0 urllib3>=2.5.0 weasyprint>=66.0