Delete unused redis activity keys from UI, first pass

This commit is contained in:
Mouse Reeve 2024-02-08 20:47:47 -08:00
parent f608ab17bb
commit cbe1014fb3
5 changed files with 137 additions and 0 deletions

View file

@ -85,6 +85,10 @@
{% url 'settings-celery' as url %} {% url 'settings-celery' as url %}
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Celery status" %}</a> <a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Celery status" %}</a>
</li> </li>
<li>
{% url 'settings-redis' as url %}
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Redis status" %}</a>
</li>
<li> <li>
{% url 'settings-schedules' as url %} {% url 'settings-schedules' as url %}
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Scheduled tasks" %}</a> <a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Scheduled tasks" %}</a>

View file

@ -0,0 +1,68 @@
{% extends 'settings/layout.html' %}
{% load humanize %}
{% load i18n %}
{% block title %}{% trans "Redis Status" %}{% endblock %}
{% block header %}{% trans "Redis Status" %}{% endblock %}
{% block panel %}
{% if info %}
<section class="block content">
<h2>{% trans "Info" %}</h2>
<div class="columns has-text-centered is-multiline">
<div class="column is-4">
<div class="notification">
<p class="header">{% trans "Used memory" %}</p>
<p class="title is-5">{{ info.used_memory_human }}</p>
</div>
</div>
<div class="column is-4">
<div class="notification">
<p class="header">{% trans "Total system memory" %}</p>
<p class="title is-5">{{ info.total_system_memory_human }}</p>
</div>
</div>
<div class="column is-4">
<div class="notification">
<p class="header">{% trans "Keys" %}</p>
<p class="title is-5">{{ info.db0.keys | intcomma }}</p>
</div>
</div>
</div>
</section>
<section>
{{ dead_key_count }}
<form name="erase-keys" method="POST" action="{% url 'settings-redis' %}">
{% csrf_token %}
<button type="submit" class="button">go</button>
</form>
<form name="erase-keys" method="POST" action="{% url 'settings-redis' %}">
{% csrf_token %}
<input type="hidden" name="dry_run" value="True">
<button type="submit" class="button">dry run</button>
</form>
</section>
{% else %}
<div class="notification is-danger is-flex is-align-items-start">
<span class="icon icon-warning is-size-4 pr-3" aria-hidden="true"></span>
<span>
{% trans "Could not connect to Redis Activity" %}
</span>
</div>
{% endif %}
{% if errors %}
<div class="block content">
<h2>{% trans "Errors" %}</h2>
{% for error in errors %}
<pre>{{ error }}</pre>
{% endfor %}
</div>
{% endif %}
{% endblock %}

View file

@ -369,6 +369,7 @@ urlpatterns = [
re_path( re_path(
r"^settings/celery/ping/?$", views.celery_ping, name="settings-celery-ping" r"^settings/celery/ping/?$", views.celery_ping, name="settings-celery-ping"
), ),
re_path(r"^settings/redis/?$", views.RedisStatus.as_view(), name="settings-redis"),
re_path( re_path(
r"^settings/schedules/(?P<task_id>\d+)?$", r"^settings/schedules/(?P<task_id>\d+)?$",
views.ScheduledTasks.as_view(), views.ScheduledTasks.as_view(),

View file

@ -5,6 +5,7 @@ from .admin.announcements import EditAnnouncement, delete_announcement
from .admin.automod import AutoMod, automod_delete, run_automod from .admin.automod import AutoMod, automod_delete, run_automod
from .admin.automod import schedule_automod_task, unschedule_automod_task from .admin.automod import schedule_automod_task, unschedule_automod_task
from .admin.celery_status import CeleryStatus, celery_ping from .admin.celery_status import CeleryStatus, celery_ping
from .admin.redis import RedisStatus
from .admin.schedule import ScheduledTasks from .admin.schedule import ScheduledTasks
from .admin.dashboard import Dashboard from .admin.dashboard import Dashboard
from .admin.federation import Federation, FederatedServer from .admin.federation import Federation, FederatedServer

View file

@ -0,0 +1,63 @@
""" redis cache status """
from django.contrib.auth.decorators import login_required, permission_required
from django.http import HttpResponse
from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views import View
import redis
from bookwyrm import models, settings
r = redis.from_url(settings.REDIS_ACTIVITY_URL)
# pylint: disable= no-self-use
@method_decorator(login_required, name="dispatch")
@method_decorator(
permission_required("bookwyrm.edit_instance_settings", raise_exception=True),
name="dispatch",
)
class RedisStatus(View):
"""Are your tasks running? Well you'd better go catch them"""
def get(self, request):
"""See workers and active tasks"""
data = {"errors": []}
try:
data["info"] = r.info
# pylint: disable=broad-except
except Exception as err:
data["errors"].append(err)
return TemplateResponse(request, "settings/redis.html", data)
# pylint: disable=unused-argument
def post(self, request):
"""Erase invalid keys"""
dry_run = request.POST.get("dry_run")
patterns = [":*:*"] # this pattern is a django cache with no prefix
for user_id in models.User.objects.filter(
is_deleted=True, local=True
).values_list("id", flat=True):
patterns.append(f"{user_id}-*")
deleted_count = 0
for pattern in patterns:
deleted_count += erase_keys(pattern, dry_run=dry_run)
if dry_run:
return HttpResponse(f"{deleted_count} keys identified for deletion")
return HttpResponse(f"{deleted_count} keys deleted")
def erase_keys(pattern, count=1000, dry_run=False):
"""Delete all redis activity keys according to a provided regex pattern"""
pipeline = r.pipeline()
key_count = 0
for keys in r.scan_iter(match=pattern, count=count):
key_count += len(keys)
if not dry_run:
for key in keys:
pipeline.delete(key)
if not dry_run:
pipeline.execute()
return key_count