From 1160f16d92cfaf97ae39a1b17a430592ec7e13a0 Mon Sep 17 00:00:00 2001 From: Radu Berinde Date: Tue, 30 Mar 2021 13:49:22 -0400 Subject: [PATCH] tenantrate: add "test" that reports IOPS estimations This change adds a "test" facility which takes the description of a uniform workload (read percentage, read size, write size) and prints out an estimation of the sustained IOPS and burst IO. This will allow a better understanding of how changes to the settings or the mechanism translate into IOPS changes. Release note: None --- pkg/kv/kvserver/tenantrate/helpers_test.go | 23 ++++++ pkg/kv/kvserver/tenantrate/limiter_test.go | 78 ++++++++++++++++++- .../tenantrate/testdata/estimate_iops | 49 ++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 pkg/kv/kvserver/tenantrate/testdata/estimate_iops diff --git a/pkg/kv/kvserver/tenantrate/helpers_test.go b/pkg/kv/kvserver/tenantrate/helpers_test.go index 81f29ec0ff94..96d4701b91fd 100644 --- a/pkg/kv/kvserver/tenantrate/helpers_test.go +++ b/pkg/kv/kvserver/tenantrate/helpers_test.go @@ -24,3 +24,26 @@ func OverrideSettingsWithRateLimits(settings *cluster.Settings, rl LimitConfigs) writeRateLimit.Override(&settings.SV, int64(rl.WriteBytes.Rate)) writeBurstLimit.Override(&settings.SV, rl.WriteBytes.Burst) } + +// DefaultLimitConfigs returns the configuration that corresponds to the default +// setting values. +func DefaultLimitConfigs() LimitConfigs { + return LimitConfigs{ + ReadRequests: LimitConfig{ + Rate: Limit(readRequestRateLimit.Default()), + Burst: readRequestBurstLimit.Default(), + }, + WriteRequests: LimitConfig{ + Rate: Limit(writeRequestRateLimit.Default()), + Burst: writeRequestBurstLimit.Default(), + }, + ReadBytes: LimitConfig{ + Rate: Limit(readRateLimit.Default()), + Burst: readBurstLimit.Default(), + }, + WriteBytes: LimitConfig{ + Rate: Limit(writeRateLimit.Default()), + Burst: writeBurstLimit.Default(), + }, + } +} diff --git a/pkg/kv/kvserver/tenantrate/limiter_test.go b/pkg/kv/kvserver/tenantrate/limiter_test.go index 1c94d70bb958..87ddf3ff609f 100644 --- a/pkg/kv/kvserver/tenantrate/limiter_test.go +++ b/pkg/kv/kvserver/tenantrate/limiter_test.go @@ -15,6 +15,7 @@ import ( "bytes" "context" "fmt" + "math" "regexp" "sort" "strings" @@ -30,6 +31,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/timeutil" "github.com/cockroachdb/datadriven" "github.com/cockroachdb/errors" + "github.com/dustin/go-humanize" "github.com/stretchr/testify/require" "gopkg.in/yaml.v2" ) @@ -105,10 +107,11 @@ var testStateCommands = map[string]func(*testState, *testing.T, *datadriven.Test "metrics": (*testState).metrics, "get_tenants": (*testState).getTenants, "release_tenants": (*testState).releaseTenants, + "estimate_iops": (*testState).estimateIOPS, } func (ts *testState) run(t *testing.T, d *datadriven.TestData) string { - if !ts.initialized && d.Cmd != "init" { + if !ts.initialized && d.Cmd != "init" && d.Cmd != "estimate_iops" { d.Fatalf(t, "expected init as first command, got %q", d.Cmd) } if f, ok := testStateCommands[d.Cmd]; ok { @@ -486,6 +489,79 @@ func (ts *testState) releaseTenants(t *testing.T, d *datadriven.TestData) string return ts.FormatTenants() } +// estimateIOPS takes in the description of a workload and produces an estimate +// of the IOPS for that workload (under the default settings). +// +// For example: +// +// estimate_iops +// readpercentage: 50 +// readsize: 4096 +// writesize: 4096 +// ---- +// Mixed workload (50% reads; 4.0 KiB reads; 4.0 KiB writes): 256 sustained IOPS, 256 burst. +// +func (ts *testState) estimateIOPS(t *testing.T, d *datadriven.TestData) string { + var workload struct { + ReadPercentage int + ReadSize int + WriteSize int + } + if err := yaml.UnmarshalStrict([]byte(d.Input), &workload); err != nil { + d.Fatalf(t, "failed to parse workload information: %v", err) + } + if workload.ReadPercentage < 0 || workload.ReadPercentage > 100 { + d.Fatalf(t, "Invalid read percentage %d", workload.ReadPercentage) + } + limits := tenantrate.DefaultLimitConfigs() + + calculateIOPS := func(readRate, readBytesRate, writeRate, writeBytesRate float64) float64 { + readIOPS := math.Min(readRate, readBytesRate/float64(workload.ReadSize)) + writeIOPS := math.Min(writeRate, writeBytesRate/float64(workload.WriteSize)) + // The reads and writes are rate-limited separately; our workload will be + // bottlenecked on one of them. + return math.Min( + writeIOPS*100.0/float64(100-workload.ReadPercentage), + readIOPS*100.0/float64(workload.ReadPercentage), + ) + } + + sustained := calculateIOPS( + float64(limits.ReadRequests.Rate), float64(limits.ReadBytes.Rate), + float64(limits.WriteRequests.Rate), float64(limits.WriteBytes.Rate), + ) + + burst := calculateIOPS( + float64(limits.ReadRequests.Burst), float64(limits.ReadBytes.Burst), + float64(limits.WriteRequests.Burst), float64(limits.WriteBytes.Burst), + ) + fmtFloat := func(val float64) string { + if val < 10 { + return fmt.Sprintf("%.1f", val) + } + return fmt.Sprintf("%.0f", val) + } + switch workload.ReadPercentage { + case 0: + return fmt.Sprintf( + "Write-only workload (%s writes): %s sustained IOPS, %s burst.", + humanize.IBytes(uint64(workload.WriteSize)), fmtFloat(sustained), fmtFloat(burst), + ) + case 100: + return fmt.Sprintf( + "Read-only workload (%s reads): %s sustained IOPS, %s burst.", + humanize.IBytes(uint64(workload.ReadSize)), fmtFloat(sustained), fmtFloat(burst), + ) + default: + return fmt.Sprintf( + "Mixed workload (%d%% reads; %s reads; %s writes): %s sustained IOPS, %s burst.", + workload.ReadPercentage, + humanize.IBytes(uint64(workload.ReadSize)), humanize.IBytes(uint64(workload.WriteSize)), + fmtFloat(sustained), fmtFloat(burst), + ) + } +} + func (rs *testState) FormatRunning() string { var states []string for _, ls := range rs.running { diff --git a/pkg/kv/kvserver/tenantrate/testdata/estimate_iops b/pkg/kv/kvserver/tenantrate/testdata/estimate_iops new file mode 100644 index 000000000000..4054e0b0f01a --- /dev/null +++ b/pkg/kv/kvserver/tenantrate/testdata/estimate_iops @@ -0,0 +1,49 @@ +estimate_iops +readpercentage: 100 +readsize: 4096 +---- +Read-only workload (4.0 KiB reads): 128 sustained IOPS, 512 burst. + +estimate_iops +readpercentage: 100 +readsize: 65536 +---- +Read-only workload (64 KiB reads): 16 sustained IOPS, 256 burst. + +estimate_iops +readpercentage: 100 +readsize: 1048576 +---- +Read-only workload (1.0 MiB reads): 1.0 sustained IOPS, 16 burst. + +estimate_iops +readpercentage: 0 +writesize: 4096 +---- +Write-only workload (4.0 KiB writes): 128 sustained IOPS, 512 burst. + +estimate_iops +readpercentage: 0 +writesize: 65536 +---- +Write-only workload (64 KiB writes): 8.0 sustained IOPS, 128 burst. + +estimate_iops +readpercentage: 0 +writesize: 1048576 +---- +Write-only workload (1.0 MiB writes): 0.5 sustained IOPS, 8.0 burst. + +estimate_iops +readpercentage: 50 +readsize: 4096 +writesize: 4096 +---- +Mixed workload (50% reads; 4.0 KiB reads; 4.0 KiB writes): 256 sustained IOPS, 1024 burst. + +estimate_iops +readpercentage: 90 +readsize: 4096 +writesize: 4096 +---- +Mixed workload (90% reads; 4.0 KiB reads; 4.0 KiB writes): 142 sustained IOPS, 569 burst.