@@ -118,6 +118,160 @@ public function testBenchmarks(): void
118118 ], Usage::TYPE_EVENT );
119119 });
120120
121+ // Multi-dim MV scenarios — bench_*_mv variants exercise the same
122+ // request shape with setUseDailyRollups on. Commit 5 wires the
123+ // routing through; until then both variants scan raw and produce
124+ // the same numbers.
125+ $ this ->runBench ('bench_events_topN_path_30d_mv ' , function (string $ queryId ) use ($ start , $ end ): void {
126+ $ this ->adapter ->setUseDailyRollups (true );
127+ $ this ->adapter ->setNextQueryId ($ queryId );
128+ $ this ->usage ->find ([
129+ UsageQuery::groupBy ('path ' ),
130+ Query::equal ('metric ' , [$ this ->metric ]),
131+ Query::greaterThanEqual ('time ' , $ start ),
132+ Query::lessThanEqual ('time ' , $ end ),
133+ Query::limit (500 ),
134+ ], Usage::TYPE_EVENT );
135+ $ this ->adapter ->setUseDailyRollups (false );
136+ });
137+
138+ $ this ->runBench ('bench_events_topN_country_30d_mv ' , function (string $ queryId ) use ($ start , $ end ): void {
139+ $ this ->adapter ->setUseDailyRollups (true );
140+ $ this ->adapter ->setNextQueryId ($ queryId );
141+ $ this ->usage ->find ([
142+ UsageQuery::groupBy ('country ' ),
143+ Query::equal ('metric ' , [$ this ->metric ]),
144+ Query::greaterThanEqual ('time ' , $ start ),
145+ Query::lessThanEqual ('time ' , $ end ),
146+ Query::limit (200 ),
147+ ], Usage::TYPE_EVENT );
148+ $ this ->adapter ->setUseDailyRollups (false );
149+ });
150+
151+ $ this ->runBench ('bench_events_topN_service_30d_mv ' , function (string $ queryId ) use ($ start , $ end ): void {
152+ $ this ->adapter ->setUseDailyRollups (true );
153+ $ this ->adapter ->setNextQueryId ($ queryId );
154+ $ this ->usage ->find ([
155+ UsageQuery::groupBy ('service ' ),
156+ Query::equal ('metric ' , [$ this ->metric ]),
157+ Query::greaterThanEqual ('time ' , $ start ),
158+ Query::lessThanEqual ('time ' , $ end ),
159+ Query::limit (200 ),
160+ ], Usage::TYPE_EVENT );
161+ $ this ->adapter ->setUseDailyRollups (false );
162+ });
163+
164+ $ this ->runBench ('bench_events_topN_method_status_30d_mv ' , function (string $ queryId ) use ($ start , $ end ): void {
165+ $ this ->adapter ->setUseDailyRollups (true );
166+ $ this ->adapter ->setNextQueryId ($ queryId );
167+ $ this ->usage ->find ([
168+ UsageQuery::groupBy ('method ' ),
169+ UsageQuery::groupBy ('status ' ),
170+ Query::equal ('metric ' , [$ this ->metric ]),
171+ Query::greaterThanEqual ('time ' , $ start ),
172+ Query::lessThanEqual ('time ' , $ end ),
173+ Query::limit (200 ),
174+ ], Usage::TYPE_EVENT );
175+ $ this ->adapter ->setUseDailyRollups (false );
176+ });
177+
178+ $ todayStart = (new \DateTime ('today ' , new \DateTimeZone ('UTC ' )))->format ('Y-m-d H:i:s ' );
179+ $ todayEnd = (new \DateTime ('+2 hour ' ))->format ('Y-m-d H:i:s ' );
180+ $ this ->runBench ('bench_events_topN_path_today_partial ' , function (string $ queryId ) use ($ todayStart , $ todayEnd ): void {
181+ $ this ->adapter ->setUseDailyRollups (true );
182+ $ this ->adapter ->setNextQueryId ($ queryId );
183+ $ this ->usage ->find ([
184+ UsageQuery::groupBy ('path ' ),
185+ Query::equal ('metric ' , [$ this ->metric ]),
186+ Query::greaterThanEqual ('time ' , $ todayStart ),
187+ Query::lessThanEqual ('time ' , $ todayEnd ),
188+ Query::limit (500 ),
189+ ], Usage::TYPE_EVENT );
190+ $ this ->adapter ->setUseDailyRollups (false );
191+ });
192+
193+ // Must route to raw — filter on a column the path MV doesn't index.
194+ $ this ->runBench ('bench_events_topN_path_30d_filtered_resource ' , function (string $ queryId ) use ($ start , $ end ): void {
195+ $ this ->adapter ->setUseDailyRollups (true );
196+ $ this ->adapter ->setNextQueryId ($ queryId );
197+ $ this ->usage ->find ([
198+ UsageQuery::groupBy ('path ' ),
199+ Query::equal ('metric ' , [$ this ->metric ]),
200+ Query::equal ('resource ' , ['function ' ]),
201+ Query::greaterThanEqual ('time ' , $ start ),
202+ Query::lessThanEqual ('time ' , $ end ),
203+ Query::limit (500 ),
204+ ], Usage::TYPE_EVENT );
205+ $ this ->adapter ->setUseDailyRollups (false );
206+ });
207+
208+ // Multi-dim breakdown — no single MV covers both path AND country.
209+ $ this ->runBench ('bench_events_topN_path_country ' , function (string $ queryId ) use ($ start , $ end ): void {
210+ $ this ->adapter ->setUseDailyRollups (true );
211+ $ this ->adapter ->setNextQueryId ($ queryId );
212+ $ this ->usage ->find ([
213+ UsageQuery::groupBy ('path ' ),
214+ UsageQuery::groupBy ('country ' ),
215+ Query::equal ('metric ' , [$ this ->metric ]),
216+ Query::greaterThanEqual ('time ' , $ start ),
217+ Query::lessThanEqual ('time ' , $ end ),
218+ Query::limit (500 ),
219+ ], Usage::TYPE_EVENT );
220+ $ this ->adapter ->setUseDailyRollups (false );
221+ });
222+
223+ // Write fan-out cost with all MVs attached. Compare against
224+ // bench_insert_10k for the multiplier (must stay ≤ 1.3×).
225+ $ this ->runBench ('bench_insert_with_mvs ' , function (string $ queryId ): void {
226+ $ batch = [];
227+ for ($ i = 0 ; $ i < 10000 ; $ i ++) {
228+ $ batch [] = [
229+ 'metric ' => 'bench.insert.with_mvs ' ,
230+ 'value ' => $ i ,
231+ 'tags ' => [
232+ 'path ' => '/v1/mv/ ' . ($ i % 100 ),
233+ 'method ' => 'POST ' ,
234+ 'status ' => '201 ' ,
235+ 'service ' => 'storage ' ,
236+ 'country ' => 'us ' ,
237+ ],
238+ ];
239+ }
240+ $ this ->adapter ->setNextQueryId ($ queryId );
241+ $ this ->usage ->addBatch ($ batch , Usage::TYPE_EVENT );
242+ }, 3 );
243+
244+ // Catches buffered/async insert lag: write then read the same key.
245+ $ this ->runBench ('bench_mv_lag ' , function (string $ queryId ): void {
246+ $ this ->usage ->addBatch ([
247+ ['metric ' => 'bench.mv.lag ' , 'value ' => 1 , 'tags ' => ['path ' => '/v1/mv-lag ' ]],
248+ ], Usage::TYPE_EVENT );
249+ $ this ->adapter ->setNextQueryId ($ queryId );
250+ $ this ->usage ->find ([
251+ UsageQuery::groupBy ('path ' ),
252+ Query::equal ('metric ' , ['bench.mv.lag ' ]),
253+ Query::limit (10 ),
254+ ], Usage::TYPE_EVENT );
255+ }, 3 );
256+
257+ // Storage footprint per busy project — reads system.parts.
258+ $ this ->runBench ('bench_mv_storage_per_busy_project ' , function (string $ queryId ): void {
259+ $ reflection = new \ReflectionClass ($ this ->adapter );
260+ $ getDatabase = $ reflection ->getProperty ('database ' );
261+ $ getDatabase ->setAccessible (true );
262+ $ databaseValue = $ getDatabase ->getValue ($ this ->adapter );
263+ $ database = is_string ($ databaseValue ) ? $ databaseValue : '' ;
264+ $ namespace = $ this ->namespace ;
265+ $ this ->adapter ->setNextQueryId ($ queryId );
266+ $ this ->runRawSql (
267+ "SELECT sum(bytes_on_disk) AS total_bytes "
268+ . "FROM system.parts "
269+ . "WHERE database = ' " . addslashes ($ database ) . "' "
270+ . "AND table LIKE ' " . addslashes ($ namespace ) . "_usage_events_daily_by_%' "
271+ . "AND active = 1 FORMAT JSON "
272+ );
273+ }, 3 );
274+
121275 $ this ->assertNotEmpty ($ this ->results , 'Benchmark scenarios must record results ' );
122276 }
123277}
0 commit comments