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