@@ -122,15 +122,66 @@ func main() {
122122 // Update status to running
123123 updateWorkerStatus (dbpool , config , & WorkerStatus {}, "running" )
124124
125+ // Get retry attempts from config (default: 3)
126+ retryAttempts := 3
127+ if ra , ok := jobConfig ["retry_attempts" ].(float64 ); ok && ra > 0 {
128+ retryAttempts = int (ra )
129+ }
130+
131+ // Get dry run mode from config (default: false)
132+ dryRun := false
133+ if dr , ok := jobConfig ["dry_run" ].(bool ); ok {
134+ dryRun = dr
135+ }
136+
137+ if dryRun {
138+ fmt .Printf ("DRY RUN MODE: Messages will be processed but NOT sent to Kafka\n " )
139+ }
140+
125141 // Process assigned files
126142 status := & WorkerStatus {}
127143 for _ , fileItem := range myFiles {
128- fmt .Printf ("Processing file: %s\n " , fileItem .Path )
129- err := processFile (fileItem , jobConfig , producer , s3Client , streams , config , status , dbpool )
130- if err != nil {
131- fmt .Fprintf (os .Stderr , "Error processing file %s: %v\n " , fileItem .Path , err )
132- status .LastError = err .Error ()
144+ fmt .Printf ("Processing file: %s (retry attempts: %d, dry_run: %v)\n " , fileItem .Path , retryAttempts , dryRun )
145+
146+ // Try processing file with retries
147+ var lastErr error
148+ for attempt := 1 ; attempt <= retryAttempts ; attempt ++ {
149+ if attempt > 1 {
150+ fmt .Printf ("Retry attempt %d/%d for file: %s\n " , attempt , retryAttempts , fileItem .Path )
151+ // Wait a bit before retrying (exponential backoff)
152+ time .Sleep (time .Duration (attempt - 1 ) * time .Second )
153+ }
154+
155+ // Save current counts in case we need to rollback on failure
156+ savedTotalLines := status .TotalLines
157+ savedSuccessCount := status .SuccessCount
158+ savedErrorCount := status .ErrorCount
159+ savedSkippedCount := status .SkippedCount
160+
161+ lastErr = processFile (fileItem , jobConfig , producer , s3Client , streams , config , status , dbpool , dryRun )
162+ if lastErr == nil {
163+ // Success - break retry loop
164+ break
165+ }
166+
167+ // Failed - rollback the counts from partial processing
168+ // We want to retry from a clean state
169+ status .TotalLines = savedTotalLines
170+ status .SuccessCount = savedSuccessCount
171+ status .ErrorCount = savedErrorCount
172+ status .SkippedCount = savedSkippedCount
173+
174+ // Log the error but continue with retry
175+ fmt .Fprintf (os .Stdout , "Error processing file %s (attempt %d/%d): %v\n " , fileItem .Path , attempt , retryAttempts , lastErr )
176+ }
177+
178+ // If all retries failed, record the error
179+ if lastErr != nil {
180+ fmt .Fprintf (os .Stderr , "Failed to process file %s after %d attempts: %v\n " , fileItem .Path , retryAttempts , lastErr )
181+ status .LastError = lastErr .Error ()
182+ status .ErrorCount ++
133183 }
184+
134185 status .ProcessedFiles ++
135186 status .ProcessedBytes += fileItem .Size
136187
@@ -210,9 +261,9 @@ func loadConfig() (*WorkerConfig, error) {
210261func initKafkaProducer (config * WorkerConfig ) (* kafkabase.Producer , error ) {
211262 producerConfig := & kafka.ConfigMap {
212263 "bootstrap.servers" : config .KafkaBootstrapServers ,
213- "queue.buffering.max.messages" : 10000 ,
214- "batch.size" : 65535 ,
215- "linger.ms" : 100 ,
264+ "queue.buffering.max.messages" : 200000 ,
265+ "batch.size" : 1048576 ,
266+ "linger.ms" : 1000 ,
216267 "compression.type" : "zstd" ,
217268 }
218269
@@ -262,7 +313,7 @@ func selectWorkerFiles(files []FileItem, workerIndex, totalWorkers int) []FileIt
262313 return myFiles
263314}
264315
265- func processFile (fileItem FileItem , jobConfig map [string ]interface {}, producer * kafkabase.Producer , s3Client * s3.Client , streams * Streams , config * WorkerConfig , status * WorkerStatus , dbpool * pgxpool.Pool ) error {
316+ func processFile (fileItem FileItem , jobConfig map [string ]interface {}, producer * kafkabase.Producer , s3Client * s3.Client , streams * Streams , config * WorkerConfig , status * WorkerStatus , dbpool * pgxpool.Pool , dryRun bool ) error {
266317 status .CurrentFile = fileItem .Path
267318
268319 // Get file reader
@@ -297,7 +348,7 @@ func processFile(fileItem FileItem, jobConfig map[string]interface{}, producer *
297348 // Process lines
298349 scanner := bufio .NewScanner (fileReader )
299350 lineNum := int64 (0 )
300- batchSize := 100 // Default batch size
351+ batchSize := 1000 // Default batch size
301352 if bs , ok := jobConfig ["batch_size" ].(float64 ); ok {
302353 batchSize = int (bs )
303354 }
@@ -328,7 +379,7 @@ func processFile(fileItem FileItem, jobConfig map[string]interface{}, producer *
328379 batch = append (batch , message )
329380
330381 if len (batch ) >= batchSize {
331- if err := sendBatch (batch , producer , config .KafkaTopicName , jobConfig , streams , status ); err != nil {
382+ if err := sendBatch (batch , producer , config .KafkaTopicName , jobConfig , streams , status , dryRun ); err != nil {
332383 fmt .Fprintf (os .Stderr , "Failed to send batch: %v\n " , err )
333384 status .ErrorCount += int64 (len (batch ))
334385 } else {
@@ -337,15 +388,15 @@ func processFile(fileItem FileItem, jobConfig map[string]interface{}, producer *
337388 batch = batch [:0 ]
338389 }
339390
340- // Update status periodically (every 1000 lines)
341- if lineNum % 1000 == 0 {
391+ // Update status periodically (every 100000 lines)
392+ if lineNum % 100000 == 0 {
342393 updateWorkerStatus (dbpool , config , status , "running" )
343394 }
344395 }
345396
346397 // Send remaining batch
347398 if len (batch ) > 0 {
348- if err := sendBatch (batch , producer , config .KafkaTopicName , jobConfig , streams , status ); err != nil {
399+ if err := sendBatch (batch , producer , config .KafkaTopicName , jobConfig , streams , status , dryRun ); err != nil {
349400 fmt .Fprintf (os .Stderr , "Failed to send final batch: %v\n " , err )
350401 status .ErrorCount += int64 (len (batch ))
351402 } else {
@@ -379,17 +430,16 @@ func downloadS3File(s3Client *s3.Client, s3Path string) (io.ReadCloser, error) {
379430func shouldProcessMessage (message map [string ]interface {}, jobConfig map [string ]interface {}) bool {
380431 // Apply filters from job config
381432 if streamIds , ok := jobConfig ["stream_ids" ].([]interface {}); ok && len (streamIds ) > 0 {
382- origin , ok := message ["origin" ].(map [string ] interface {})
433+ origin , ok := message ["origin" ].(* jsonorder. OrderedMap [string , interface {}] )
383434 if ! ok {
384435 return false
385436 }
386437
387- sourceId , _ := origin ["source_id" ].(string )
388- slug , _ := origin ["slug" ].(string )
389-
438+ sourceId := origin .GetS ("sourceId" )
439+ slug := origin .GetS ("slug" )
390440 found := false
391441 for _ , sid := range streamIds {
392- if sidStr , ok := sid .( string ); ok && ( sidStr == sourceId || sidStr == slug ) {
442+ if sid == sourceId || sid == slug {
393443 found = true
394444 break
395445 }
@@ -398,10 +448,9 @@ func shouldProcessMessage(message map[string]interface{}, jobConfig map[string]i
398448 return false
399449 }
400450 }
401-
402451 // Add date filtering if needed
403- if dateFrom , ok := jobConfig ["date_from" ].(string ); ok && dateFrom != "" {
404- messageCreated , _ := message ["message_created " ].(string )
452+ if dateFrom , ok := jobConfig ["date_from" ].(string ); ok && dateFrom != "" && dateFrom != "0001-01-01T00:00:00Z" {
453+ messageCreated , _ := message ["messageCreated " ].(string )
405454 if messageCreated != "" {
406455 msgTime , err := time .Parse (time .RFC3339Nano , messageCreated )
407456 if err == nil {
@@ -413,8 +462,8 @@ func shouldProcessMessage(message map[string]interface{}, jobConfig map[string]i
413462 }
414463 }
415464
416- if dateTo , ok := jobConfig ["date_to" ].(string ); ok && dateTo != "" {
417- messageCreated , _ := message ["message_created " ].(string )
465+ if dateTo , ok := jobConfig ["date_to" ].(string ); ok && dateTo != "" && dateTo != "0001-01-01T00:00:00Z" {
466+ messageCreated , _ := message ["messageCreated " ].(string )
418467 if messageCreated != "" {
419468 msgTime , err := time .Parse (time .RFC3339Nano , messageCreated )
420469 if err == nil {
@@ -425,11 +474,10 @@ func shouldProcessMessage(message map[string]interface{}, jobConfig map[string]i
425474 }
426475 }
427476 }
428-
429477 return true
430478}
431479
432- func sendBatch (batch []map [string ]interface {}, producer * kafkabase.Producer , topic string , jobConfig map [string ]interface {}, streams * Streams , status * WorkerStatus ) error {
480+ func sendBatch (batch []map [string ]interface {}, producer * kafkabase.Producer , topic string , jobConfig map [string ]interface {}, streams * Streams , status * WorkerStatus , dryRun bool ) error {
433481 for _ , message := range batch {
434482 // Get connection IDs from job config or repository
435483 connectionIds := []string {}
@@ -443,10 +491,10 @@ func sendBatch(batch []map[string]interface{}, producer *kafkabase.Producer, top
443491
444492 // If no connection IDs provided, look them up from repository
445493 if len (connectionIds ) == 0 && streams != nil {
446- origin , _ := message ["origin" ].(map [string ] interface {})
494+ origin , _ := message ["origin" ].(* jsonorder. OrderedMap [string , interface {}] )
447495 if origin != nil {
448- sourceId , _ := origin [ "source_id" ].( string )
449- slug , _ := origin [ "slug" ].( string )
496+ sourceId := origin . GetS ( "sourceId" )
497+ slug := origin . GetS ( "slug" )
450498 streamId := sourceId
451499 if streamId == "" {
452500 streamId = slug
@@ -483,6 +531,13 @@ func sendBatch(batch []map[string]interface{}, producer *kafkabase.Producer, top
483531 return err
484532 }
485533
534+ // Skip Kafka send if in dry run mode
535+ if dryRun {
536+ // In dry run mode, just validate that we can marshal the message
537+ // but don't actually send to Kafka
538+ continue
539+ }
540+
486541 err = producer .ProduceAsync (topic , uuid .New (), messageBytes , headers , kafka .PartitionAny , "" , false )
487542 if err != nil {
488543 return err
0 commit comments