Skip to content

Commit f14477c

Browse files
authored
Use TTLCache for _BACKENDS_CACHE (#3389)
* Use TTLCache for _BACKENDS_CACHE * Set ttl=300
1 parent a6c4c20 commit f14477c

File tree

1 file changed

+12
-9
lines changed
  • src/dstack/_internal/server/services/backends

1 file changed

+12
-9
lines changed

src/dstack/_internal/server/services/backends/__init__.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Callable, Coroutine, Dict, List, Optional, Tuple
55
from uuid import UUID
66

7+
from cachetools import TTLCache
78
from sqlalchemy import delete, update
89
from sqlalchemy.ext.asyncio import AsyncSession
910

@@ -187,27 +188,25 @@ async def delete_backends(
187188
BackendTuple = Tuple[BackendModel, Backend]
188189

189190

190-
_BACKENDS_CACHE_LOCKS = {}
191-
_BACKENDS_CACHE: Dict[UUID, Dict[BackendType, BackendTuple]] = {}
191+
_BACKENDS_CACHE_LOCKS: Dict[UUID, asyncio.Lock] = {}
192+
_BACKENDS_CACHE = TTLCache[UUID, Dict[BackendType, BackendTuple]](maxsize=1000, ttl=300)
192193

193194

194195
def _get_project_cache_lock(project_id: UUID) -> asyncio.Lock:
195196
return _BACKENDS_CACHE_LOCKS.setdefault(project_id, asyncio.Lock())
196197

197198

198199
async def get_project_backends_with_models(project: ProjectModel) -> List[BackendTuple]:
199-
backends = []
200200
async with _get_project_cache_lock(project.id):
201201
key = project.id
202-
project_backends_cache = _BACKENDS_CACHE.setdefault(key, {})
202+
project_backends = _BACKENDS_CACHE.get(key, {})
203203
for backend_model in project.backends:
204-
cached_backend = project_backends_cache.get(backend_model.type)
204+
cached_backend = project_backends.get(backend_model.type)
205205
if (
206206
cached_backend is not None
207207
and cached_backend[0].config == backend_model.config
208208
and cached_backend[0].auth == backend_model.auth
209209
):
210-
backends.append(cached_backend)
211210
continue
212211
configurator = get_configurator(backend_model.type)
213212
if configurator is None:
@@ -231,9 +230,13 @@ async def get_project_backends_with_models(project: ProjectModel) -> List[Backen
231230
backend_model.type.value,
232231
)
233232
continue
234-
backends.append((backend_model, backend))
235-
_BACKENDS_CACHE[key][backend_model.type] = (backend_model, backend)
236-
return backends
233+
project_backends[backend_model.type] = (backend_model, backend)
234+
# `__setitem__()` will also expire the cache.
235+
# Note that there is no global cache lock so a race condition is possible:
236+
# one coroutine updates/re-assigns backends expired by another coroutine.
237+
# This is ok since the only effect is that project's cache gets restored.
238+
_BACKENDS_CACHE[key] = project_backends
239+
return list(project_backends.values())
237240

238241

239242
_get_project_backend_with_model_by_type = None

0 commit comments

Comments
 (0)