Цена читающего маршрута: чекпойнт WAL по таймеру и условный запрос
- рядом с воркером свёртки живёт горутина, раз в минуту разбирающая журнал пассивным чекпойнтом; «журнал не разбирается» видно строкой владельцу, а не только по `df`. Признак — пара чисел, а не флаг занятости: тот молчит под удерживаемым читателем (`busy=0` при 6256 страницах и пяти перенесённых), а при занятой блокировке отдаёт `-1` вместо ответа, и `-1 >= -1` читалось бы как «разобрано целиком» - каталог отвечает `304` на `If-None-Match`, не открывая снимок витрины. Метка собрана из всего, от чего зависит ответ: версии витрины (`data_version` с закреплённого соединения плюс поколение — значение локально для соединения и не переживает переоткрытия), горизонта измерения и области действия ресурса. Версия снимается до и после сборки: снятая после пометила бы устаревший снимок свежим номером - предел и дедлайн ответа отложены в задачу Read API точек вместе с измеренной ценой первого запроса; попутно починен флаки-тест чужой задачи, искавший значение точки в сыром буфере записи лога
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Сравнение меток слабое, звёздочка совпадает с любой существующей меткой, а
|
||||
// мусор условия не выполняет — и это не отказ: клиент, приславший кривой
|
||||
// заголовок, получает данные, а не `400`.
|
||||
func TestУсловиеЗапроса(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const tag = `W/"metrics.gen-7"`
|
||||
cases := []struct {
|
||||
name string
|
||||
header []string
|
||||
tag string
|
||||
want bool
|
||||
}{
|
||||
{"заголовка нет", nil, tag, false},
|
||||
{"пустая строка", []string{""}, tag, false},
|
||||
{"та же метка", []string{tag}, tag, true},
|
||||
{"та же метка без W/", []string{`"metrics.gen-7"`}, tag, true},
|
||||
{"чужая метка", []string{`W/"metrics.gen-8"`}, tag, false},
|
||||
{"метка другого ресурса", []string{`W/"points.gen-7"`}, tag, false},
|
||||
{"список, метка вторая", []string{`W/"metrics.gen-1", W/"metrics.gen-7"`}, tag, true},
|
||||
{"две строки заголовка", []string{`W/"metrics.gen-1"`, `W/"metrics.gen-7"`}, tag, true},
|
||||
{"запятая внутри метки", []string{`W/"points.a,b-7"`}, `W/"points.a,b-7"`, true},
|
||||
{"метка без закрывающей кавычки", []string{`W/"metrics.gen-7`}, tag, false},
|
||||
{"звёздочка", []string{"*"}, tag, true},
|
||||
{"звёздочка без метки", []string{"*"}, "", false},
|
||||
{"мусор", []string{"metrics.gen-7"}, tag, false},
|
||||
{"мусор со звёздочкой внутри", []string{`"*"`}, tag, false},
|
||||
{"метки нет", []string{tag}, "", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := notModified(c.header, c.tag); got != c.want {
|
||||
t.Errorf("%s: %v, ждали %v", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Пустая версия метки не даёт: подписать ответ нечем, и притворяться нельзя.
|
||||
func TestМеткаИзВерсии(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := etag("metrics", ""); got != "" {
|
||||
t.Errorf("пустая версия дала метку %q", got)
|
||||
}
|
||||
if got := etag("metrics", "gen-7"); got != `W/"metrics.gen-7"` {
|
||||
t.Errorf("метка %q, ждали слабую с областью", got)
|
||||
}
|
||||
if etag("metrics", "gen-7") == etag("points", "gen-7") {
|
||||
t.Error("метки разных ресурсов совпали — 304 отдал бы чужие данные")
|
||||
}
|
||||
}
|
||||
|
||||
// Подписать нечем — заголовка нет вовсе. Пустой `ETag:` синтаксически невалиден,
|
||||
// и что с ним сделает посредник, не определено ничем.
|
||||
func TestОтветБезВерсииНеНесётМетки(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
setReadHeaders(rec, "")
|
||||
if _, ok := rec.Header()["Etag"]; ok {
|
||||
t.Errorf("ответ без версии несёт метку %q", rec.Header().Get("ETag"))
|
||||
}
|
||||
if got := rec.Header().Get("Cache-Control"); got != "private, no-cache" {
|
||||
t.Errorf("правило кеширования %q — оно не зависит от наличия метки", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user