Announcement

v0.21.0: Dials for tuning Steampipe

Memory management, rate limiting, and diagnostics to keep your Steampipe engine running smoothly at scale.

Steampipe Team
5 min. read - Oct 02, 2023

Do you run Steampipe at scale, and/or in constrained environments? Now you can control the amount of memory Steampipe and its plugins can use. Do your queries sometimes trigger excessive throttling? Now you can define plugin-level limiters for API-call concurrency and request rates. Do you wonder which permissions are needed for a query? The new diagnostic mode will show you, and it'll help you fine-tune your limiters too. Let's see how it all works.

Out of memory? Dial it down.

Steampipe can run anywhere: your laptop, an EC2 instance, a GitHub runner. In some environments, memory is a hard constraint. In Kubernetes, for example, the OOM killer can suddenly and silently shut things down. Good news! Steampipe and its plugins now default to a 1024MB limit. You override any plugin's max memory in its config file.

plugin "aws" {
memory_max_mb = 2048 # megabytes
}

You can also use the new environment variables STEAMPIPE_MEMORY_MAX_MB to set memory limits for the Steampipe process and STEAMPIPE_PLUGIN_MEMORY_MAX_MB to do the same for plugins.

Overwhelming your network? Dial it down.

Steampipe is massively parallel and we've seen cases where it can take down a home network or reverse proxy. You can now tame that behavior with rate limiters. Here's a simple example that constrains the number of List, Get, and Hydrate functions that the AWS plugin can run in parallel.

plugin "aws" {
limiter "max_concurrency" {
max_concurrency = 200
}
}

See many more examples in the documentation.

Getting throttled? Dial it down.

Steampipe plugins typically use exponential backoff/retry when API calls trigger throttling. Some amount of throttling is normal, but too much can cause problems. Now you can use limiters to keep things calm. Pen testers can fly under the radar, and everyone can avoid being a noisy cloud neighbor.

plugin "aws" {
limiter "aws_global" {
bucket_size = 1000
fill_rate = 1000
}
}

Those settings will regulate the entire AWS plugin. bucket_size defines how many requests can be made per second, and fill_rate defines how many are added back per-second to refill the bucket. The two settings work together to implement a token-bucket rate limiting algorithm.

What are the optimal values for these settings? It's hard to know in advance what's best for a given API, so you'll need to experiment. Happily, the new diagnostic mode will help you do that!

Diagnostic mode

Use the new STEAMPIPE_DIAGNOSTIC_LEVEL environment variable to turn on Steampipe's version of EXPLAIN ANALYZE. Let's dial down the above settings from 1000 to 10 and run Steampipe in diagnostic mode.

$ STEAMPIPE_DIAGNOSTIC_LEVEL=ALL steampipe query
> select jsonb_pretty(_ctx) as _ctx, display_names, tags from aws_sns_topic

Here's a single row of output.

+------------------------------------------------------------+--------------+--------+
| _ctx | display_name | tags |
+------------------------------------------------------------+--------------+--------+
| { | | <null> |
| "diagnostics": { | | |
| "calls": [ | | |
| { | | |
| "type": "list", | | |
| "scope_values": { | | |
| "table": "aws_sns_topic", | | |
| "action": "ListTopics", | | |
| "region": "us-east-1", | | |
| "service": "sns", | | |
| "connection": "sil", | | |
| "function_name": "listAwsSnsTopics" | | |
| }, | | |
| "function_name": "listAwsSnsTopics", | | |
| "rate_limiters": [ | | |
| "aws_global" | | |
| ], | | |
| "rate_limiter_delay_ms": 0 | | |
| }, | | |
| { | | |
| "type": "hydrate", | | |
| "scope_values": { | | |
| "table": "aws_sns_topic", | | |
| "action": "GetTopicAttributes", | | |
| "region": "us-east-1", | | |
| "service": "sns", | | |
| "connection": "sil", | | |
| "function_name": "" | | |
| }, | | |
| "function_name": "getTopicAttributes", | | |
| "rate_limiters": [ | | |
| "aws_global" | | |
| ], | | |
| "rate_limiter_delay_ms": 5295 | | |
| }, | | |
| { | | |
| "type": "hydrate", | | |
| "scope_values": { | | |
| "table": "aws_sns_topic", | | |
| "action": "ListTagsForResource", | | |
| "region": "us-east-1", | | |
| "service": "sns", | | |
| "connection": "sil", | | |
| "function_name": "listTagsForSnsTopic" | | |
| }, | | |
| "function_name": "listTagsForSnsTopic", | | |
| "rate_limiters": [ | | |
| "aws_global" | | |
| ], | | |
| "rate_limiter_delay_ms": 5401 | | |
| } | | |
| ] | | |
| }, | | |
| "connection_name": "sil" | | |
| } | | |

The diagnostics information includes information about each Get, List, and Hydrate function that was called to fetch the row, including:

KeyDescription
typeThe type of function (list, get, or hydrate).
function_nameThe name of the function.
scope_valuesA map of scope names to values. This includes the built-in scopes as well as any matrix qualifier scopes and function tags.
rate_limitersA list of the rate limiters that are scoped to the function.
rate_limiter_delay_msThe amount of time (in milliseconds) that Steampipe waited before calling this function due to client-side (limiter) rate limiting.

The _ctx column shows that the aws_global limiter is in effect, and that it has delayed two of the sub-API calls that feed the aws_sns_topic table.

Whether or not you've imposed limits, the function_name scope value is a key piece of information. Have you ever struggled to know which permissions are needed to query a particular table? The names of the functions that populate the table correspond to API calls; they'll help you figure that out.

What if you don't want to slow everything down? You can be much more precise. For example, the limits imposed by AWS on APIs can vary by region. To rate-limit just us-east-1 traffic you can do this.

plugin "aws" {
limiter "us-east-1" {
bucket_size = 10
fill_rate = 10
scope = [ "region" ]
where = "'region = 'us-east-1'"
}
}

A limiter defined with no scopes applies broadly. When you list one or more values in the scope argument you create a unique limiter for that set of scopes. With the AWS plugin, for example, you could create a limiter scoped to just connection, or connection and region, or any combination of available scopes — it's wildly flexible.

And if the plugin implements function tags, as the AWS plugin does, you can even target a specific function call.

plugin "aws" {
limiter "aws-sns-topic-us-east-1-" {
bucket_size = 10
fill_rate = 10
scope = [ "region" ]
where = "'region = 'us-east-1' and action = 'ListTagsForResource'"
}
}

The plugin block

All these examples use the new plugin block to set plugin-level options (in .spc files) that you can bind to connections. Steampipe will create a separate plugin process for each plugin/connection pair.

In this example Steampipe creates two plugin processes. aws_high binds to aws_prod_1 and aws_prod_2, sets a 2000MB maximum, and defines no limiters. aws_low binds to aws_dev_1 and aws_dev_2, sets a 500MB limit, and defines an all_requests limiter.

plugin "aws_high" {
memory_max_mb = 2000
source = "aws"
}
plugin "aws_low" {
memory_max_mb = 500
source = "aws"
limiter "all_requests" {
bucket_size = 100
fill_rate = 100
max_concurrency = 50
}
}
connection "aws_prod_1" {
plugin = plugin.aws_high
profile = "prod1"
regions = ["*"]
}
connection "aws_prod_2" {
plugin = plugin.aws_high
profile = "prod2"
regions = ["*"]
}
connection "aws_dev_1" {
plugin = plugin.aws_low
profile = "dev1"
regions = ["*"]
}
connection "aws_dev_2" {
plugin = plugin.aws_low
profile = "dev2"
regions = ["*"]
}

See it in action

Tune your Steampipe engine

New memory limits can regulate Steampipe's memory consumption overall, or per-plugin. Concurrency and rate limiters can control how plugins use APIs. And diagnostic mode helps you understand and fine-tune the diverse and flexible limiters you can create. Steampipe is a racecar that sometimes goes too fast. With these new mechanisms of control, and unprecedented visibility into the engine, you can now precisely calibrate its speed and optimize its performance. Read all about the new plugin block and limiters, then give it all a try and let us know how it goes!