@@ -784,6 +784,29 @@ private void buildGraphFromScratch(final GraphBuildCallback graphCallback) {
784784 * @param graphCallback Optional callback for graph build progress
785785 */
786786 private void buildGraphFromScratchWithRetry (final GraphBuildCallback graphCallback ) {
787+ // Always have a progress reporter: if caller didn't provide one, log throttled progress every ~2s
788+ final GraphBuildCallback effectiveGraphCallback ;
789+ if (graphCallback != null ) {
790+ effectiveGraphCallback = graphCallback ;
791+ } else {
792+ final long [] lastLogTimeMs = {System .currentTimeMillis ()};
793+ final int [] lastLoggedProcessed = {-1 };
794+ effectiveGraphCallback = (phase , processedNodes , totalNodes , vectorAccesses ) -> {
795+ if (totalNodes <= 0 )
796+ return ;
797+
798+ final long now = System .currentTimeMillis ();
799+ final boolean shouldLog = processedNodes == totalNodes
800+ || (now - lastLogTimeMs [0 ] >= 2000 && processedNodes != lastLoggedProcessed [0 ]);
801+
802+ if (shouldLog ) {
803+ LogManager .instance ().log (this , Level .INFO ,
804+ "Graph build %s: %d/%d (vector accesses=%d)" , phase , processedNodes , totalNodes , vectorAccesses );
805+ lastLogTimeMs [0 ] = now ;
806+ lastLoggedProcessed [0 ] = processedNodes ;
807+ }
808+ };
809+ }
787810 // CRITICAL FIX: Collect vectors DIRECTLY from pages instead of from vectorIndex.
788811 // This avoids race conditions where concurrent replication adds entries to vectorIndex
789812 // that don't yet exist on disk pages. We iterate pages and read what's actually persisted.
@@ -966,14 +989,14 @@ private void buildGraphFromScratchWithRetry(final GraphBuildCallback graphCallba
966989
967990 // Report validation progress
968991 validatedCount ++;
969- if (graphCallback != null && validatedCount % VALIDATION_PROGRESS_INTERVAL == 0 ) {
970- graphCallback .onGraphBuildProgress ("validating" , validatedCount , totalVectorsToValidate , 0 );
992+ if (effectiveGraphCallback != null && validatedCount % VALIDATION_PROGRESS_INTERVAL == 0 ) {
993+ effectiveGraphCallback .onGraphBuildProgress ("validating" , validatedCount , totalVectorsToValidate , 0 );
971994 }
972995 }
973996
974997 // Final validation progress report
975- if (graphCallback != null && validatedCount > 0 ) {
976- graphCallback .onGraphBuildProgress ("validating" , validatedCount , totalVectorsToValidate , 0 );
998+ if (effectiveGraphCallback != null && validatedCount > 0 ) {
999+ effectiveGraphCallback .onGraphBuildProgress ("validating" , validatedCount , totalVectorsToValidate , 0 );
9771000 }
9781001
9791002 if (skippedDeletedDocs > 0 ) {
@@ -1034,7 +1057,7 @@ private void buildGraphFromScratchWithRetry(final GraphBuildCallback graphCallba
10341057 // Start progress monitoring thread if callback provided
10351058 final Thread progressMonitor ;
10361059 final AtomicBoolean buildComplete = new AtomicBoolean (false );
1037- if (graphCallback != null ) {
1060+ if (effectiveGraphCallback != null ) {
10381061 final int totalNodes = vectors .size ();
10391062 progressMonitor = new Thread (() -> {
10401063 try {
@@ -1044,7 +1067,7 @@ private void buildGraphFromScratchWithRetry(final GraphBuildCallback graphCallba
10441067 final int insertsInProgress = builder .insertsInProgress ();
10451068
10461069 // Report progress
1047- graphCallback .onGraphBuildProgress ("building" , nodesAdded , totalNodes , nodesAdded + insertsInProgress );
1070+ effectiveGraphCallback .onGraphBuildProgress ("building" , nodesAdded , totalNodes , nodesAdded + insertsInProgress );
10481071
10491072 // Sleep briefly before next poll
10501073 Thread .sleep (100 ); // Poll every 100ms
@@ -1102,8 +1125,8 @@ private void buildGraphFromScratchWithRetry(final GraphBuildCallback graphCallba
11021125 LogManager .instance ().log (this , Level .FINE , "Writing vector graph to disk for index: %s (nodes=%d)" , indexName , totalNodes );
11031126
11041127 // Report persistence phase start
1105- if (graphCallback != null )
1106- graphCallback .onGraphBuildProgress ("persisting" , 0 , totalNodes , 0 );
1128+ if (effectiveGraphCallback != null )
1129+ effectiveGraphCallback .onGraphBuildProgress ("persisting" , 0 , totalNodes , 0 );
11071130
11081131 // Start a dedicated transaction for graph persistence with chunked commits
11091132 long chunkSizeMB = getTxChunkSize ();
@@ -1132,8 +1155,8 @@ private void buildGraphFromScratchWithRetry(final GraphBuildCallback graphCallba
11321155 graphFile .writeGraph (graphIndex , vectors , chunkSizeMB , chunkCallback );
11331156
11341157 // Report persistence completion
1135- if (graphCallback != null ) {
1136- graphCallback .onGraphBuildProgress ("persisting" , totalNodes , totalNodes , 0 );
1158+ if (effectiveGraphCallback != null ) {
1159+ effectiveGraphCallback .onGraphBuildProgress ("persisting" , totalNodes , totalNodes , 0 );
11371160 }
11381161
11391162 // Commit the transaction to persist graph pages
0 commit comments