query_cost.go raw
1 package ratelimit
2
3 // QueryCostLevel categorizes query expense for adaptive rate limiting.
4 // Higher levels incur greater delays under load and are rejected first
5 // during emergency mode.
6 type QueryCostLevel int
7
8 const (
9 // CostTrivial is a single ID lookup or very constrained query.
10 CostTrivial QueryCostLevel = iota
11 // CostLight is a small query: few authors, small limit.
12 CostLight
13 // CostMedium is a moderate query: 11-100 authors or moderate complexity.
14 CostMedium
15 // CostHeavy is an expensive query: 101-1000 authors or no limit with wide scope.
16 CostHeavy
17 // CostExtreme is a very expensive query: 1000+ authors (follow-list queries).
18 CostExtreme
19 )
20
21 // String returns a human-readable name for the cost level.
22 func (c QueryCostLevel) String() string {
23 switch c {
24 case CostTrivial:
25 return "trivial"
26 case CostLight:
27 return "light"
28 case CostMedium:
29 return "medium"
30 case CostHeavy:
31 return "heavy"
32 case CostExtreme:
33 return "extreme"
34 default:
35 return "unknown"
36 }
37 }
38
39 // QueryCost holds the computed cost of a query.
40 type QueryCost struct {
41 Level QueryCostLevel
42 Multiplier float64 // Delay multiplier (0.1 = cheap, 5.0 = expensive)
43 AuthorCount int
44 KindCount int
45 IDCount int
46 HasLimit bool
47 Limit int
48 }
49
50 // ClassifyQuery computes the cost of a query based on filter parameters.
51 // It uses primitive types to avoid import cycles with nostr encoder packages.
52 func ClassifyQuery(authorCount, kindCount, idCount int, hasLimit bool, limit int) QueryCost {
53 cost := QueryCost{
54 AuthorCount: authorCount,
55 KindCount: kindCount,
56 IDCount: idCount,
57 HasLimit: hasLimit,
58 Limit: limit,
59 }
60
61 // ID-only queries are trivial (direct key lookups)
62 if idCount > 0 && authorCount == 0 {
63 cost.Level = CostTrivial
64 cost.Multiplier = 0.1
65 return cost
66 }
67
68 switch {
69 case authorCount > 1000:
70 cost.Level = CostExtreme
71 cost.Multiplier = 5.0
72 case authorCount > 100:
73 cost.Level = CostHeavy
74 cost.Multiplier = 2.5
75 case authorCount > 10:
76 cost.Level = CostMedium
77 cost.Multiplier = 1.0
78 case authorCount <= 10 && hasLimit && limit <= 100:
79 cost.Level = CostLight
80 cost.Multiplier = 0.5
81 default:
82 // No authors specified but also no IDs — could be a wide scan
83 if !hasLimit {
84 cost.Level = CostMedium
85 cost.Multiplier = 1.0
86 } else {
87 cost.Level = CostLight
88 cost.Multiplier = 0.5
89 }
90 }
91
92 // A tight limit significantly reduces actual cost regardless of author count
93 if hasLimit && limit > 0 && limit <= 10 && cost.Level > CostLight {
94 cost.Level--
95 cost.Multiplier *= 0.5
96 }
97
98 return cost
99 }
100