Approach: Compute backend_rps = incoming_rps * (1 - cache_hit_rate) * downstream_requests_per_miss (assumes each cache miss triggers downstream_requests_per_miss independent downstream requests). Then compute number_of_instances_needed = ceil(backend_rps / instance_capacity). Validate inputs.python
import math
import unittest
def capacity_plan(incoming_rps, cache_hit_rate, downstream_requests_per_miss, instance_capacity):
"""
Returns (backend_rps, number_of_instances_needed)
- incoming_rps: total incoming requests per second (float >=0)
- cache_hit_rate: fraction [0,1] of requests served by cache
- downstream_requests_per_miss: average downstream calls triggered per miss (>=0)
- instance_capacity: downstream request handling capacity per instance (requests/sec, >0)
"""
if incoming_rps < 0:
raise ValueError("incoming_rps must be >= 0")
if not (0 <= cache_hit_rate <= 1):
raise ValueError("cache_hit_rate must be between 0 and 1")
if downstream_requests_per_miss < 0:
raise ValueError("downstream_requests_per_miss must be >= 0")
if instance_capacity <= 0:
raise ValueError("instance_capacity must be > 0")
miss_rate = 1.0 - cache_hit_rate
backend_rps = incoming_rps * miss_rate * downstream_requests_per_miss
instances = math.ceil(backend_rps / instance_capacity) if backend_rps > 0 else 0
return backend_rps, instances
class TestCapacityPlan(unittest.TestCase):
def test_basic(self):
brps, inst = capacity_plan(1000, 0.9, 1, 100)
# 10% misses -> 100 misses * 1 downstream = 100 backend rps -> needs 1 instance
self.assertEqual(brps, 100.0)
self.assertEqual(inst, 1)
def test_multiple_downstream(self):
brps, inst = capacity_plan(500, 0.8, 3, 200)
# misses=100 -> backend=300 -> instances=2
self.assertEqual(brps, 300.0)
self.assertEqual(inst, 2)
def test_zero_traffic(self):
brps, inst = capacity_plan(0, 0.5, 5, 10)
self.assertEqual(brps, 0.0)
self.assertEqual(inst, 0)
def test_full_cache(self):
brps, inst = capacity_plan(100, 1.0, 10, 10)
self.assertEqual(brps, 0.0)
self.assertEqual(inst, 0)
def test_invalid_inputs(self):
with self.assertRaises(ValueError):
capacity_plan(-1, 0.5, 1, 10)
with self.assertRaises(ValueError):
capacity_plan(10, 1.5, 1, 10)
with self.assertRaises(ValueError):
capacity_plan(10, 0.5, -1, 10)
with self.assertRaises(ValueError):
capacity_plan(10, 0.5, 1, 0)
if __name__ == "__main__":
unittest.main()
Key points and assumptions:- Assumes cache misses are independent and each miss multiplies downstream requests by downstream_requests_per_miss (an average).- backend_rps models the downstream service load only; it doesn't include cache-serving work.- Uses ceiling to ensure enough instances (0 when backend_rps==0).- Does not model burstiness, retries, queueing, or latency—if those matter, add safety factors (e.g., 1.2 headroom) or use p95 traffic for capacity.Complexity: O(1) time and O(1) space. Edge cases: zero traffic, full cache, fractional rates, and invalid inputs handled.