forked from jsl/video_editing_poc
I added a reproducible cost-model harness in src/test/java/org/example/videoclips/perf/CostModelBenchmarkHarness.java:1 that turns the checked-in FFmpeg and object-storage benchmark baselines into per-source-minute estimates
under explicit planning assumptions. The checked-in companion doc is docs/cost-model-by-video-minute.md:1, and it now includes the modeled baseline table: - exact-veryfast-720p: 0.000754 USD / source min - exact-medium-720p: 0.000816 USD / source min - exact-veryfast-720p-slow-storage: 0.000802 USD / source min
This commit is contained in:
parent
632959104b
commit
1b9246bafe
|
|
@ -0,0 +1,44 @@
|
|||
# Cost Model By Video Minute
|
||||
|
||||
This document captures the reproducible planning baseline for performance plan item `Cost model by video minute`.
|
||||
|
||||
What this model is:
|
||||
|
||||
- A benchmark-backed planning model for cost per source-video minute.
|
||||
- Derived from the checked-in FFmpeg runtime baseline and object-storage bandwidth baseline.
|
||||
- Based on explicit example cost assumptions that are intended to be replaced with current vendor-specific prices during rollout.
|
||||
|
||||
What this model is not:
|
||||
|
||||
- It is not live cloud pricing.
|
||||
- It is not a production invoice forecast.
|
||||
- It does not include cross-region egress, observability platform costs, database costs, or support overhead.
|
||||
|
||||
How to rerun:
|
||||
|
||||
```bash
|
||||
mvn -q -Dtest=CostModelBenchmarkHarness test
|
||||
```
|
||||
|
||||
Generated artifact:
|
||||
|
||||
- `target/benchmarks/cost-model-by-video-minute.md`
|
||||
|
||||
Interpretation notes:
|
||||
|
||||
- `compute` reflects FFmpeg worker time from the preset benchmark.
|
||||
- `transfer` reflects worker time spent waiting on object-storage throughput assumptions.
|
||||
- `retention` scales with source and output bitrate assumptions plus retention days.
|
||||
- `requests` captures one source GET plus clip PUT activity and remains a minor component in the modeled scenarios.
|
||||
|
||||
Operational note:
|
||||
|
||||
- Replace the example hourly and storage rates with current prices from the target cloud before using this model for budgeting or margin decisions.
|
||||
|
||||
Modeled baseline on this workspace:
|
||||
|
||||
| Scenario | Compute USD / source min | Transfer USD / source min | Retention USD / source min | Request USD / source min | Total USD / source min |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| `exact-veryfast-720p` | 0.000189 | 0.000048 | 0.000472 | 0.000045 | 0.000754 |
|
||||
| `exact-medium-720p` | 0.000251 | 0.000048 | 0.000472 | 0.000045 | 0.000816 |
|
||||
| `exact-veryfast-720p-slow-storage` | 0.000189 | 0.000096 | 0.000472 | 0.000045 | 0.000802 |
|
||||
|
|
@ -715,7 +715,7 @@ Keep the domain independent from Spring framework details. Adapters implement st
|
|||
3. [x] Object-storage bandwidth tests.
|
||||
4. [x] Queue visibility-timeout tuning.
|
||||
5. [x] Autoscaling policies.
|
||||
6. [ ] Cost model by video minute.
|
||||
6. [x] Cost model by video minute.
|
||||
|
||||
### Operational Readiness
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
package org.example.videoclips.perf;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
class CostModelBenchmarkHarness {
|
||||
|
||||
@Test
|
||||
void generateCostModelByVideoMinuteReport() throws IOException {
|
||||
Path reportDir = Path.of("target/benchmarks");
|
||||
Files.createDirectories(reportDir);
|
||||
|
||||
CostAssumptions assumptions = new CostAssumptions(
|
||||
0.096,
|
||||
0.023,
|
||||
0.005,
|
||||
0.0004,
|
||||
8.0,
|
||||
4.0,
|
||||
7,
|
||||
7
|
||||
);
|
||||
|
||||
List<Scenario> scenarios = List.of(
|
||||
new Scenario("exact-veryfast-720p", 16.0, 1.89261, 50.0),
|
||||
new Scenario("exact-medium-720p", 16.0, 2.50786, 50.0),
|
||||
new Scenario("exact-veryfast-720p-slow-storage", 16.0, 1.89261, 25.0)
|
||||
);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("# Cost Model By Video Minute\n\n");
|
||||
builder.append("Methodology:\n");
|
||||
builder.append("- Compute runtime ratio comes from the checked-in FFmpeg preset benchmark.\n");
|
||||
builder.append("- Storage transfer time uses the checked-in object-storage bandwidth baseline.\n");
|
||||
builder.append("- Costs below use explicit planning assumptions, not live cloud pricing.\n");
|
||||
builder.append("- Replace the hourly and storage rates with current vendor-specific numbers before production budgeting.\n\n");
|
||||
builder.append("Assumptions:\n");
|
||||
builder.append("- Worker pod cost: `$").append(format(assumptions.workerPodUsdPerHour())).append("` per hour\n");
|
||||
builder.append("- Object storage: `$").append(format(assumptions.objectStorageUsdPerGiBMonth())).append("` per GiB-month\n");
|
||||
builder.append("- PUT requests: `$").append(format(assumptions.putUsdPerThousandRequests())).append("` per 1,000 requests\n");
|
||||
builder.append("- GET requests: `$").append(format(assumptions.getUsdPerThousandRequests())).append("` per 1,000 requests\n");
|
||||
builder.append("- Source bitrate: `").append(format(assumptions.sourceBitrateMbps())).append(" Mbps`\n");
|
||||
builder.append("- Output bitrate: `").append(format(assumptions.outputBitrateMbps())).append(" Mbps`\n");
|
||||
builder.append("- Source retention: `").append(assumptions.sourceRetentionDays()).append("` days\n");
|
||||
builder.append("- Clip retention: `").append(assumptions.clipRetentionDays()).append("` days\n\n");
|
||||
builder.append("| Scenario | Compute USD / source min | Transfer USD / source min | Retention USD / source min | Request USD / source min | Total USD / source min |\n");
|
||||
builder.append("| --- | ---: | ---: | ---: | ---: | ---: |\n");
|
||||
|
||||
for (Scenario scenario : scenarios) {
|
||||
ScenarioCost cost = calculateScenarioCost(scenario, assumptions);
|
||||
builder.append("| ")
|
||||
.append(scenario.name()).append(" | ")
|
||||
.append(format(cost.computeUsdPerSourceMinute())).append(" | ")
|
||||
.append(format(cost.transferUsdPerSourceMinute())).append(" | ")
|
||||
.append(format(cost.retentionUsdPerSourceMinute())).append(" | ")
|
||||
.append(format(cost.requestUsdPerSourceMinute())).append(" | ")
|
||||
.append(format(cost.totalUsdPerSourceMinute())).append(" |\n");
|
||||
}
|
||||
|
||||
builder.append("\nFormulas:\n");
|
||||
builder.append("- `compute = worker_seconds_per_source_minute / 3600 * worker_pod_usd_per_hour`\n");
|
||||
builder.append("- `transfer = staging_seconds_per_source_minute / 3600 * worker_pod_usd_per_hour`\n");
|
||||
builder.append("- `retention = retained_gib_per_source_minute * usd_per_gib_month * retention_days / 30`\n");
|
||||
builder.append("- `requests = (1 GET + 1 PUT + clip_count PUTs) * request_rate`\n");
|
||||
|
||||
Path reportPath = reportDir.resolve("cost-model-by-video-minute.md");
|
||||
Files.writeString(reportPath, builder.toString());
|
||||
System.out.println("Cost model report written to " + reportPath.toAbsolutePath());
|
||||
}
|
||||
|
||||
private ScenarioCost calculateScenarioCost(Scenario scenario, CostAssumptions assumptions) {
|
||||
double workerSecondsPerSourceMinute = scenario.ffmpegWallSecondsForSample() / scenario.sampleDurationSeconds() * 60.0;
|
||||
double sourceMiBPerMinute = assumptions.sourceBitrateMbps() * 60.0 / 8.0;
|
||||
double outputMiBPerMinute = assumptions.outputBitrateMbps() * 60.0 / 8.0;
|
||||
double transferSecondsPerSourceMinute = (sourceMiBPerMinute + outputMiBPerMinute) / scenario.storageBandwidthMiBPerSecond();
|
||||
|
||||
double computeUsdPerSourceMinute = workerSecondsPerSourceMinute / 3600.0 * assumptions.workerPodUsdPerHour();
|
||||
double transferUsdPerSourceMinute = transferSecondsPerSourceMinute / 3600.0 * assumptions.workerPodUsdPerHour();
|
||||
|
||||
double retainedGiBPerSourceMinute = ((sourceMiBPerMinute * assumptions.sourceRetentionDays())
|
||||
+ (outputMiBPerMinute * assumptions.clipRetentionDays()))
|
||||
/ 1024.0;
|
||||
double retentionUsdPerSourceMinute = retainedGiBPerSourceMinute
|
||||
* assumptions.objectStorageUsdPerGiBMonth()
|
||||
/ 30.0;
|
||||
|
||||
int clipCountPerSourceMinute = (int) Math.ceil(60.0 / 8.0);
|
||||
double requestUsdPerSourceMinute = assumptions.getUsdPerThousandRequests() / 1000.0
|
||||
+ ((1.0 + clipCountPerSourceMinute) * assumptions.putUsdPerThousandRequests() / 1000.0);
|
||||
|
||||
return new ScenarioCost(
|
||||
computeUsdPerSourceMinute,
|
||||
transferUsdPerSourceMinute,
|
||||
retentionUsdPerSourceMinute,
|
||||
requestUsdPerSourceMinute
|
||||
);
|
||||
}
|
||||
|
||||
private String format(double value) {
|
||||
return String.format(Locale.ROOT, "%.6f", value);
|
||||
}
|
||||
|
||||
private record CostAssumptions(
|
||||
double workerPodUsdPerHour,
|
||||
double objectStorageUsdPerGiBMonth,
|
||||
double putUsdPerThousandRequests,
|
||||
double getUsdPerThousandRequests,
|
||||
double sourceBitrateMbps,
|
||||
double outputBitrateMbps,
|
||||
int sourceRetentionDays,
|
||||
int clipRetentionDays
|
||||
) {
|
||||
}
|
||||
|
||||
private record Scenario(String name, double sampleDurationSeconds, double ffmpegWallSecondsForSample, double storageBandwidthMiBPerSecond) {
|
||||
}
|
||||
|
||||
private record ScenarioCost(
|
||||
double computeUsdPerSourceMinute,
|
||||
double transferUsdPerSourceMinute,
|
||||
double retentionUsdPerSourceMinute,
|
||||
double requestUsdPerSourceMinute
|
||||
) {
|
||||
private double totalUsdPerSourceMinute() {
|
||||
return computeUsdPerSourceMinute + transferUsdPerSourceMinute + retentionUsdPerSourceMinute + requestUsdPerSourceMinute;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue