Цена читающего маршрута: чекпойнт WAL по таймеру и условный запрос
- рядом с воркером свёртки живёт горутина, раз в минуту разбирающая журнал пассивным чекпойнтом; «журнал не разбирается» видно строкой владельцу, а не только по `df`. Признак — пара чисел, а не флаг занятости: тот молчит под удерживаемым читателем (`busy=0` при 6256 страницах и пяти перенесённых), а при занятой блокировке отдаёт `-1` вместо ответа, и `-1 >= -1` читалось бы как «разобрано целиком» - каталог отвечает `304` на `If-None-Match`, не открывая снимок витрины. Метка собрана из всего, от чего зависит ответ: версии витрины (`data_version` с закреплённого соединения плюс поколение — значение локально для соединения и не переживает переоткрытия), горизонта измерения и области действия ресурса. Версия снимается до и после сборки: снятая после пометила бы устаревший снимок свежим номером - предел и дедлайн ответа отложены в задачу Read API точек вместе с измеренной ценой первого запроса; попутно починен флаки-тест чужой задачи, искавший значение точки в сыром буфере записи лога
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -294,6 +295,16 @@ func newAPI(t *testing.T, writeTokens []string) (http.Handler, *store.Store) {
|
||||
// переписывать два десятка вызовов ради одного параметра.
|
||||
func newAPITokens(t *testing.T, writeTokens, readTokens []string) (http.Handler, *store.Store, *catalog.Service) {
|
||||
t.Helper()
|
||||
|
||||
h, st, cat, _ := newAPILogged(t, writeTokens, readTokens)
|
||||
return h, st, cat
|
||||
}
|
||||
|
||||
// newAPILogged отдаёт ещё и записи лога. Нужен там, где лог служит ОРАКУЛОМ, а
|
||||
// не наблюдением: единственный внешний признак того, что каталог собирался, —
|
||||
// его предупреждения владельцу.
|
||||
func newAPILogged(t *testing.T, writeTokens, readTokens []string) (http.Handler, *store.Store, *catalog.Service, *records) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
|
||||
st, err := store.Open(filepath.Join(dir, "healthlog.db"))
|
||||
@@ -307,7 +318,8 @@ func newAPITokens(t *testing.T, writeTokens, readTokens []string) (http.Handler,
|
||||
t.Fatalf("archive.New: %v", err)
|
||||
}
|
||||
|
||||
log := slog.New(slog.DiscardHandler)
|
||||
seen := &records{}
|
||||
log := slog.New(seen)
|
||||
cat := catalog.New(st, log)
|
||||
h := httpapi.New(httpapi.Options{
|
||||
Ingest: ingest.New(arch, st, nil, log),
|
||||
@@ -320,5 +332,41 @@ func newAPITokens(t *testing.T, writeTokens, readTokens []string) (http.Handler,
|
||||
// и это ровно тот транспорт, на котором приём обязан продолжать работать.
|
||||
IngestWriteBudget: time.Minute,
|
||||
})
|
||||
return h, st, cat
|
||||
return h, st, cat, seen
|
||||
}
|
||||
|
||||
// records — slog.Handler, копящий сообщения. Значений атрибутов не хранит:
|
||||
// проверяется факт записи, а данные о здоровье в тесты тащить незачем.
|
||||
type records struct {
|
||||
mu sync.Mutex
|
||||
msg []string
|
||||
}
|
||||
|
||||
func (r *records) Enabled(context.Context, slog.Level) bool { return true }
|
||||
|
||||
func (r *records) Handle(_ context.Context, rec slog.Record) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.msg = append(r.msg, rec.Message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *records) WithAttrs([]slog.Attr) slog.Handler { return r }
|
||||
func (r *records) WithGroup(string) slog.Handler { return r }
|
||||
|
||||
func (r *records) has(msg string) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
for _, m := range r.msg {
|
||||
if m == msg {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *records) reset() {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.msg = nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user