@@ -139,6 +139,10 @@ export async function importJob(job: Job<ImportQueuePayload>) {
139139
140140 // Phase 1: Fetch & Transform - Process events in batches
141141 if ( shouldRunStep ( 'loading' ) ) {
142+ // Track insert_ids to detect duplicates from Mixpanel
143+ const seenInsertIds : Record < string , number > = { } ;
144+ let totalDuplicatesDetected = 0 ;
145+
142146 let eventBatch : any = [ ] ;
143147 for await ( const rawEvent of providerInstance . parseSource (
144148 resumeLoadingFrom ,
@@ -158,9 +162,26 @@ export async function importJob(job: Job<ImportQueuePayload>) {
158162
159163 // Process batch when it reaches the batch size
160164 if ( eventBatch . length >= BATCH_SIZE ) {
165+ // Check for duplicate insert_ids in this batch
166+ let batchDuplicates = 0 ;
167+ for ( const rawEvent of eventBatch ) {
168+ const insertId = rawEvent . properties ?. $insert_id ;
169+ if ( insertId ) {
170+ const count = seenInsertIds [ insertId ] || 0 ;
171+ if ( count > 0 ) {
172+ batchDuplicates ++ ;
173+ totalDuplicatesDetected ++ ;
174+ console . log ( `🔄 Duplicate insert_id detected from Mixpanel: ${ insertId } (occurrence: ${ count + 1 } , event: ${ rawEvent . event } )` ) ;
175+ }
176+ seenInsertIds [ insertId ] = count + 1 ;
177+ }
178+ }
179+
161180 const memUsage = process . memoryUsage ( ) ;
162181 jobLogger . info ( 'Processing batch' , {
163182 batchSize : eventBatch . length ,
183+ duplicatesInBatch : batchDuplicates ,
184+ totalDuplicates : totalDuplicatesDetected ,
164185 heapUsedMB : Math . round ( memUsage . heapUsed / 1024 / 1024 ) ,
165186 heapTotalMB : Math . round ( memUsage . heapTotal / 1024 / 1024 ) ,
166187 externalMB : Math . round ( memUsage . external / 1024 / 1024 ) ,
@@ -196,9 +217,26 @@ export async function importJob(job: Job<ImportQueuePayload>) {
196217
197218 // Process remaining events in the last batch
198219 if ( eventBatch . length > 0 ) {
220+ // Check for duplicate insert_ids in final batch
221+ let batchDuplicates = 0 ;
222+ for ( const rawEvent of eventBatch ) {
223+ const insertId = rawEvent . properties ?. $insert_id ;
224+ if ( insertId ) {
225+ const count = seenInsertIds [ insertId ] || 0 ;
226+ if ( count > 0 ) {
227+ batchDuplicates ++ ;
228+ totalDuplicatesDetected ++ ;
229+ console . log ( `🔄 Duplicate insert_id detected from Mixpanel: ${ insertId } (occurrence: ${ count + 1 } , event: ${ rawEvent . event } )` ) ;
230+ }
231+ seenInsertIds [ insertId ] = count + 1 ;
232+ }
233+ }
234+
199235 const memUsage = process . memoryUsage ( ) ;
200236 jobLogger . info ( 'Processing final batch' , {
201237 batchSize : eventBatch . length ,
238+ duplicatesInBatch : batchDuplicates ,
239+ totalDuplicates : totalDuplicatesDetected ,
202240 heapUsedMB : Math . round ( memUsage . heapUsed / 1024 / 1024 ) ,
203241 heapTotalMB : Math . round ( memUsage . heapTotal / 1024 / 1024 ) ,
204242 externalMB : Math . round ( memUsage . external / 1024 / 1024 ) ,
@@ -230,6 +268,20 @@ export async function importJob(job: Job<ImportQueuePayload>) {
230268 // Yield control back to event loop after processing final batch
231269 await yieldToEventLoop ( ) ;
232270 }
271+
272+ // Log summary of duplicate detection
273+ const uniqueInsertIds = Object . keys ( seenInsertIds ) . length ;
274+ console . log ( '\n📊 Phase 1 Import Summary:' ) ;
275+ console . log ( ` Total events processed: ${ processedEvents } ` ) ;
276+ console . log ( ` Unique insert_ids: ${ uniqueInsertIds } ` ) ;
277+ console . log ( ` Duplicates detected: ${ totalDuplicatesDetected } ` ) ;
278+ if ( totalDuplicatesDetected > 0 ) {
279+ const duplicatePercentage = ( ( totalDuplicatesDetected / processedEvents ) * 100 ) . toFixed ( 2 ) ;
280+ console . log ( ` Duplication rate: ${ duplicatePercentage } %` ) ;
281+ console . log ( `\n⚠️ Mixpanel sent ${ totalDuplicatesDetected } duplicate events (same $insert_id multiple times)` ) ;
282+ } else {
283+ console . log ( `\n✅ No duplicates detected from Mixpanel` ) ;
284+ }
233285 }
234286
235287 // Phase 2: Generate session IDs if provider requires it
0 commit comments