Skip to content

Commit

Permalink
radio: make SongStorage.LastPlayed use a from-key
Browse files Browse the repository at this point in the history
This switches the LastPlayed pagination from limit/offset to keyset
pagination which should help performance.
  • Loading branch information
Wessie committed Jun 17, 2024
1 parent 1c6634a commit 5ab9f32
Show file tree
Hide file tree
Showing 13 changed files with 605 additions and 58 deletions.
20 changes: 18 additions & 2 deletions ircbot/commands_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func NowPlaying(e Event) error {
func LastPlayed(e Event) error {
const op errors.Op = "irc/LastPlayed"

songs, err := e.Storage.Song(e.Ctx).LastPlayed(0, 5)
songs, err := e.Storage.Song(e.Ctx).LastPlayed(radio.LPKeyLast, 5)
if err != nil {
return errors.E(op, err)
}
Expand Down Expand Up @@ -230,7 +230,23 @@ func FaveTrack(e Event) error {

// count the amount of `last`'s used to determine how far back we should go
index := strings.Count(e.Arguments["relative"], "last") - 1
songs, err := ss.LastPlayed(int64(index), 1)

key := radio.LPKeyLast
if index > 0 {
// if our index is higher than 0 we need to lookup the key for that
prev, _, err := ss.LastPlayedPagination(radio.LPKeyLast, 1, 50)
if err != nil {
return errors.E(op, err)
}
// make sure our index exists in the list
if index >= len(prev) {
return errors.E(op, "index too far")
}

key = prev[index]
}

songs, err := ss.LastPlayed(key, 1)
if err != nil {
return errors.E(op, err)
}
Expand Down
118 changes: 87 additions & 31 deletions mocks/radio.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion radio.go
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,10 @@ type SongStorageService interface {
SongTx(context.Context, StorageTx) (SongStorage, StorageTx, error)
}

type LastPlayedKey uint32

const LPKeyLast = LastPlayedKey(math.MaxUint32)

// SongStorage stores information about songs
//
// A song can be anything that plays on stream, unlike a track which is a specific
Expand All @@ -818,7 +822,9 @@ type SongStorage interface {

// LastPlayed returns songs that have recently played, up to amount given after
// applying the offset
LastPlayed(offset, amount int64) ([]Song, error)
LastPlayed(key LastPlayedKey, amountPerPage int) ([]Song, error)
// LastPlayedPagination looks up keys for adjacent pages of key
LastPlayedPagination(key LastPlayedKey, amountPerPage, pageCount int) (prev, next []LastPlayedKey, err error)
// LastPlayedCount returns the amount of plays recorded
LastPlayedCount() (int64, error)
// PlayedCount returns the amount of times the song has been played on stream
Expand Down
66 changes: 62 additions & 4 deletions storage/mariadb/track.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package mariadb

import (
"database/sql"
"slices"
"strings"
"time"

radio "github.com/R-a-dio/valkyrie"
"github.com/R-a-dio/valkyrie/errors"
"github.com/R-a-dio/valkyrie/util"
"github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
Expand Down Expand Up @@ -234,20 +236,22 @@ LEFT JOIN
users ON djs.id = users.djid
LEFT JOIN
themes ON djs.theme_id = themes.id
WHERE
eplay.id < ?
ORDER BY
eplay.dt DESC, eplay.id DESC
LIMIT ? OFFSET ?;
LIMIT ?;
`)

// LastPlayed implements radio.SongStorage
func (ss SongStorage) LastPlayed(offset, amount int64) ([]radio.Song, error) {
func (ss SongStorage) LastPlayed(key radio.LastPlayedKey, amountPerPage int) ([]radio.Song, error) {
const op errors.Op = "mariadb/SongStorage.LastPlayed"
handle, deferFn := ss.handle.span(op)
defer deferFn()

var songs = make([]radio.Song, 0, amount)
var songs = make([]radio.Song, 0, amountPerPage)

err := sqlx.Select(handle, &songs, songLastPlayedQuery, amount, offset)
err := sqlx.Select(handle, &songs, songLastPlayedQuery, key, amountPerPage)
if err != nil {
return nil, errors.E(op, err)
}
Expand All @@ -264,6 +268,60 @@ func (ss SongStorage) LastPlayed(offset, amount int64) ([]radio.Song, error) {
return songs, nil
}

func (ss SongStorage) LastPlayedPagination(key radio.LastPlayedKey, amountPerPage, pageCount int) (prev, next []radio.LastPlayedKey, err error) {
const op errors.Op = "mariadb/SongStorage.LastPlayedPagination"
handle, deferFn := ss.handle.span(op)
defer deferFn()

total := amountPerPage * pageCount
tmp := make([]radio.LastPlayedKey, 0, total)

query := `
SELECT
id
FROM
eplay
WHERE
id < ?
ORDER BY
dt DESC, id DESC
LIMIT ?;
`

err = sqlx.Select(handle, &tmp, query, key, total)
if err != nil {
return nil, nil, errors.E(op, err)
}
// reduce to just the page boundaries
next = util.ReduceWithStep(tmp, amountPerPage)

// reset tmp for the next set
tmp = tmp[:0]
query = `
SELECT
id
FROM
eplay
WHERE
id >= ?
ORDER BY
dt ASC, id ASC
LIMIT ?;
`

err = sqlx.Select(handle, &tmp, query, key, total)
if err != nil {
return nil, nil, errors.E(op, err)
}

// reduce to just the page boundaries
prev = util.ReduceWithStep(tmp, amountPerPage)
// reverse since they're in ascending order
slices.Reverse(prev)

return prev, next, nil
}

// LastPlayedCount implements radio.SongStorage
func (ss SongStorage) LastPlayedCount() (int64, error) {
const op errors.Op = "mariadb/SongStorage.LastPlayedCount"
Expand Down
Loading

0 comments on commit 5ab9f32

Please sign in to comment.