Enhance TED scraper and worker initialization with startup catch-up logic
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Introduced a mutex to ensure only one TED scraper run executes at a time, preventing concurrent executions during startup and scheduled runs. - Implemented a mechanism to check if today's TED scrape has already been completed during startup, logging appropriate messages for both completed and new runs. - Added startup catch-up logic for tender translations and unprocessed notices, ensuring that any missed tasks are executed without blocking the application startup. This update improves the reliability and efficiency of the TED scraper and worker processes, ensuring that all necessary tasks are completed after a server restart.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
package schedule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/mongo"
|
||||
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
const (
|
||||
dailyJobRunsCollection = "daily_job_runs"
|
||||
DailyJobStatusCompleted = "completed"
|
||||
|
||||
TEDScraperJobName = "ted_scraper"
|
||||
)
|
||||
|
||||
type dailyJobRun struct {
|
||||
ID string `bson:"_id"`
|
||||
JobName string `bson:"job_name"`
|
||||
Date string `bson:"date"`
|
||||
CompletedAt int64 `bson:"completed_at"`
|
||||
Status string `bson:"status"`
|
||||
}
|
||||
|
||||
// DailyJobTracker records successful daily job runs so startup catch-up can skip
|
||||
// dates that were already completed before a server restart.
|
||||
type DailyJobTracker struct {
|
||||
mongo *mongo.ConnectionManager
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func NewDailyJobTracker(mongoManager *mongo.ConnectionManager, log logger.Logger) *DailyJobTracker {
|
||||
return &DailyJobTracker{
|
||||
mongo: mongoManager,
|
||||
logger: log,
|
||||
}
|
||||
}
|
||||
|
||||
func dailyJobRunID(jobName string, date time.Time) string {
|
||||
return fmt.Sprintf("%s:%s", jobName, date.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
// IsCompleted reports whether jobName finished successfully for the given local calendar date.
|
||||
func (t *DailyJobTracker) IsCompleted(ctx context.Context, jobName string, date time.Time) (bool, error) {
|
||||
if jobName == "" {
|
||||
return false, fmt.Errorf("daily job tracker: job name is required")
|
||||
}
|
||||
|
||||
var doc dailyJobRun
|
||||
err := t.mongo.GetCollection(dailyJobRunsCollection).FindOne(
|
||||
ctx,
|
||||
bson.M{"_id": dailyJobRunID(jobName, date), "status": DailyJobStatusCompleted},
|
||||
).Decode(&doc)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongodriver.ErrNoDocuments) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("daily job tracker: check completed %q: %w", jobName, err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// MarkCompleted records a successful daily job run for the given local calendar date.
|
||||
func (t *DailyJobTracker) MarkCompleted(ctx context.Context, jobName string, date time.Time) error {
|
||||
if jobName == "" {
|
||||
return fmt.Errorf("daily job tracker: job name is required")
|
||||
}
|
||||
|
||||
dateKey := date.Format("2006-01-02")
|
||||
now := time.Now().Unix()
|
||||
|
||||
_, err := t.mongo.GetCollection(dailyJobRunsCollection).UpdateOne(
|
||||
ctx,
|
||||
bson.M{"_id": dailyJobRunID(jobName, date)},
|
||||
bson.M{
|
||||
"$set": bson.M{
|
||||
"job_name": jobName,
|
||||
"date": dateKey,
|
||||
"completed_at": now,
|
||||
"status": DailyJobStatusCompleted,
|
||||
},
|
||||
"$setOnInsert": bson.M{"_id": dailyJobRunID(jobName, date)},
|
||||
},
|
||||
options.UpdateOne().SetUpsert(true),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("daily job tracker: mark completed %q on %s: %w", jobName, dateKey, err)
|
||||
}
|
||||
|
||||
t.logger.Debug("Daily job marked completed", map[string]interface{}{
|
||||
"job_name": jobName,
|
||||
"date": dateKey,
|
||||
"completed_at": now,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package schedule
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDailyJobRunID(t *testing.T) {
|
||||
date := time.Date(2026, 6, 27, 15, 30, 0, 0, time.Local)
|
||||
got := dailyJobRunID(TEDScraperJobName, date)
|
||||
want := "ted_scraper:2026-06-27"
|
||||
if got != want {
|
||||
t.Fatalf("dailyJobRunID() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
Executable → Regular
Reference in New Issue
Block a user