diff --git a/docs/content/docs/self-hosting-guides/self-hosting-advanced.mdx b/docs/content/docs/self-hosting-guides/self-hosting-advanced.mdx index f79ce2cb1..d989b19e9 100644 --- a/docs/content/docs/self-hosting-guides/self-hosting-advanced.mdx +++ b/docs/content/docs/self-hosting-guides/self-hosting-advanced.mdx @@ -86,4 +86,7 @@ IMAGE_TAG=latest # Port mapping (only needed for custom ports or --no-webserver) HOST_BACKEND_PORT="3001:3001" HOST_CLIENT_PORT="3002:3002" -``` \ No newline at end of file + +# ClickHouse insert retries for pageview queue (default: 3 retries after the initial attempt) +PAGEVIEW_QUEUE_MAX_RETRIES=3 +``` diff --git a/server/src/services/tracker/pageviewQueue.ts b/server/src/services/tracker/pageviewQueue.ts index ac20e1f76..18bb836d4 100644 --- a/server/src/services/tracker/pageviewQueue.ts +++ b/server/src/services/tracker/pageviewQueue.ts @@ -10,6 +10,11 @@ type TotalPayload = TotalTrackingPayload & { sessionId: string; }; +const parsePositiveNumber = (value: string | undefined, fallback: number) => { + const parsed = Number(value); + return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; +}; + const getParsedProperties = (properties: string | undefined) => { try { return properties ? JSON.parse(properties) : undefined; @@ -24,6 +29,7 @@ class PageviewQueue { private interval = 10000; private processing = false; private logger = createServiceLogger("pageview-queue"); + private maxRetries = parsePositiveNumber(process.env.PAGEVIEW_QUEUE_MAX_RETRIES, 3); constructor() { // Start processing interval @@ -116,7 +122,16 @@ class PageviewQueue { }); this.logger.info({ count: processedPageviews.length }, "Bulk insert to ClickHouse"); - // Bulk insert into database + try { + await this.insertBatchWithRetry(processedPageviews); + } catch (error) { + this.logger.error(error, `Error processing pageview queue after ${this.maxRetries + 1} attempts, dropping ${batch.length} items`); + } finally { + this.processing = false; + } + } + + private async insertBatchWithRetry(processedPageviews: Record[], retryCount = 0): Promise { try { await clickhouse.insert({ table: "events", @@ -124,9 +139,15 @@ class PageviewQueue { format: "JSONEachRow", }); } catch (error) { - this.logger.error(error, "Error processing pageview queue"); - } finally { - this.processing = false; + if (retryCount >= this.maxRetries) { + throw error; + } + + const retryAttempt = retryCount + 1; + const delay = this.interval * Math.pow(2, retryCount); + this.logger.warn({ retryAttempt, delay }, "Bulk insert to ClickHouse failed, retrying"); + await new Promise(resolve => setTimeout(resolve, delay)); + await this.insertBatchWithRetry(processedPageviews, retryCount + 1); } } }