Round-robin: rotate by channel instead of by source
Source-keyed rotation skewed toward whichever channel had the most feeds (4 of 7 sources routed to politics, so politics dominated the rotation). Channel-keyed rotation guarantees variety regardless of feed counts. Schema: round_robin_state.last_source -> last_channel, added via addColumnIfMissing so existing DBs migrate in place.
This commit is contained in:
@@ -81,6 +81,7 @@ func runMigrations(d *sql.DB) error {
|
||||
addColumnIfMissing(d, "stories", "url_canonical", "TEXT")
|
||||
addColumnIfMissing(d, "stories", "headline_norm", "TEXT")
|
||||
addColumnIfMissing(d, "post_log", "url_canonical", "TEXT")
|
||||
addColumnIfMissing(d, "round_robin_state", "last_channel", "TEXT")
|
||||
|
||||
// FTS5 virtual tables don't support IF NOT EXISTS reliably.
|
||||
// Check sqlite_master before creating.
|
||||
|
||||
@@ -270,29 +270,29 @@ func GetNewestPostableStoryByChannel(channel string) (*Story, error) {
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// GetRoundRobinState returns the last source posted by the round-robin
|
||||
// GetRoundRobinState returns the last channel posted by the round-robin
|
||||
// scheduler and the timestamp of that tick. Empty string + 0 if no state yet.
|
||||
func GetRoundRobinState() (lastSource string, lastTickAt int64, err error) {
|
||||
row := Get().QueryRow(`SELECT last_source, last_tick_at FROM round_robin_state WHERE id = 1`)
|
||||
var ls *string
|
||||
if scanErr := row.Scan(&ls, &lastTickAt); scanErr != nil {
|
||||
func GetRoundRobinState() (lastChannel string, lastTickAt int64, err error) {
|
||||
row := Get().QueryRow(`SELECT last_channel, last_tick_at FROM round_robin_state WHERE id = 1`)
|
||||
var lc *string
|
||||
if scanErr := row.Scan(&lc, &lastTickAt); scanErr != nil {
|
||||
if errors.Is(scanErr, sql.ErrNoRows) {
|
||||
return "", 0, nil
|
||||
}
|
||||
return "", 0, scanErr
|
||||
}
|
||||
if ls != nil {
|
||||
lastSource = *ls
|
||||
if lc != nil {
|
||||
lastChannel = *lc
|
||||
}
|
||||
return lastSource, lastTickAt, nil
|
||||
return lastChannel, lastTickAt, nil
|
||||
}
|
||||
|
||||
// SetRoundRobinState persists the last-posted source and tick timestamp.
|
||||
func SetRoundRobinState(lastSource string, tickAt int64) {
|
||||
// SetRoundRobinState persists the last-posted channel and tick timestamp.
|
||||
func SetRoundRobinState(lastChannel string, tickAt int64) {
|
||||
exec("set round_robin_state",
|
||||
`INSERT INTO round_robin_state (id, last_source, last_tick_at) VALUES (1, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET last_source = excluded.last_source, last_tick_at = excluded.last_tick_at`,
|
||||
lastSource, tickAt)
|
||||
`INSERT INTO round_robin_state (id, last_channel, last_tick_at) VALUES (1, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET last_channel = excluded.last_channel, last_tick_at = excluded.last_tick_at`,
|
||||
lastChannel, tickAt)
|
||||
}
|
||||
|
||||
// MarshalPlatforms converts a string slice to a JSON array string for storage.
|
||||
|
||||
@@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS post_log (
|
||||
|
||||
CREATE TABLE IF NOT EXISTS round_robin_state (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
last_source TEXT,
|
||||
last_channel TEXT,
|
||||
last_tick_at INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user