@@ -710,38 +710,31 @@ function updateChunkHistory(job: any, now: number) {
710710}
711711
712712// ETA to finish merging the current staging chunk.
713- // Uses historical merge rate from completed chunks when available; falls back to overall job
714- // throughput (includes loading time, so slightly conservative) when none observed yet.
713+ // Uses historical merge rate from completed chunks only. With no chunk history yet
714+ // (e.g. monitor started mid-merge) there is no trustworthy throughput signal — pgRows/elapsed
715+ // is diluted by the export+download phases where pgRows is ~0 and mis-projects by ~30x — so we
716+ // show "—" until a chunk boundary is observed rather than print a wildly wrong number.
715717// eslint-disable-next-line @typescript-eslint/no-explicit-any
716718function computeChunkEta ( job : any ) {
717719 if ( job . status !== 'merging' ) return null
718720 const stagingRows = Number ( job . row_count_staging ) || 0
719721 if ( ! stagingRows ) return null
720722 const hist = chunkMergeHistory . get ( job . id )
721723 if ( ! hist || hist . mergeStart == null ) return null
722-
723- let rateRowsPerMs : number
724- if ( hist . completedChunks . length > 0 ) {
725- // Exponential recency weighting: chunk i gets weight 2^i (oldest=0, newest=n-1).
726- // Most recent chunk contributes ~50% of the rate; history stabilises it.
727- const chunks = hist . completedChunks
728- let weightedRate = 0 ,
729- totalWeight = 0
730- for ( let i = 0 ; i < chunks . length ; i ++ ) {
731- const w = Math . pow ( 2 , i )
732- weightedRate += ( chunks [ i ] . stagingRows / chunks [ i ] . durationMs ) * w
733- totalWeight += w
734- }
735- if ( totalWeight === 0 ) return null
736- rateRowsPerMs = weightedRate / totalWeight
737- } else {
738- // No completed chunks observed — derive rate from overall job progress (conservative: includes loading)
739- const pgRows = Number ( job . row_count_pg ) || 0
740- if ( ! pgRows || ! job . started_at ) return null
741- const jobElapsedMs = Date . now ( ) - new Date ( job . started_at ) . getTime ( )
742- if ( jobElapsedMs < 10000 ) return null
743- rateRowsPerMs = pgRows / jobElapsedMs
724+ if ( hist . completedChunks . length === 0 ) return null
725+
726+ // Exponential recency weighting: chunk i gets weight 2^i (oldest=0, newest=n-1).
727+ // Most recent chunk contributes ~50% of the rate; history stabilises it.
728+ const chunks = hist . completedChunks
729+ let weightedRate = 0 ,
730+ totalWeight = 0
731+ for ( let i = 0 ; i < chunks . length ; i ++ ) {
732+ const w = Math . pow ( 2 , i )
733+ weightedRate += ( chunks [ i ] . stagingRows / chunks [ i ] . durationMs ) * w
734+ totalWeight += w
744735 }
736+ if ( totalWeight === 0 ) return null
737+ const rateRowsPerMs = weightedRate / totalWeight
745738
746739 const estimatedDurationMs = stagingRows / rateRowsPerMs
747740 const elapsedMs = Date . now ( ) - hist . mergeStart
0 commit comments