diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-04-27 21:20:10 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-04-27 21:20:10 +0900 |
| commit | cb00405041ee4714b6e817e9570cfa10ae972840 (patch) | |
| tree | ff98728b3e5e099eb9ac5556eeb407c68e0fc208 /backend/cmd | |
| parent | 938863425bf8ad6c17e43b3da128f92cf6d6ab63 (diff) | |
| download | feedaka-cb00405041ee4714b6e817e9570cfa10ae972840.tar.gz feedaka-cb00405041ee4714b6e817e9570cfa10ae972840.tar.zst feedaka-cb00405041ee4714b6e817e9570cfa10ae972840.zip | |
Add per-feed fetch_interval_seconds (clamped to [1h, 24h]) that halves
on new articles and grows 1.5x when a fetch yields nothing, replacing
the fixed 1h schedule with the 10min cooldown filter. Scheduler tick
shortened to 30min so the 1h floor is honored with reasonable precision.
Diffstat (limited to 'backend/cmd')
| -rw-r--r-- | backend/cmd/serve.go | 68 | ||||
| -rw-r--r-- | backend/cmd/serve_test.go | 30 |
2 files changed, 72 insertions, 26 deletions
diff --git a/backend/cmd/serve.go b/backend/cmd/serve.go index 4b32868..75bcb53 100644 --- a/backend/cmd/serve.go +++ b/backend/cmd/serve.go @@ -23,46 +23,62 @@ import ( "undef.ninja/x/feedaka/feed" ) -func fetchOneFeed(feedID int64, url string, ctx context.Context, queries *db.Queries) error { - log.Printf("Fetching %s...\n", url) - result, err := feed.Fetch(ctx, url) - if err != nil { - return err +const ( + minFetchIntervalSeconds = 60 * 60 // 1 hour + maxFetchIntervalSeconds = 24 * 60 * 60 // 1 day +) + +// nextFetchInterval adapts the per-feed poll interval based on whether the +// last fetch yielded new articles. Active feeds converge toward the floor +// quickly (halving) while quiet feeds back off gently (1.5x) so a brief lull +// doesn't push the interval to the ceiling. +func nextFetchInterval(current int64, hadNewArticles bool) int64 { + var next int64 + if hadNewArticles { + next = current / 2 + } else { + next = current * 3 / 2 } - return feed.Sync(ctx, queries, feedID, result.Feed) + if next < minFetchIntervalSeconds { + next = minFetchIntervalSeconds + } + if next > maxFetchIntervalSeconds { + next = maxFetchIntervalSeconds + } + return next } -func listFeedsToBeFetched(ctx context.Context, queries *db.Queries) (map[int64]string, error) { - feeds, err := queries.GetFeedsToFetch(ctx) +func fetchOneFeed(ctx context.Context, queries *db.Queries, f db.GetFeedsToFetchRow) error { + log.Printf("Fetching %s...\n", f.Url) + result, err := feed.Fetch(ctx, f.Url) + if err != nil { + return err + } + newCount, err := feed.Sync(ctx, queries, f.ID, result.Feed) if err != nil { - return nil, err + return err } - - result := make(map[int64]string) - for _, feed := range feeds { - fetchedAtTime, err := time.Parse(time.RFC3339, feed.FetchedAt) - if err != nil { - log.Fatal(err) - } - now := time.Now().UTC() - if now.Sub(fetchedAtTime).Minutes() <= 10 { - continue + next := nextFetchInterval(f.FetchIntervalSeconds, newCount > 0) + if next != f.FetchIntervalSeconds { + if err := queries.UpdateFeedFetchInterval(ctx, db.UpdateFeedFetchIntervalParams{ + FetchIntervalSeconds: next, + ID: f.ID, + }); err != nil { + return err } - result[feed.ID] = feed.Url } - return result, nil + return nil } func fetchAllFeeds(ctx context.Context, queries *db.Queries) error { - feeds, err := listFeedsToBeFetched(ctx, queries) + feeds, err := queries.GetFeedsToFetch(ctx) if err != nil { return err } var result *multierror.Error - for feedID, url := range feeds { - err := fetchOneFeed(feedID, url, ctx, queries) - if err != nil { + for _, f := range feeds { + if err := fetchOneFeed(ctx, queries, f); err != nil { result = multierror.Append(result, err) } time.Sleep(5 * time.Second) @@ -121,7 +137,7 @@ func RunServe(database *sql.DB, cfg *config.Config, publicFS embed.FS) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - scheduled(ctx, 1*time.Hour, func() { + scheduled(ctx, 30*time.Minute, func() { err := fetchAllFeeds(ctx, queries) if err != nil { log.Printf("Failed to fetch feeds: %v\n", err) diff --git a/backend/cmd/serve_test.go b/backend/cmd/serve_test.go new file mode 100644 index 0000000..82b7d53 --- /dev/null +++ b/backend/cmd/serve_test.go @@ -0,0 +1,30 @@ +package cmd + +import "testing" + +func TestNextFetchInterval(t *testing.T) { + tests := []struct { + name string + current int64 + hadNewArticles bool + want int64 + }{ + {"new articles halve from mid-range", 14400, true, 7200}, + {"new articles halve from max", maxFetchIntervalSeconds, true, 43200}, + {"new articles clamp at min when halving below", 3600, true, minFetchIntervalSeconds}, + {"new articles clamp at min from slightly above", 4000, true, minFetchIntervalSeconds}, + {"no articles grow by 1.5x from min", minFetchIntervalSeconds, false, 5400}, + {"no articles grow by 1.5x from mid-range", 14400, false, 21600}, + {"no articles clamp at max", maxFetchIntervalSeconds, false, maxFetchIntervalSeconds}, + {"no articles clamp at max from slightly below", 60000, false, maxFetchIntervalSeconds}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := nextFetchInterval(tt.current, tt.hadNewArticles) + if got != tt.want { + t.Errorf("nextFetchInterval(%d, %v) = %d, want %d", tt.current, tt.hadNewArticles, got, tt.want) + } + }) + } +} |
