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:
prosolis
2026-05-24 19:31:27 -07:00
parent afe2ef996b
commit 87906719fa
7 changed files with 121 additions and 137 deletions

View File

@@ -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.