Two things that were only ever confusing, and one of them stopped a command working on the cluster. The store type was named SQLite and kept the name when PostgreSQL was added, so OpenPostgres returned a *SQLite and all seventy-odd methods on the production store were spelled after the database it was not running on. Nothing broke; people did. Earlier today I added three methods to *SQLite for a table that on this cluster only ever lives in PostgreSQL, and the name did not once look wrong. It is a field, which is all it ever was. `llmgw users` and `llmgw keys` defaulted --store to a SQLite path. On a deployment whose store is PostgreSQL every one of them failed with "unable to open database file" while the right answer sat in auth.store in the config file beside them; the conclusion an operator draws is that the tool is broken, and they are not far off. They now ask the server's own configuration before falling back, and the error names which of the three sources chose the address — the failure was never the path, it was which one picked it. The single-machine default still applies last, so a deployment with no config file keeps working with no flags. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
89 lines
3.0 KiB
Go
89 lines
3.0 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// StoredCapacity is a benchmark's conclusion, kept for whoever has to size the
|
|
// model later.
|
|
type StoredCapacity struct {
|
|
Model string
|
|
Profile string
|
|
// Measurement is bench.Capacity as JSON. Stored whole for the reason a
|
|
// model definition is: a measurement is a nested shape that is still
|
|
// growing, and as columns every field added would be a migration.
|
|
Measurement []byte
|
|
MeasuredAt time.Time
|
|
MeasuredBy string
|
|
}
|
|
|
|
// PutCapacity records what a replica of a model was measured to serve.
|
|
//
|
|
// Replacing rather than accumulating. A second measurement of the same model on
|
|
// the same profile is a better answer to the same question, not another data
|
|
// point: hardware and engine builds change, and keeping every run would leave a
|
|
// reader choosing between numbers with no basis for the choice. The run itself
|
|
// is printed when it happens, for anyone who wants the ramp.
|
|
func (s *DB) PutCapacity(ctx context.Context, c StoredCapacity) error {
|
|
if c.Model == "" || c.Profile == "" {
|
|
return fmt.Errorf("a capacity measurement needs a model and a profile")
|
|
}
|
|
if !json.Valid(c.Measurement) {
|
|
return fmt.Errorf("capacity for %s/%s: the measurement is not valid JSON", c.Model, c.Profile)
|
|
}
|
|
if c.MeasuredAt.IsZero() {
|
|
c.MeasuredAt = time.Now()
|
|
}
|
|
_, err := s.db.ExecContext(ctx,
|
|
`INSERT INTO capacity (model, profile, measurement, measured_at, measured_by)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
ON CONFLICT(model, profile) DO UPDATE SET
|
|
measurement = excluded.measurement,
|
|
measured_at = excluded.measured_at,
|
|
measured_by = excluded.measured_by`,
|
|
c.Model, c.Profile, string(c.Measurement), c.MeasuredAt, c.MeasuredBy)
|
|
return err
|
|
}
|
|
|
|
// Capacities lists every measurement, newest first.
|
|
//
|
|
// Newest first because the question a reader usually has is "what do we know
|
|
// lately", and an old measurement is the one most likely to be describing
|
|
// hardware that has since changed.
|
|
func (s *DB) Capacities(ctx context.Context) ([]StoredCapacity, error) {
|
|
rows, err := s.db.QueryContext(ctx,
|
|
`SELECT model, profile, measurement, measured_at, measured_by
|
|
FROM capacity ORDER BY measured_at DESC, model, profile`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []StoredCapacity
|
|
for rows.Next() {
|
|
var c StoredCapacity
|
|
var m string
|
|
if err := rows.Scan(&c.Model, &c.Profile, &m, &c.MeasuredAt, &c.MeasuredBy); err != nil {
|
|
return nil, err
|
|
}
|
|
c.Measurement = []byte(m)
|
|
out = append(out, c)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// DeleteCapacity forgets a measurement.
|
|
//
|
|
// Wanted when a model is removed, and wanted on its own: a profile whose
|
|
// hardware has been replaced is worse than no measurement, because it looks
|
|
// like knowledge. Deleting one that is not there is not an error — the caller
|
|
// wanted it gone, and it is.
|
|
func (s *DB) DeleteCapacity(ctx context.Context, model, profile string) error {
|
|
_, err := s.db.ExecContext(ctx,
|
|
`DELETE FROM capacity WHERE model = ? AND profile = ?`, model, profile)
|
|
return err
|
|
}
|