Files
MrAlders0n c58774a69f perf(api): allow /nodes pages of up to 2000
The map and neighbour graph load every node of a region, 200 at a time:
391 paced requests (~105 s) on a 78k-node mesh. A node row is cheap
(2000 rows took 26-38 ms against 10-16 ms for 200 on a prod copy), so
/nodes now takes up to 2000 per page; every other list keeps 200.
Older clients are unaffected.
2026-10-27 08:08:16 -04:00

46 lines
1.3 KiB
Go

// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
package handlers
import (
"errors"
"log/slog"
"net/http"
"strconv"
"time"
)
const maxListLimit int32 = 200
// The map and neighbour graph load every node of a region; a node row is cheap (2000 rows cost about
// twice the database time of 200), so bigger pages cut a whole-mesh load from ~390 requests to ~40.
const maxNodeListLimit int32 = 2000
const clockSkewTolerance = 5 * time.Minute // client clocks run ahead; accept a small future 'until'
// parseLimit caps the requested result size while retaining each endpoint's default.
func parseLimit(r *http.Request, defaultLimit int32) (int32, error) {
return parseLimitUpTo(r, defaultLimit, maxListLimit)
}
// parseLimitUpTo is parseLimit with an endpoint's own maximum.
func parseLimitUpTo(r *http.Request, defaultLimit, maxLimit int32) (int32, error) {
value := r.URL.Query().Get("limit")
if value == "" {
return defaultLimit, nil
}
limit, err := strconv.ParseInt(value, 10, 32)
if err != nil {
return 0, errors.New("limit must be an integer")
}
if limit <= 0 {
return 0, errors.New("limit must be positive")
}
if limit > int64(maxLimit) {
slog.Info("api: clamped list limit", "component", "api", "requested_limit", limit, "limit", maxLimit)
return maxLimit, nil
}
return int32(limit), nil
}