docs: wanted, dbtest, recommendation and playlists comments say what the code does (#6277)
The same rewrite as d191a282, for four packages whose comments carried the
most history: tuning stories, quotes from the operator, single-install
measurements in library_scale.go, and 'used to' accounts of bugs. The
constraint each story protected stays as a present-tense reason (why tier 3
is reduced not removed, why the randomised arms take an order seed, why the
seed artist is eligible in Songs-like). Comments naming system_cron.go and
config.RecommendationConfig, neither of which holds what they claimed, now
name the scheduler and internal/recsettings.
Checked: non-comment tokens identical to HEAD; gofmt clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,44 +11,26 @@ import (
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
)
|
||||
|
||||
// Scaling the candidate pool to the library (#3880).
|
||||
// Scaling the candidate pool to the library.
|
||||
//
|
||||
// DefaultCandidateSourceLimits returns what its own comment calls "the v1
|
||||
// hardcoded constants per spec" — ~170 candidates, identical for a 500-track
|
||||
// library and a 100,000-track one. Operator, 2026-09-10: "is the pool that we
|
||||
// draw from somehow scaled to the amount of music in the library... my earlier
|
||||
// understanding of the tuning and work may have been skewed by what was in my
|
||||
// library."
|
||||
//
|
||||
// It was not, and the consequence compounds. The pool samples a shrinking
|
||||
// FRACTION of the library as it grows — 17% of 1,000 tracks, 1.7% of 10,000,
|
||||
// 0.17% of 100,000 — so the ceiling on how much of a collection can ever
|
||||
// surface stays flat while the collection does not. RandomFill, whose entire
|
||||
// job is exploration, becomes a thinner and noisier slice of a more diverse
|
||||
// corpus at exactly the moment diversity rises.
|
||||
// DefaultCandidateSourceLimits is ~170 candidates, whatever the library's
|
||||
// size. Unscaled, the pool samples a shrinking FRACTION of the library as it
|
||||
// grows — 17% of 1,000 tracks, 1.7% of 10,000, 0.17% of 100,000 — so the
|
||||
// ceiling on how much of a collection can ever surface stays flat while the
|
||||
// collection does not, and RandomFill, whose job is exploration, becomes a
|
||||
// thinner slice of a more diverse corpus.
|
||||
|
||||
const (
|
||||
// libraryScaleReference is the library size at which the base limits
|
||||
// apply unchanged. Below it nothing scales, so small libraries keep
|
||||
// today's behaviour exactly.
|
||||
//
|
||||
// ASSUMED, NOT MEASURED. The size the v1 constants were actually tuned
|
||||
// against is unrecorded; 5,000 is a plausible mid-size library and a
|
||||
// deliberately conservative place to start growing. This constant is the
|
||||
// single thing to change.
|
||||
//
|
||||
// Measured on the operator's library, 26,005 tracks (scale 2.28), for the
|
||||
// 15 most-played seeds and two Moe Shop tracks (#3880, 2026-10-21):
|
||||
// lb_similar 0–6 edges (3 of 17 seeds have any): never reaches
|
||||
// even the base limit, so scaling it costs nothing
|
||||
// similar_artists 860–6,307 tracks over 7–33 artists: always full
|
||||
// tag_overlap 4,658–19,658 tracks: always full
|
||||
// likes_overlap 193–497 liked tracks sharing a tag: always full
|
||||
// taste_overlap 20,045 tracks over 174 artists: always full
|
||||
// coplay 0 rows: a single-user server
|
||||
// So the arms the scale grows all have far more rows than it asks for, and
|
||||
// the tuned-against size cannot be recovered from data; the number stays
|
||||
// until a library shows a scaled arm running dry or a pool too slow.
|
||||
// An assumption, not a measurement: 5,000 is a plausible mid-size library
|
||||
// and a deliberately conservative place to start growing. On a large
|
||||
// library the arms the scale grows (similar_artists, tag_overlap,
|
||||
// likes_overlap, taste_overlap) hold far more rows than it asks for, while
|
||||
// lb_similar rarely reaches even its base limit. Change this if a library
|
||||
// shows a scaled arm running dry or a pool too slow.
|
||||
libraryScaleReference = 5000
|
||||
|
||||
// maxLibraryScale bounds growth so a very large library does not turn
|
||||
@@ -63,9 +45,9 @@ const (
|
||||
// Square-root growth keeps the pool meaningfully proportional while staying
|
||||
// bounded: 1x at the reference, 2x at four times it, 4x at sixteen times.
|
||||
//
|
||||
// Never returns below 1: the base limits are a floor, not a midpoint. That
|
||||
// also keeps this safe against #3889 — growing an arm ordered by unseeded
|
||||
// random() is fine, shrinking one is what breaks same-day determinism.
|
||||
// Never returns below 1: the base limits are a floor, not a midpoint. Growing
|
||||
// an arm ordered by unseeded random() is harmless; shrinking one would break
|
||||
// same-day determinism.
|
||||
func libraryScale(libraryTracks int64) float64 {
|
||||
if libraryTracks <= libraryScaleReference {
|
||||
return 1.0
|
||||
@@ -113,8 +95,7 @@ func ScaleForLibrary(base CandidateSourceLimits, libraryTracks int64) CandidateS
|
||||
// CountLibraryTracks returns the whole library's track count.
|
||||
//
|
||||
// Reuses CountTracksMatching with an empty pattern — `title ILIKE '%%'`
|
||||
// matches every row — rather than adding a query, because a new one would
|
||||
// need sqlc regeneration (rule 28, and see #3889 for where that blocks).
|
||||
// matches every row — rather than adding a query.
|
||||
// The user id is left invalid so the quarantine anti-join is skipped: this
|
||||
// number sizes a pool, and a per-user view of it would be false precision.
|
||||
func CountLibraryTracks(ctx context.Context, q *dbq.Queries) (int64, error) {
|
||||
@@ -155,11 +136,11 @@ func NewLibrarySize(now func() time.Time) *LibrarySize {
|
||||
// Get returns the cached size, refreshing through load when stale.
|
||||
//
|
||||
// A NIL RECEIVER IS VALID and means "no cache": the count still runs, it is
|
||||
// just not memoised. That is deliberate rather than defensive habit. The api
|
||||
// handlers struct is built directly by a dozen tests that cannot know about
|
||||
// every field, and a nil here previously panicked inside a radio request —
|
||||
// turning a missing pool-sizing HINT into a 500. Uncached-but-correct is the
|
||||
// right failure for something whose whole contract is that it degrades.
|
||||
// just not memoised. The api handlers struct is built directly by a dozen
|
||||
// tests that cannot know about every field, and a panic here would turn a
|
||||
// missing pool-sizing HINT into a 500 inside a radio request.
|
||||
// Uncached-but-correct is the right failure for something whose whole
|
||||
// contract is that it degrades.
|
||||
func (l *LibrarySize) Get(ctx context.Context, load func(context.Context) (int64, error)) int64 {
|
||||
if l == nil {
|
||||
n, err := loadWithDeadline(ctx, load)
|
||||
@@ -191,8 +172,8 @@ func (l *LibrarySize) Get(ctx context.Context, load func(context.Context) (int64
|
||||
return l.size
|
||||
}
|
||||
|
||||
// loadWithDeadline bounds the count. Rule 156: the caller is a user waiting
|
||||
// on a radio, and a pool-sizing hint is never worth hanging for.
|
||||
// loadWithDeadline bounds the count: the caller is a user waiting on a radio,
|
||||
// and a pool-sizing hint is never worth hanging for.
|
||||
func loadWithDeadline(ctx context.Context, load func(context.Context) (int64, error)) (int64, error) {
|
||||
cctx, cancel := context.WithTimeout(ctx, librarySizeTimeout)
|
||||
defer cancel()
|
||||
|
||||
Reference in New Issue
Block a user