service-capacity-modeling 0.3.98__py3-none-any.whl → 0.3.100__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1064,12 +1064,36 @@ class CapacityDesires(ExcludeUnsetModel):
1064
1064
  .get("estimated_state_size_gib", None)
1065
1065
  )
1066
1066
  user_count = self.data_shape.estimated_state_item_count
1067
- item_size_bytes = desires.query_pattern.estimated_mean_write_size_bytes.mid
1067
+
1068
+ # Bidirectional inference: state_size_gib ↔ item_count
1069
+ #
1070
+ # Users often know one but not the other. We can infer the missing value:
1071
+ # state_size_gib = item_count × item_size_bytes
1072
+ # item_count = state_size_gib / item_size_bytes
1073
+ #
1074
+ # Item size comes from write_size (the size of records written to storage).
1075
+ # For read-only workloads (e.g., read replicas), use read_size as fallback.
1076
+ # If user provided both or neither, no inference is needed.
1077
+ item_size_bytes = (
1078
+ desires.query_pattern.estimated_mean_write_size_bytes.mid
1079
+ or desires.query_pattern.estimated_mean_read_size_bytes.mid
1080
+ )
1081
+
1068
1082
  if user_size is None and user_count is not None:
1083
+ if not item_size_bytes:
1084
+ raise ValueError(
1085
+ "Model default_desires() must set estimated_mean_write_size_bytes "
1086
+ "or estimated_mean_read_size_bytes to infer state_size_gib"
1087
+ )
1069
1088
  desires.data_shape.estimated_state_size_gib = user_count.scale(
1070
1089
  factor=(item_size_bytes / GIB_IN_BYTES)
1071
1090
  )
1072
1091
  elif user_size is not None and user_count is None:
1092
+ if not item_size_bytes:
1093
+ raise ValueError(
1094
+ "Model default_desires() must set estimated_mean_write_size_bytes "
1095
+ "or estimated_mean_read_size_bytes to infer item_count"
1096
+ )
1073
1097
  user_size_gib = self.data_shape.estimated_state_size_gib
1074
1098
  desires.data_shape.estimated_state_item_count = user_size_gib.scale(
1075
1099
  factor=(GIB_IN_BYTES / item_size_bytes)
@@ -33,7 +33,6 @@ def models() -> Dict[str, Any]:
33
33
  "org.netflix.counter": nflx_counter_capacity_model,
34
34
  "org.netflix.zookeeper": nflx_zookeeper_capacity_model,
35
35
  "org.netflix.evcache": nflx_evcache_capacity_model,
36
- "org.netflix.rds": nflx_rds_capacity_model,
37
36
  "org.netflix.elasticsearch": nflx_elasticsearch_capacity_model,
38
37
  "org.netflix.elasticsearch.node": nflx_elasticsearch_data_capacity_model,
39
38
  "org.netflix.elasticsearch.master": nflx_elasticsearch_master_capacity_model,
@@ -43,6 +42,7 @@ def models() -> Dict[str, Any]:
43
42
  "org.netflix.cockroachdb": nflx_cockroachdb_capacity_model,
44
43
  "org.netflix.aurora": nflx_aurora_capacity_model,
45
44
  "org.netflix.postgres": nflx_postgres_capacity_model,
45
+ "org.netflix.rds": nflx_rds_capacity_model,
46
46
  "org.netflix.kafka": nflx_kafka_capacity_model,
47
47
  "org.netflix.dynamodb": nflx_ddb_capacity_model,
48
48
  "org.netflix.wal": nflx_wal_capacity_model,
@@ -307,7 +307,7 @@ class NflxAuroraCapacityModel(CapacityModel):
307
307
 
308
308
  @staticmethod
309
309
  def allowed_platforms() -> Tuple[Platform, ...]:
310
- return Platform.aurora_mysql, Platform.aurora_mysql
310
+ return Platform.aurora_mysql, Platform.aurora_postgres
311
311
 
312
312
  @staticmethod
313
313
  def default_desires(
@@ -67,23 +67,50 @@ class NflxControlCapacityModel(CapacityModel):
67
67
  def compose_with(
68
68
  user_desires: CapacityDesires, extra_model_arguments: Dict[str, Any]
69
69
  ) -> Tuple[Tuple[str, Callable[[CapacityDesires], CapacityDesires]], ...]:
70
- def _modify_aurora_desires(
70
+ def _modify_rds_desires(
71
71
  user_desires: CapacityDesires,
72
72
  ) -> CapacityDesires:
73
+ """RDS proxy for Control service."""
73
74
  relaxed = user_desires.model_copy(deep=True)
74
75
 
75
- # Aurora doesn't support tier 0, so downgrade to tier 1
76
+ # RDS doesn't support tier 0
76
77
  if relaxed.service_tier == 0:
77
78
  relaxed.service_tier = 1
78
79
 
79
- # Control caches reads in memory, only writes go to Aurora
80
- # Set read QPS to minimal since Aurora only handles writes
80
+ # Control caches reads, so proxy only sees writes + minimal reads
81
+ relaxed.query_pattern.estimated_read_per_second = certain_int(1)
82
+ if relaxed.query_pattern.estimated_write_per_second:
83
+ relaxed.query_pattern.estimated_write_per_second = (
84
+ relaxed.query_pattern.estimated_write_per_second.scale(0.05)
85
+ )
86
+
87
+ # Minimal data footprint for connection metadata
88
+ relaxed.data_shape.estimated_state_size_gib = (
89
+ relaxed.data_shape.estimated_state_size_gib.scale(0.01)
90
+ )
91
+
92
+ return relaxed
93
+
94
+ def _modify_postgres_desires(
95
+ user_desires: CapacityDesires,
96
+ ) -> CapacityDesires:
97
+ relaxed = user_desires.model_copy(deep=True)
98
+
99
+ # Postgres doesn't support tier 0, so downgrade to tier 1
100
+ if relaxed.service_tier == 0:
101
+ relaxed.service_tier = 1
102
+
103
+ # Control caches reads in memory, only writes go to Postgres
104
+ # Set read QPS to minimal since Postgres only handles writes
81
105
  if relaxed.query_pattern.estimated_read_per_second:
82
106
  relaxed.query_pattern.estimated_read_per_second = certain_int(1)
83
107
 
84
108
  return relaxed
85
109
 
86
- return (("org.netflix.aurora", _modify_aurora_desires),)
110
+ return (
111
+ ("org.netflix.rds", _modify_rds_desires),
112
+ ("org.netflix.postgres", _modify_postgres_desires),
113
+ )
87
114
 
88
115
  @staticmethod
89
116
  def default_desires(
@@ -30,6 +30,10 @@ class NflxEntityCapacityModel(CapacityModel):
30
30
  desires: CapacityDesires,
31
31
  extra_model_arguments: Dict[str, Any],
32
32
  ) -> Optional[CapacityPlan]:
33
+ # Entity doesn't support tier 0
34
+ if desires.service_tier == 0:
35
+ return None
36
+
33
37
  # Entity wants 20GiB root volumes
34
38
  extra_model_arguments.setdefault("root_disk_gib", 20)
35
39
 
@@ -59,7 +63,35 @@ class NflxEntityCapacityModel(CapacityModel):
59
63
  def compose_with(
60
64
  user_desires: CapacityDesires, extra_model_arguments: Dict[str, Any]
61
65
  ) -> Tuple[Tuple[str, Callable[[CapacityDesires], CapacityDesires]], ...]:
62
- def _modify_crdb_desires(
66
+ def _modify_rds_desires(
67
+ user_desires: CapacityDesires,
68
+ ) -> CapacityDesires:
69
+ """RDS proxy handles connection pooling and auth translation.
70
+ Capacity needs are much lower than the backend database."""
71
+ relaxed = user_desires.model_copy(deep=True)
72
+
73
+ # RDS doesn't support tier 0
74
+ if relaxed.service_tier == 0:
75
+ relaxed.service_tier = 1
76
+
77
+ # Proxy layer sees ~5% of actual load due to connection pooling
78
+ if relaxed.query_pattern.estimated_read_per_second:
79
+ relaxed.query_pattern.estimated_read_per_second = (
80
+ relaxed.query_pattern.estimated_read_per_second.scale(0.05)
81
+ )
82
+ if relaxed.query_pattern.estimated_write_per_second:
83
+ relaxed.query_pattern.estimated_write_per_second = (
84
+ relaxed.query_pattern.estimated_write_per_second.scale(0.05)
85
+ )
86
+
87
+ # Proxy only needs to store connection metadata, not actual data
88
+ relaxed.data_shape.estimated_state_size_gib = (
89
+ relaxed.data_shape.estimated_state_size_gib.scale(0.01)
90
+ )
91
+
92
+ return relaxed
93
+
94
+ def _modify_postgres_desires(
63
95
  user_desires: CapacityDesires,
64
96
  ) -> CapacityDesires:
65
97
  relaxed = user_desires.model_copy(deep=True)
@@ -96,7 +128,8 @@ class NflxEntityCapacityModel(CapacityModel):
96
128
  return relaxed
97
129
 
98
130
  return (
99
- ("org.netflix.cockroachdb", _modify_crdb_desires),
131
+ ("org.netflix.rds", _modify_rds_desires),
132
+ ("org.netflix.postgres", _modify_postgres_desires),
100
133
  ("org.netflix.key-value", lambda x: x),
101
134
  ("org.netflix.elasticsearch", _modify_elasticsearch_desires),
102
135
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: service-capacity-modeling
3
- Version: 0.3.98
3
+ Version: 0.3.100
4
4
  Summary: Contains utilities for modeling capacity for pluggable workloads
5
5
  Author: Joseph Lynch
6
6
  Author-email: josephl@netflix.com
@@ -1,7 +1,7 @@
1
1
  service_capacity_modeling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  service_capacity_modeling/capacity_planner.py,sha256=JRagEFlg3u_zB1N5GzGsKAN55JZLad6p4IF_PmL8kcg,32780
3
3
  service_capacity_modeling/enum_utils.py,sha256=50Rw2kgYoJYCrybSbo9WaPPCWxlF5CyPCQtHxQ3kB18,5229
4
- service_capacity_modeling/interface.py,sha256=mgX2qUsYf_rVxbYg1rFjRUH-QY11Z87mke4nWa1LwhM,42327
4
+ service_capacity_modeling/interface.py,sha256=jTuMoMc6XEbMl859YNAwV9RiEgJf4phe6g1qHp7_kNg,43453
5
5
  service_capacity_modeling/stats.py,sha256=LCNUcQPfwF5hhIZwsfAsDe4ZbnuhDnl3vQHKfpK61Xc,6142
6
6
  service_capacity_modeling/hardware/__init__.py,sha256=P5ostvoSOMUqPODtepeFYb4qfTVH0E73mMFraP49rYU,9196
7
7
  service_capacity_modeling/hardware/profiles/__init__.py,sha256=7-y3JbCBkgzaAjFla2RIymREcImdZ51HTl3yn3vzoGw,1602
@@ -56,15 +56,15 @@ service_capacity_modeling/models/common.py,sha256=Bs-G1eHrb9qmS9qEWzx0rzN9QjQ419
56
56
  service_capacity_modeling/models/headroom_strategy.py,sha256=rGo_d7nxkQDjx0_hIAXKKZAWnQDBtqZhc0eTMouVh8s,682
57
57
  service_capacity_modeling/models/utils.py,sha256=WosEEg4o1_WSbTb5mL-M1v8JuWJgvS2oWvnDS3qNz3k,2662
58
58
  service_capacity_modeling/models/org/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
- service_capacity_modeling/models/org/netflix/__init__.py,sha256=2Ld2NPxiO3vbYtOMqHtrV4f1nEH390Hoxqo3I5NbBDI,2553
60
- service_capacity_modeling/models/org/netflix/aurora.py,sha256=Js33ZjxCtt34HiDPsWRT9mjKCAsnnCo9du15QArVFMo,13073
59
+ service_capacity_modeling/models/org/netflix/__init__.py,sha256=keaBt7dk6DB2VuRINdo8wRfsobK655Gfw3hYjruacJs,2553
60
+ service_capacity_modeling/models/org/netflix/aurora.py,sha256=ZCileXosW1A8D9QfPf8mCu1BIt4xKvzGdqU5iW487Jg,13076
61
61
  service_capacity_modeling/models/org/netflix/cassandra.py,sha256=6CV0UIWpK0lcfF9_-LGrUjDomysswCvoMYS3S-wpSOU,39627
62
- service_capacity_modeling/models/org/netflix/control.py,sha256=wkJnqG7Nn7kQNcKEMzl9LFlA9xTAji2wYxL011VAKPI,5762
62
+ service_capacity_modeling/models/org/netflix/control.py,sha256=-MqVCtr528vCElMAInFucX-NjsnERd4cot-mYDncbZg,6813
63
63
  service_capacity_modeling/models/org/netflix/counter.py,sha256=kTDL7dCnkn-XU27_Z1VBc4CCLCPoOqJZe9WgcENHHd4,10517
64
64
  service_capacity_modeling/models/org/netflix/crdb.py,sha256=iW7tyG8jpXhHIdXrw3DPYSHRAknPN42MlCRLJO4o9C8,20826
65
65
  service_capacity_modeling/models/org/netflix/ddb.py,sha256=9qRiuTqWev9zbYFFzewyowU7M41uALsuLklYx20yAXw,26502
66
66
  service_capacity_modeling/models/org/netflix/elasticsearch.py,sha256=zPrC6b2LNrAh3IWE3HCMUEYASacjYbHChbO4WZSMma4,25234
67
- service_capacity_modeling/models/org/netflix/entity.py,sha256=VHgEwnGtJAKlhvbE2kTif75OZmIsjjjoZrT6kb1LTgA,8750
67
+ service_capacity_modeling/models/org/netflix/entity.py,sha256=bL_7UcHb-E7zxbjmy1a1whMfYc4sRdvIL96B_aVs1hE,10139
68
68
  service_capacity_modeling/models/org/netflix/evcache.py,sha256=BDVRWely3F_3Ecb3Um3dQ024_I6XgvagpRJ6zdP5E18,25687
69
69
  service_capacity_modeling/models/org/netflix/graphkv.py,sha256=7ncEhx9lLsN_vGIKNHkvWfDdKffG7cYe91Wr-DB7IjU,8659
70
70
  service_capacity_modeling/models/org/netflix/iso_date_math.py,sha256=oC5sgIXDqwOp6-5z2bdTkm-bJLlnzhqcONI_tspHjac,1137
@@ -82,9 +82,9 @@ service_capacity_modeling/tools/auto_shape.py,sha256=K248-DayPrcZwLw1dYr47lpeQQw
82
82
  service_capacity_modeling/tools/fetch_pricing.py,sha256=fO84h77cqiiIHF4hZt490RwbZ6JqjB45UsnPpV2AXD4,6122
83
83
  service_capacity_modeling/tools/generate_missing.py,sha256=F7YqvMJAV4nZc20GNrlIsnQSF8_77sLgwYZqc5k4LDg,3099
84
84
  service_capacity_modeling/tools/instance_families.py,sha256=e5RuYkCLUITvsAazDH12B6KjX_PaBsv6Ne3mj0HK_sQ,9223
85
- service_capacity_modeling-0.3.98.dist-info/licenses/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
86
- service_capacity_modeling-0.3.98.dist-info/METADATA,sha256=_XVCm9T7EhrvHCSswx_57lwhTsn7R1O6-vYdPQOjQrE,10366
87
- service_capacity_modeling-0.3.98.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
88
- service_capacity_modeling-0.3.98.dist-info/entry_points.txt,sha256=ZsjzpG5SomWpT1zCE19n1uSXKH2gTI_yc33sdl0vmJg,146
89
- service_capacity_modeling-0.3.98.dist-info/top_level.txt,sha256=H8XjTCLgR3enHq5t3bIbxt9SeUkUT8HT_SDv2dgIT_A,26
90
- service_capacity_modeling-0.3.98.dist-info/RECORD,,
85
+ service_capacity_modeling-0.3.100.dist-info/licenses/LICENSE,sha256=nl_Lt5v9VvJ-5lWJDT4ddKAG-VZ-2IaLmbzpgYDz2hU,11343
86
+ service_capacity_modeling-0.3.100.dist-info/METADATA,sha256=LF_RV_duMAiGaOc7BX7jDNBjizo2BRtOX-cZtWLPYzw,10367
87
+ service_capacity_modeling-0.3.100.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
88
+ service_capacity_modeling-0.3.100.dist-info/entry_points.txt,sha256=ZsjzpG5SomWpT1zCE19n1uSXKH2gTI_yc33sdl0vmJg,146
89
+ service_capacity_modeling-0.3.100.dist-info/top_level.txt,sha256=H8XjTCLgR3enHq5t3bIbxt9SeUkUT8HT_SDv2dgIT_A,26
90
+ service_capacity_modeling-0.3.100.dist-info/RECORD,,