Skip to content

Commit 496bf20

Browse files
committed
Adopt multi get
1 parent 82cafeb commit 496bf20

4 files changed

Lines changed: 64 additions & 34 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ dependencies = [
33
"uhashring >= 2.3, < 3",
44
"marisa-trie >= 1.2.1, < 1.3",
55
"zstandard >= 0.25.0, < 0.26",
6-
"meta-memcache-socket >= 0.2.0, < 0.3.0",
6+
"meta-memcache-socket >= 0.2.1, < 0.3.0",
77
]
88
authors = [{ name = "Guillermo Perez", email = "bisho@revenuecat.com" }]
99
license = { text = "MIT License" }

src/meta_memcache/executors/default.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,37 @@ def exec_on_pool(
159159
else:
160160
return NotStored()
161161

162+
def _exec_multi_get_on_pool(
163+
self,
164+
pool: ConnectionPool,
165+
keys: List[Union[bytes, str]],
166+
flags: Optional[RequestFlags],
167+
raise_on_server_error: Optional[bool] = None,
168+
) -> Dict[Key, MemcacheResponse]:
169+
try:
170+
conn = pool.pop_connection()
171+
error = False
172+
try:
173+
responses = conn.meta_multiget(keys, flags)
174+
except Exception as e:
175+
error = True
176+
raise MemcacheServerError(pool.server, "Memcache error") from e
177+
finally:
178+
pool.release_connection(conn, error=error)
179+
return {
180+
key: self._process_response(response)
181+
for key, response in zip(keys, responses, strict=True)
182+
}
183+
except MemcacheServerError:
184+
raise_on_server_error = (
185+
raise_on_server_error
186+
if raise_on_server_error is not None
187+
else self._raise_on_server_error
188+
)
189+
if raise_on_server_error:
190+
raise
191+
return {key: Miss() for key in keys}
192+
162193
def exec_multi_on_pool( # noqa: C901
163194
self,
164195
pool: ConnectionPool,
@@ -168,12 +199,16 @@ def exec_multi_on_pool( # noqa: C901
168199
track_write_failures: bool,
169200
raise_on_server_error: Optional[bool] = None,
170201
) -> Dict[Key, MemcacheResponse]:
202+
if command == MetaCommand.META_GET:
203+
return self._exec_multi_get_on_pool(
204+
pool, [key.key for key, _ in key_values], flags, raise_on_server_error
205+
)
206+
171207
results: Dict[Key, MemcacheResponse] = {}
172208
try:
173209
conn = pool.pop_connection()
174210
error = False
175211
try:
176-
# with pool.get_connection() as conn:
177212
for key, value in key_values:
178213
cmd_value, flags = (
179214
(None, flags)

tests/commands_test.py

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,7 +1131,7 @@ def test_multi_get_with_values(
11311131
int_encoded = serializer.serialize(Key("k2"), 42)
11321132
list_encoded = serializer.serialize(Key("k3"), [1, 2])
11331133

1134-
memcache_socket.get_response.side_effect = [
1134+
memcache_socket.meta_multiget.return_value = [
11351135
Value(
11361136
size=len(str_encoded.data),
11371137
value=str_encoded.data,
@@ -1151,12 +1151,9 @@ def test_multi_get_with_values(
11511151

11521152
results = cache_client.multi_get(keys=[Key("k1"), Key("k2"), Key("k3")])
11531153

1154-
calls = memcache_socket.send_meta_get.call_args_list
1155-
assert len(calls) == 3
1156-
assert calls[0][0][0] == "k1"
1157-
assert calls[1][0][0] == "k2"
1158-
assert calls[2][0][0] == "k3"
1159-
assert memcache_socket.get_response.call_count == 3
1154+
memcache_socket.meta_multiget.assert_called_once()
1155+
keys_sent = memcache_socket.meta_multiget.call_args.args[0]
1156+
assert keys_sent == ["k1", "k2", "k3"]
11601157
assert results == {
11611158
Key("k1"): "hello",
11621159
Key("k2"): 42,
@@ -1167,8 +1164,8 @@ def test_multi_get_with_values(
11671164
def test_multi_get_with_touch_ttl(
11681165
memcache_socket: MemcacheSocket, cache_client: CacheClient
11691166
) -> None:
1170-
"""Test multi_get passes touch_ttl through to the pipelining path."""
1171-
memcache_socket.get_response.side_effect = [
1167+
"""Test multi_get passes touch_ttl through to meta_multiget."""
1168+
memcache_socket.meta_multiget.return_value = [
11721169
Miss(),
11731170
Miss(),
11741171
]
@@ -1178,54 +1175,49 @@ def test_multi_get_with_touch_ttl(
11781175
touch_ttl=300,
11791176
)
11801177

1181-
calls = memcache_socket.send_meta_get.call_args_list
1182-
assert len(calls) == 2
1183-
assert calls[0][0][0] == "a"
1184-
assert calls[1][0][0] == "b"
1185-
# Verify the flags contain the touch TTL
1186-
for c in calls:
1187-
flags = c[0][1]
1188-
assert flags is not None
1189-
assert flags.cache_ttl == 300
1178+
memcache_socket.meta_multiget.assert_called_once()
1179+
keys_sent = memcache_socket.meta_multiget.call_args.args[0]
1180+
flags = memcache_socket.meta_multiget.call_args.kwargs["request_flags"]
1181+
assert keys_sent == ["a", "b"]
1182+
assert flags is not None
1183+
assert flags.cache_ttl == 300
11901184
assert results == {Key("a"): None, Key("b"): None}
11911185

11921186

11931187
def test_multi_get_with_recache(
11941188
memcache_socket: MemcacheSocket, cache_client: CacheClient
11951189
) -> None:
1196-
"""Test multi_get with recache_policy passes recache_ttl in flags."""
1197-
memcache_socket.get_response.side_effect = [Miss()]
1190+
"""Test multi_get with recache_policy passes recache_ttl in request flags."""
1191+
memcache_socket.meta_multiget.return_value = [Miss()]
11981192

11991193
cache_client.multi_get(
12001194
keys=[Key("x")],
12011195
recache_policy=RecachePolicy(ttl=60),
12021196
)
12031197

1204-
calls = memcache_socket.send_meta_get.call_args_list
1205-
assert len(calls) == 1
1206-
flags = calls[0][0][1]
1198+
memcache_socket.meta_multiget.assert_called_once()
1199+
flags = memcache_socket.meta_multiget.call_args.kwargs["request_flags"]
12071200
assert flags is not None
12081201
assert flags.recache_ttl == 60
12091202

12101203

12111204
def test_meta_multiget_no_reply(
12121205
memcache_socket: MemcacheSocket, cache_client: CacheClient
12131206
) -> None:
1214-
"""Test meta_multiget with no_reply flag uses pipelining path correctly.
1207+
"""Test meta_multiget with no_reply uses socket batch API."""
1208+
memcache_socket.meta_multiget.return_value = [
1209+
Success(flags=ResponseFlags()),
1210+
Success(flags=ResponseFlags()),
1211+
]
12151212

1216-
With no_reply on mg, the executor skips get_response and returns Success.
1217-
"""
12181213
results = cache_client.meta_multiget(
12191214
keys=[Key("a"), Key("b")],
12201215
flags=RequestFlags(no_reply=True, cache_ttl=60),
12211216
)
12221217

1223-
calls = memcache_socket.send_meta_get.call_args_list
1224-
assert len(calls) == 2
1225-
assert calls[0][0][0] == "a"
1226-
assert calls[1][0][0] == "b"
1227-
# no_reply: get_response not called, executor returns Success directly
1228-
memcache_socket.get_response.assert_not_called()
1218+
memcache_socket.meta_multiget.assert_called_once()
1219+
keys_sent = memcache_socket.meta_multiget.call_args.args[0]
1220+
assert keys_sent == ["a", "b"]
12291221
assert all(isinstance(r, Success) for r in results.values())
12301222

12311223

tests/conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ def mock_memcache_socket(mocker, mock_raw_socket) -> MagicMock:
3939
mock.get_version.return_value = ServerVersion.STABLE
4040
_success = Success(flags=ResponseFlags())
4141
mock.meta_get.return_value = Miss()
42+
mock.meta_multiget.side_effect = lambda keys, request_flags=None: [
43+
Miss() for _ in keys
44+
]
4245
mock.meta_set.return_value = _success
4346
mock.meta_delete.return_value = _success
4447
mock.meta_arithmetic.return_value = _success

0 commit comments

Comments
 (0)