Skip to content

Commit f0b0c97

Browse files
chrisburraldbr
authored andcommitted
refactor: add per-phase timing to sandbox cleanup logging
Log duration of each phase (SELECT, S3 delete, DB delete) per batch so bottlenecks are immediately visible. Demote per-chunk DB delete log to DEBUG to reduce noise.
1 parent 31157e6 commit f0b0c97

2 files changed

Lines changed: 33 additions & 7 deletions

File tree

  • diracx-db/src/diracx/db/sql/sandbox_metadata
  • diracx-logic/src/diracx/logic/jobs

diracx-db/src/diracx/db/sql/sandbox_metadata/db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,5 +298,5 @@ async def delete_sandboxes(self, sb_ids: list[int]) -> int:
298298

299299
delete_stmt = delete(SandBoxes).where(SandBoxes.SBId.in_(sb_ids))
300300
result = await self.conn.execute(delete_stmt)
301-
logger.info("Deleted %d expired/unassigned sandboxes", result.rowcount)
301+
logger.debug("Deleted %d expired/unassigned sandboxes", result.rowcount)
302302
return result.rowcount

diracx-logic/src/diracx/logic/jobs/sandboxes.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import logging
5+
import time
56
from typing import TYPE_CHECKING, Any, Literal
67

78
from diracx.core.exceptions import SandboxAlreadyInsertedError, SandboxNotFoundError
@@ -221,8 +222,12 @@ async def clean_sandboxes(
221222
"""
222223
total_deleted = 0
223224
cursor = 0
225+
batch_num = 0
224226
while True:
227+
batch_num += 1
228+
225229
# Phase 1: SELECT candidates (short transaction, no locks)
230+
t0 = time.monotonic()
226231
async with sandbox_metadata_db:
227232
(
228233
sb_ids,
@@ -232,25 +237,37 @@ async def clean_sandboxes(
232237
batch_size=batch_size,
233238
cursor=cursor,
234239
)
240+
select_duration = time.monotonic() - t0
235241

236242
if not pfns:
243+
logger.info(
244+
"Batch %d: no candidates found (%.1fs)", batch_num, select_duration
245+
)
237246
break
238247

248+
logger.info(
249+
"Batch %d: selected %d candidates (%.1fs)",
250+
batch_num,
251+
len(pfns),
252+
select_duration,
253+
)
254+
239255
# Phase 2: Delete from S3 (no transaction — prevents dark data
240256
# since S3 is cleaned first)
257+
t0 = time.monotonic()
241258
objects: list[S3Object] = [{"Key": pfn_to_key(pfn)} for pfn in pfns]
242-
if logger.isEnabledFor(logging.DEBUG):
243-
for pfn in pfns:
244-
logger.debug("Deleting sandbox %s from S3", pfn)
245-
246259
await s3_bulk_delete_with_retry(
247260
settings.s3_client, settings.bucket_name, objects
248261
)
249-
logger.info("Deleted %d sandboxes from %s", len(objects), settings.bucket_name)
262+
s3_duration = time.monotonic() - t0
263+
logger.info(
264+
"Batch %d: deleted %d from S3 (%.1fs)", batch_num, len(objects), s3_duration
265+
)
250266

251267
# Phase 3: Delete from DB in small chunks (each chunk is a short
252268
# transaction to avoid locking millions of rows in a single DELETE).
253269
# Up to 10 chunks run concurrently via a semaphore.
270+
t0 = time.monotonic()
254271
delete_chunk_size = 1000
255272
sem = asyncio.Semaphore(10)
256273

@@ -263,6 +280,15 @@ async def _delete_chunk(chunk: list[int], _sem: asyncio.Semaphore = sem) -> int:
263280
for i in range(0, len(sb_ids), delete_chunk_size)
264281
]
265282
results = await asyncio.gather(*tasks)
266-
total_deleted += sum(results)
283+
deleted = sum(results)
284+
db_duration = time.monotonic() - t0
285+
total_deleted += deleted
286+
logger.info(
287+
"Batch %d: deleted %d from DB (%.1fs, total so far: %d)",
288+
batch_num,
289+
deleted,
290+
db_duration,
291+
total_deleted,
292+
)
267293

268294
return total_deleted

0 commit comments

Comments
 (0)