Skip to content

Commit 1e865a3

Browse files
author
Vasyl Dizhak
authored
#609, use pipeline for delete_pattern (#617)
* #609, use pipeline for delete_pattern * #609, add changelog description
1 parent 38e630f commit 1e865a3

File tree

4 files changed

+38
-2
lines changed

4 files changed

+38
-2
lines changed

README.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,15 @@ pattern syntax as the ``keys`` function and returns the number of deleted keys.
483483
>>> from django.core.cache import cache
484484
>>> cache.delete_pattern("foo_*")
485485
486+
To achieve the best performance while deleting many keys, you should set ``DJANGO_REDIS_SCAN_ITERSIZE`` to a relatively
487+
high number (e.g., 100_000) by default in Django settings or pass it directly to the ``delete_pattern``.
488+
489+
490+
.. code-block:: pycon
491+
492+
>>> from django.core.cache import cache
493+
>>> cache.delete_pattern("foo_*", itersize=100_000)
494+
486495
Redis native commands
487496
~~~~~~~~~~~~~~~~~~~~~
488497

changelog.d/609.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Speed up deleting multiple keys by a pattern with pipelines and larger itersize

django_redis/client/default.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,13 @@ def delete_pattern(
393393

394394
try:
395395
count = 0
396+
pipeline = client.pipeline()
397+
396398
for key in client.scan_iter(match=pattern, count=itersize):
397-
client.delete(key)
399+
pipeline.delete(key)
398400
count += 1
401+
pipeline.execute()
402+
399403
return count
400404
except _main_exceptions as e:
401405
raise ConnectionInterrupted(connection=client) from e

tests/test_client.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from typing import Iterable
2-
from unittest.mock import Mock, patch
2+
from unittest.mock import Mock, call, patch
33

44
import pytest
55
from django.core.cache import DEFAULT_CACHE_ALIAS
@@ -104,6 +104,28 @@ def test_delete_pattern_calls_scan_iter_with_count_if_itersize_given(
104104
count=90210, match=make_pattern_mock.return_value
105105
)
106106

107+
@patch("test_client.DefaultClient.make_pattern")
108+
@patch("test_client.DefaultClient.get_client", return_value=Mock())
109+
@patch("test_client.DefaultClient.__init__", return_value=None)
110+
def test_delete_pattern_calls_pipeline_delete_and_execute(
111+
self, init_mock, get_client_mock, make_pattern_mock
112+
):
113+
client = DefaultClient()
114+
client._backend = Mock()
115+
client._backend.key_prefix = ""
116+
get_client_mock.return_value.scan_iter.return_value = [":1:foo", ":1:foo-a"]
117+
get_client_mock.return_value.pipeline.return_value = Mock()
118+
get_client_mock.return_value.pipeline.return_value.delete = Mock()
119+
get_client_mock.return_value.pipeline.return_value.execute = Mock()
120+
121+
client.delete_pattern(pattern="foo*")
122+
123+
assert get_client_mock.return_value.pipeline.return_value.delete.call_count == 2
124+
get_client_mock.return_value.pipeline.return_value.delete.assert_has_calls(
125+
[call(":1:foo"), call(":1:foo-a")]
126+
)
127+
get_client_mock.return_value.pipeline.return_value.execute.assert_called_once()
128+
107129

108130
class TestShardClient:
109131
@patch("test_client.DefaultClient.make_pattern")

0 commit comments

Comments
 (0)