Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for rate limiting. Closes #623 #618

Merged
merged 75 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
11ef322
Add MultiRateLimiter
kaidaguerre Jul 6, 2023
1f22e34
v5.6.0-dev.5
kaidaguerre Jul 6, 2023
eadd918
read rate limit from env
kaidaguerre Jul 7, 2023
5b587b1
limit row concurrency
kaidaguerre Jul 10, 2023
03487bf
redesign v1 - removed multi limiter, added LimiterMap
kaidaguerre Jul 10, 2023
f3a7f14
tidy rate limiting
kaidaguerre Jul 12, 2023
981bd82
tidy, sort logging, enable rate limiting
kaidaguerre Jul 12, 2023
6ecf6ab
remove max concurrent hydrate calls semaphore (for now)
kaidaguerre Jul 12, 2023
c6fdfc6
Ensure last page of data is read
kaidaguerre Jul 12, 2023
6f957cf
fix fetch call rate limiters
kaidaguerre Jul 14, 2023
64159ac
rename tag values to ScopeValues ands split into static and column va…
kaidaguerre Jul 17, 2023
a62249c
Add TableRateLimiterConfig, tidy language
kaidaguerre Jul 18, 2023
053bd7d
add rate limiters doc
kaidaguerre Jul 18, 2023
511d376
update doc
kaidaguerre Jul 18, 2023
e5d0b6d
Simplify
kaidaguerre Jul 19, 2023
ec556e9
Initial filter implementation
kaidaguerre Jul 20, 2023
00c3fbd
ScopeFilter working
kaidaguerre Jul 20, 2023
9306e53
Update scope filter and tests
kaidaguerre Jul 25, 2023
c3974ff
Add support for config override/defintion of rate limiters
kaidaguerre Jul 26, 2023
c221c8b
Add filter
kaidaguerre Jul 26, 2023
fddea0e
v5.6.0-dev.6
kaidaguerre Jul 26, 2023
373b4f7
deps
kaidaguerre Jul 26, 2023
60d9b9c
v5.6.0-dev.7
kaidaguerre Jul 26, 2023
b92f4a7
Only populate scope values for matrix quals
kaidaguerre Jul 26, 2023
3d14b72
logging
kaidaguerre Jul 26, 2023
b20e068
Populate rate limit metadata in ctx column
kaidaguerre Jul 27, 2023
bb8aa87
Update docs
kaidaguerre Jul 27, 2023
91da02f
v5.6.0-dev.8
kaidaguerre Jul 27, 2023
148140a
remove cost
kaidaguerre Jul 28, 2023
297b61a
Restructure _ctx diagnostics
kaidaguerre Jul 28, 2023
60da88a
v5.6.0-dev.9
kaidaguerre Jul 28, 2023
c15c12d
nil check for row
kaidaguerre Jul 29, 2023
da33a30
revert retry.Do from startAllHydrateCalls (for now)
kaidaguerre Jul 29, 2023
a26a2ae
v5.6.0-dev.11
kaidaguerre Jul 29, 2023
f0e13ea
Rename ScopeValues to Tags and Scopes to Scope
kaidaguerre Aug 7, 2023
a9efcb9
v5.6.0-dev.12
kaidaguerre Aug 7, 2023
636db13
deps
kaidaguerre Aug 7, 2023
bf04932
Add recover for setRateLimiters and setCacheOptions
kaidaguerre Aug 7, 2023
66b1927
Allow hydrate config for Get and List functions - just validate there…
kaidaguerre Aug 7, 2023
8967052
v5.6.0-dev.14
kaidaguerre Aug 7, 2023
9a6e594
Add semaphore to MultiLimiter
kaidaguerre Aug 7, 2023
363832b
Each limiter now has a semaphore, multilimiter acquires them all
kaidaguerre Aug 8, 2023
aa579c2
deps
kaidaguerre Aug 8, 2023
db647b4
v5.6.0-dev.15
kaidaguerre Aug 8, 2023
2ab7f13
Fix compile error
kaidaguerre Aug 8, 2023
91e9a7c
v5.6.0-dev.16
kaidaguerre Aug 8, 2023
9011203
Fix matrix scope values for rate limiters
kaidaguerre Aug 8, 2023
5a52b35
rename ShallowCopy
kaidaguerre Aug 9, 2023
de57a74
comments
kaidaguerre Aug 9, 2023
629f162
renames
kaidaguerre Aug 9, 2023
1fa9e82
dedupe rate limiters
kaidaguerre Aug 9, 2023
a6a537f
v5.6.0-dev.17
kaidaguerre Aug 9, 2023
55a185b
Add logging
kaidaguerre Aug 9, 2023
58c6946
v5.6.0-dev.18
kaidaguerre Aug 9, 2023
7fbf12f
improve logging
kaidaguerre Aug 9, 2023
cdcc6c7
v5.6.0-dev.19
kaidaguerre Aug 9, 2023
3798938
Include limiter name in map key
kaidaguerre Aug 10, 2023
897c2d6
v5.6.0-dev.20
kaidaguerre Aug 10, 2023
75b8014
Fix unit tests
kaidaguerre Aug 10, 2023
9d277d9
v5.6.0-rc.21
kaidaguerre Aug 11, 2023
a1793a1
rename StartupPanicMessage to PluginStartupFailureMessage
kaidaguerre Aug 12, 2023
3d1c92a
Fix shallowCopy
kaidaguerre Aug 24, 2023
069781d
v5.6.0-dev.22
kaidaguerre Aug 24, 2023
02504d2
Add GetRateLimiters
kaidaguerre Aug 25, 2023
b2d918d
Include concurrency delay in ctx delay field
kaidaguerre Aug 25, 2023
ea230f8
Fix rate limiters with only max concurrency defined
kaidaguerre Aug 28, 2023
b63a4fa
Update limiter validation to allow only concurrency to be set
kaidaguerre Aug 29, 2023
203e19c
update go version in workflow
kaidaguerre Sep 1, 2023
ebc583c
v5.6.0-dev.23
kaidaguerre Sep 1, 2023
9cbeab4
remove max memory setting
kaidaguerre Sep 1, 2023
ac94cd0
Add QueryData.WaitForListRateLimit
kaidaguerre Sep 1, 2023
e7e934e
v5.6.0-rc.24
kaidaguerre Sep 1, 2023
ae4ee08
Add validation for rate limiter names
kaidaguerre Sep 4, 2023
39be9d5
Update pluginClient.go
kaidaguerre Sep 6, 2023
7f6e559
Update table.go
kaidaguerre Sep 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/acceptance-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.19
go-version: 1.21

- name: golangci-lint
uses: golangci/golangci-lint-action@v3
Expand All @@ -40,7 +40,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.19
go-version: 1.21

- name: Checkout
uses: actions/checkout@v3
Expand Down
256 changes: 256 additions & 0 deletions design/rate_limiters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@

# Rate Limiting

## Overview

Rate limiting can be applied to all `Get`, `List` and column `Hydrate` calls.

For each call, multiple rate limiters may apply. When more than one limiter applies to a call,
the rate limiter with the largest required wait is respected.

The rate limiters which apply to a call are resolved using `scopes`. Each rate limiter definition specifies
scopes which apply to it, for example `region`, `connection`. Then for each call, the values of these scopes are
determined and used to identify which limiters apply.


## Defining Rate Limiters

Rate limiters may be defined in the plugin definition (by the plugin author), or in HCL config (by the user)

### Plugin Definition
A rate limiters is defined using the `Definition` struct:
```go
type Definition struct {
// the limiter name
Name string
// the actual limiter config
FillRate rate.Limit
BucketSize int

// the scopes which identify this limiter instance
// one limiter instance will be created for each combination of scopes which is encountered
Scopes []string

// filter used to target the limiter
Where string
}
```
`Scopes` is list of all the scopes which apply to the rate limiter.
For example, if you want a rate limiter that applies to a single account, region and service, you could use the scopes:
[`connection`, `region`,`service`].
(See below for details of predefined vs custom scope names)

`Where` is a SQL compatible where clause which allows a rate limiter to be targeted to spcific set of scope values,
for example to specify a rate limiter for a specific service only, the filter `"service"="s3` be used.

For example:
```go
p := &plugin.Plugin{
Name: "aws",
TableMap: map[string]*plugin.Table{...}
RateLimiters: []*rate_limiter.Definition{
Name: "connection-region-service",
BucketSize: 10,
FillRate: 50,
Scopes: []string{"region", "connection", "servive"},
Where: "service = 's3'",
},
},
```

### HCL Definition
Rate limiters may be define in HCL in an `.spc` file in the config folder.
If a limiter has the same name as one defined in the plugin it will override it, if not, a new limiter is defined.

```
limiter "connection-region-service" {
plugin = "aws"
bucket_size = 5
fill_rate = 25
scope = ["region", "connection", "servive"]
where = "service = 's3'"
}

```

## Resolving Rate Limiters

When executing a hydrate call the following steps are followed:
1) Build the set of rate limiter definitions which may apply to the hydrate call
2) Build the set of scope values which apply to the hydrate call
3) Determine which limiter defintions are satisfied by the scope values (looking at both required scopes and the scope filters)
4) Build a MultiLimiter from the resultant limiter defintions

### Resolving Scope Values
Scope values are popuylated from 3 sources:
- *implicit* scope values populated automatically
- `tabe`, `connection`
- *matrix* scope values populated from matrix quals (e.g. `region`)
- *custom* scope values (tags?) which may be defined in `Table` defintions, `HydrateConfig`, `GetConfig` and `ListConfig`

## Paged List Calls

If the list call uses paging, the SDK provides a hook, `WaitForListRateLimit`, which can be called before paging to apply rate limiting to the list call:

```go
// List call
for paginator.HasMorePages() {

// apply rate limiting
d.WaitForListRateLimit(ctx)

output, err := paginator.NextPage(ctx)
if err != nil {
plugin.Logger(ctx).Error("aws_codepipeline_pipeline.listCodepipelinePipelines", "api_error", err)
return nil, err
}
for _, items := range output.Pipelines {
d.StreamListItem(ctx, items)

// Context can be cancelled due to manual cancellation or the limit has been hit
if d.RowsRemaining(ctx) == 0 {
return nil, nil
}
}
}
```

## Scenarios

### 1. Plugin defines a single unscoped rate limiter

```go
func Plugin(_ context.Context) *plugin.Plugin {
p := &plugin.Plugin{
Name: "aws",
TableMap: map[string]*plugin.Table{...},
RateLimiters: []*rate_limiter.Definition{
{
Limit: 50,
BurstSize: 10,
},
},
...
}

return p
}
```

### 2. Plugin defines a rate limiter scoped by implicit scope "connection", custom scope "service" and matrix scope "region"

#### Plugin definition
```go

func Plugin(_ context.Context) *plugin.Plugin {
p := &plugin.Plugin{
Name: pluginName,
TableMap: map[string]*plugin.Table{...},
RateLimiters:[]*rate_limiter.Definition{
{
Limit: 50,
BurstSize: 10,
Scopes: []string{
"connection",
"service"
"region",
},
},
},
...
}

return p
}
```
NOTE: `region` must be defined as a matrix qual in order to use the matrix scope value,
and `service` must be defined as a custom scope value for tables or hydrate calls which this limiter targets.

#### 2a. Table definition which defines a "region" key column and sets the "service" scope value for all hydrate calls

```go
func tableAwsS3AccessPoint(_ context.Context) *plugin.Table {
return &plugin.Table{
Name: "aws_s3_access_point",
List: &plugin.ListConfig{
Hydrate: listS3AccessPoints,
KeyColumns: plugin.SingleColumn("region"),
},
Get: &plugin.GetConfig{
KeyColumns: plugin.AllColumns([]string{"name", "region"}),
Hydrate: getS3AccessPoint,
},
// set "service" scope to "s3" for all hydrate calls
Tags: map[string]string{
"service": "s3",
},
Columns: awsRegionalColumns([]*plugin.Column{...}),
}
}

```
#### 2b. Hydrate call definition which specifies the "service" scope value


```go
func tableAwsS3AccountSettings(_ context.Context) *plugin.Table {
return &plugin.Table{
Name: "aws_s3_account_settings",
List: &plugin.ListConfig{...},
HydrateConfig: []plugin.HydrateConfig{
{
Func: getAccountBucketPublicAccessBlock,
// set the "service" scope value for this hydrate call
Tags: map[string]string{
"service": "s3",
},
},
},
Columns: awsGlobalRegionColumns([]*plugin.Column{...}),
}
}

```


### 3. Plugin defines rate limiters for "s3" and "ec2" services and one for all other services
NOTE: also scoped by "connection" and "region"

```go

// scopes used for all rate limiters
var rateLimiterScopes=[]string{"connection","service","region",}

func Plugin(_ context.Context) *plugin.Plugin {
p := &plugin.Plugin{
Name: pluginName,
TableMap: map[string]*plugin.Table{ ... },
RateLimiters: []*rate_limiter.Definition{
// rate limiter for s3 service
{
Limit: 20,
BurstSize: 5,
Scopes: rateLimiterScopes,
Where: "service='s3'",
},
},
// rate limiter for ec2 service
{
Limit: 40,
BurstSize: 5,
Scopes: rateLimiterScopes,
Where: "service='ec2'",
},
// rate limiter for all other services
{
Limit: 75,
BurstSize: 10,
Where: "service not in ('s3,'ec2')",
},
},
...
}

return p
}
```
Loading
Loading