Skip to content

Commit fb53cf8

Browse files
authored
Parallelize get_project_backends_with_models (#3787)
1 parent af3667b commit fb53cf8

1 file changed

Lines changed: 44 additions & 10 deletions

File tree

  • src/dstack/_internal/server/services/backends

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

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ async def get_project_backends_with_models(project: ProjectModel) -> List[Backen
279279
async with _get_project_cache_lock(project.id):
280280
key = project.id
281281
project_backends = _BACKENDS_CACHE.get(key, {})
282+
to_init: List[Tuple[BackendModel, Configurator, StoredBackendRecord]] = []
282283
for backend_model in project.backends:
283284
cached_backend = project_backends.get(backend_model.type)
284285
if (
@@ -300,16 +301,41 @@ async def get_project_backends_with_models(project: ProjectModel) -> List[Backen
300301
backend_model.type.value,
301302
)
302303
continue
303-
try:
304-
backend_record = get_stored_backend_record(backend_model)
305-
backend = await run_async(configurator.get_backend, backend_record)
306-
except (BackendInvalidCredentialsError, BackendAuthError):
307-
logger.warning(
308-
"Credentials for %s backend are invalid. Backend will be ignored.",
309-
backend_model.type.value,
310-
)
311-
continue
312-
project_backends[backend_model.type] = (backend_model, backend)
304+
backend_record = get_stored_backend_record(backend_model)
305+
to_init.append((backend_model, configurator, backend_record))
306+
307+
if to_init:
308+
t0 = time.time()
309+
tasks = [
310+
_get_backend_tracked(configurator, backend_record)
311+
for _, configurator, backend_record in to_init
312+
]
313+
results = await asyncio.gather(*tasks, return_exceptions=True)
314+
initialized_results = []
315+
for (backend_model, _, _), result in zip(to_init, results):
316+
if isinstance(result, BaseException):
317+
if isinstance(result, (BackendInvalidCredentialsError, BackendAuthError)):
318+
logger.warning(
319+
"Credentials for %s backend are invalid. Backend will be ignored.",
320+
backend_model.type.value,
321+
)
322+
else:
323+
logger.error(
324+
"Failed to initialize %s backend. Backend will be ignored.",
325+
backend_model.type.value,
326+
exc_info=result,
327+
)
328+
else:
329+
backend, duration = result
330+
project_backends[backend_model.type] = (backend_model, backend)
331+
initialized_results.append(f"{backend_model.type.value}={duration:.1f}s")
332+
logger.debug(
333+
"Initialized %d backends in %.1fs: %s",
334+
len(initialized_results),
335+
time.time() - t0,
336+
", ".join(initialized_results),
337+
)
338+
313339
# `__setitem__()` will also expire the cache.
314340
# Note that there is no global cache lock so a race condition is possible:
315341
# one coroutine updates/re-assigns backends expired by another coroutine.
@@ -318,6 +344,14 @@ async def get_project_backends_with_models(project: ProjectModel) -> List[Backen
318344
return list(project_backends.values())
319345

320346

347+
async def _get_backend_tracked(
348+
configurator: Configurator, backend_record: StoredBackendRecord
349+
) -> Tuple[Backend, float]:
350+
t = time.time()
351+
backend = await run_async(configurator.get_backend, backend_record)
352+
return backend, time.time() - t
353+
354+
321355
_get_project_backend_with_model_by_type = None
322356

323357

0 commit comments

Comments
 (0)