From 7bc57ec227c0caa8cb165f277d8ca8c609e9ca17 Mon Sep 17 00:00:00 2001 From: xCyanGrizzly Date: Mon, 10 Aug 2026 08:17:51 +0200 Subject: [PATCH] fix(worker): prune TDLib's unbounded local file cache after each run TDLib keeps a permanent local copy of every file it downloads or uploads (via inputFileLocal) with no automatic cleanup. Across the two worker accounts this had grown to ~270GB, filling the host disk to 91% and triggering a cascading disk-full failure in the bot's TDLib session on 2026-08-05. Call optimizeStorage after every ingestion run to clear it; a 5-minute immunity_delay protects files an in-flight operation might still reference. --- worker/src/tdlib/client.ts | 31 +++++++++++++++++++++++++++++++ worker/src/worker.ts | 3 ++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/worker/src/tdlib/client.ts b/worker/src/tdlib/client.ts index 74cb5e1..0c88dc4 100644 --- a/worker/src/tdlib/client.ts +++ b/worker/src/tdlib/client.ts @@ -142,3 +142,34 @@ export async function closeTdlibClient(client: Client): Promise { log.warn({ err }, "Error closing TDLib client"); } } + +/** + * Prune TDLib's local file cache (filesDirectory). TDLib keeps a permanent + * copy of every file it has ever downloaded or uploaded — via inputFileLocal + * uploads in particular — with no automatic cleanup. That cache is redundant + * (the content already lives in the source and destination Telegram chats) + * and grows unbounded, so it's cleared after every ingestion run. A short + * immunity_delay protects files from an in-flight operation that might still + * reference them. + */ +export async function optimizeTdlibStorage( + client: Client, + accountId: string +): Promise { + try { + const result = (await client.invoke({ + _: "optimizeStorage", + size: 0, + ttl: 0, + count: 0, + immunity_delay: 300, + return_deleted_file_statistics: true, + })) as { size?: number; count?: number }; + log.info( + { accountId, freedBytes: result.size, freedCount: result.count }, + "TDLib local file cache pruned" + ); + } catch (err) { + log.warn({ err, accountId }, "TDLib storage optimization failed"); + } +} diff --git a/worker/src/worker.ts b/worker/src/worker.ts index 0eb9d66..ca132c8 100644 --- a/worker/src/worker.ts +++ b/worker/src/worker.ts @@ -42,7 +42,7 @@ import { isTopicFetchEnabled, } from "./db/queries.js"; import type { ActivityUpdate } from "./db/queries.js"; -import { createTdlibClient, closeTdlibClient } from "./tdlib/client.js"; +import { createTdlibClient, closeTdlibClient, optimizeTdlibStorage } from "./tdlib/client.js"; import { getAccountChats, joinChatByInviteLink, @@ -1203,6 +1203,7 @@ export async function runWorkerForAccount( accountLog.info({ counters }, "Ingestion run completed"); } finally { await throttled.flush(); + await optimizeTdlibStorage(client, account.id); await closeTdlibClient(client); } } catch (err) {