Skip to content

Commit 9f75e47

Browse files
authored
feat: add dynamic defaults for tuple write command (#517)
* feat: add dynamic defaults for tuple write when max rps set * validate tuple write integers * Use constants for dynamic write defaults * Refactor tuple write defaults * Validate integer flags for tuple write * chore: added changelog entry
1 parent bb88057 commit 9f75e47

4 files changed

Lines changed: 94 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
#### [Unreleased](https://github.com/openfga/cli/compare/v0.7.0...HEAD)
44

5+
Changed:
6+
- Adjusted defaults for `--max-tuples-per-write`, `--max-parallel-requests`, `--max-rps`, and `--rampup-period-in-sec` when `--max-rps` is specified (#517).
7+
8+
59
#### [0.7.0](https://github.com/openfga/cli/compare/v0.6.6...v0.7.0) (2025-06-10)
610

711
> [!NOTE]

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -671,17 +671,18 @@ fga tuple **write** <user> <relation> <object> --store-id=<store-id>
671671
* `--store-id`: Specifies the store id
672672
* `--model-id`: Specifies the model id to target (optional)
673673
* `--file`: Specifies the file name, `json`, `yaml` and `csv` files are supported
674-
* `--max-tuples-per-write`: Max tuples to send in a single write (optional, default=1)
675-
* `--max-parallel-requests`: Max requests to send in parallel (optional, default=4)
674+
* `--max-tuples-per-write`: Max tuples to send in a single write (optional, default=1, or 40 if `--max-rps` is set and this flag is omitted)
675+
* `--max-parallel-requests`: Max requests to send in parallel (optional, default=4, or `max-rps/5` if `--max-rps` is set and this flag is omitted)
676676
* `--hide-imported-tuples`: When importing from a file, do not output successfully imported tuples in the command output (optional, default=false)
677-
* `--max-rps`: Max requests per second, when set the CLI will ramp up requests from 1RPS to the set value over the set period. Used in conjunction with `--rampup-period-in-sec` (optional)
678-
* `--rampup-period-in-sec`: Time in seconds to wait between each batch of tuples when ramping up. Used in conjunction with `--max-rps` (optional)
677+
* `--max-rps`: Max requests per second. When set, the CLI will ramp up requests from 1 RPS to the set value. If `--rampup-period-in-sec` is omitted it defaults to `max-rps*2`.
678+
* `--rampup-period-in-sec`: Time in seconds to wait between each batch of tuples when ramping up. Only used if `--max-rps` is set.
679+
* All integer parameters must be greater than zero when provided.
679680

680681
###### Example (with arguments)
681682
- `fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne can_view document:roadmap`
682683
- `fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 user:anne can_view document:roadmap --condition-name inOffice --condition-context '{"office_ip":"10.0.1.10"}'`
683684
- `fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 --model-id=01GXSA8YR785C4FYS3C0RTG7B1 --file tuples.json`
684-
- `fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 --file tuples.csv --max-tuples-per-write 10 --max-parallel-requests 5 --max-rps 10 --rampup-period-in-sec 10`
685+
- `fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 --file tuples.csv --max-rps 10`
685686

686687
###### Response
687688
```json5

cmd/tuple/write.go

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ var writeCmd = &cobra.Command{
7474
fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 --file tuples.yaml
7575
fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 --file tuples.csv
7676
fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 --file tuples.csv --max-tuples-per-write 10 --max-parallel-requests 5
77-
fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 --file tuples.csv --max-tuples-per-write 10 --max-parallel-requests 5 --max-rps 10 --rampup-period-in-sec 10`,
77+
fga tuple write --store-id=01H0H015178Y2V4CX10C2KGHF4 --file tuples.csv --max-rps 10`,
7878
RunE: func(cmd *cobra.Command, args []string) error {
7979
clientConfig := cmdutils.GetClientConfig(cmd)
8080

@@ -97,6 +97,30 @@ func writeTuplesFromArgs(cmd *cobra.Command, args []string, fgaClient *client.Op
9797
return err //nolint:wrapcheck
9898
}
9999

100+
maxTuplesPerWrite, err := cmd.Flags().GetInt("max-tuples-per-write")
101+
if err != nil {
102+
return fmt.Errorf("failed to parse max-tuples-per-write due to %w", err)
103+
}
104+
105+
maxParallelRequests, err := cmd.Flags().GetInt("max-parallel-requests")
106+
if err != nil {
107+
return fmt.Errorf("failed to parse max-parallel-requests due to %w", err)
108+
}
109+
110+
maxRPS, err := cmd.Flags().GetInt("max-rps")
111+
if err != nil {
112+
return fmt.Errorf("failed to parse max-rps due to %w", err)
113+
}
114+
115+
rampUpPeriodInSec, err := cmd.Flags().GetInt("rampup-period-in-sec")
116+
if err != nil {
117+
return fmt.Errorf("failed to parse rampup-period-in-sec due to %w", err)
118+
}
119+
120+
if err := validateWriteFlags(cmd.Flags(), maxTuplesPerWrite, maxParallelRequests, maxRPS, rampUpPeriodInSec); err != nil {
121+
return err
122+
}
123+
100124
body := client.ClientWriteTuplesBody{
101125
client.ClientTupleKey{
102126
User: args[0],
@@ -122,6 +146,48 @@ func writeTuplesFromArgs(cmd *cobra.Command, args []string, fgaClient *client.Op
122146
)
123147
}
124148

149+
func validateWriteFlags(flags *flag.FlagSet, maxTuplesPerWrite, maxParallelRequests, maxRPS, rampUpPeriodInSec int) error {
150+
if flags.Changed("max-tuples-per-write") && maxTuplesPerWrite <= 0 {
151+
return errors.New("max-tuples-per-write must be greater than zero") //nolint:err113
152+
}
153+
154+
if flags.Changed("max-parallel-requests") && maxParallelRequests <= 0 {
155+
return errors.New("max-parallel-requests must be greater than zero") //nolint:err113
156+
}
157+
158+
if flags.Changed("max-rps") && maxRPS <= 0 {
159+
return errors.New("max-rps must be greater than zero") //nolint:err113
160+
}
161+
162+
if flags.Changed("rampup-period-in-sec") && rampUpPeriodInSec <= 0 {
163+
return errors.New("rampup-period-in-sec must be greater than zero") //nolint:err113
164+
}
165+
166+
return nil
167+
}
168+
169+
func applyWriteDefaults(flags *flag.FlagSet, maxTuplesPerWrite, maxParallelRequests, maxRPS, rampUpPeriodInSec int) (int, int, int, int) {
170+
if maxRPS > 0 && !flags.Changed("rampup-period-in-sec") {
171+
rampUpPeriodInSec = maxRPS * tuple.RPSToRampupPeriodMultiplier
172+
}
173+
174+
if maxRPS > 0 && !flags.Changed("max-parallel-requests") {
175+
defaultParallel := maxRPS / tuple.RPSToParallelRequestsDivisor
176+
177+
if defaultParallel < 1 {
178+
defaultParallel = 1
179+
}
180+
181+
maxParallelRequests = defaultParallel
182+
}
183+
184+
if maxRPS > 0 && !flags.Changed("max-tuples-per-write") {
185+
maxTuplesPerWrite = tuple.DefaultMaxTuplesPerWriteWithRPS
186+
}
187+
188+
return maxTuplesPerWrite, maxParallelRequests, maxRPS, rampUpPeriodInSec
189+
}
190+
125191
func writeTuplesFromFile(ctx context.Context, flags *flag.FlagSet, fgaClient *client.OpenFgaClient) error { //nolint:cyclop
126192
startTime := time.Now()
127193

@@ -154,6 +220,14 @@ func writeTuplesFromFile(ctx context.Context, flags *flag.FlagSet, fgaClient *cl
154220
return fmt.Errorf("failed to parse parallel requests due to %w", err)
155221
}
156222

223+
if err := validateWriteFlags(flags, maxTuplesPerWrite, maxParallelRequests, maxRPS, rampUpPeriodInSec); err != nil {
224+
return err
225+
}
226+
227+
maxTuplesPerWrite, maxParallelRequests, maxRPS, rampUpPeriodInSec = applyWriteDefaults(
228+
flags, maxTuplesPerWrite, maxParallelRequests, maxRPS, rampUpPeriodInSec,
229+
)
230+
157231
debug, err := flags.GetBool("debug")
158232
if err != nil {
159233
return fmt.Errorf("failed to parse debug flag due to %w", err)
@@ -209,10 +283,6 @@ func init() {
209283

210284
writeCmd.Flags().Int("max-rps", 0, "The maximum requests per second.")
211285
writeCmd.Flags().Int("rampup-period-in-sec", 0, "The period over which to ramp up the request rate.")
212-
writeCmd.MarkFlagsRequiredTogether(
213-
"max-rps",
214-
"rampup-period-in-sec",
215-
)
216286

217287
writeCmd.Flags().BoolVar(&hideImportedTuples, "hide-imported-tuples", false, "Hide successfully imported tuples from output")
218288
}

internal/tuple/import.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ const (
2626

2727
// DefaultMinRPS Default minimum requests per second.
2828
DefaultMinRPS = 1
29+
30+
// DefaultMaxTuplesPerWriteWithRPS is the tuples per write when --max-rps is set but --max-tuples-per-write is omitted.
31+
DefaultMaxTuplesPerWriteWithRPS = 40
32+
33+
// RPSToParallelRequestsDivisor defines how max-rps translates to max parallel requests.
34+
RPSToParallelRequestsDivisor = 5
35+
36+
// RPSToRampupPeriodMultiplier defines how max-rps translates to ramp-up period.
37+
RPSToRampupPeriodMultiplier = 2
2938
)
3039

3140
type failedWriteResponse struct {

0 commit comments

Comments
 (0)