feat (docs/observability): added docs and observability dashboard for "advanced graphql" (#2653)

Co-authored-by: Nuno Pato <nunopato@gmail.com>
This commit is contained in:
David Barroso
2024-04-11 10:22:54 +02:00
committed by GitHub
parent b2be3642aa
commit 87ae23ba05
6 changed files with 833 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
---
'@nhost/docs': minor
---
feat: added "advanced graphql" documentation

View File

@@ -0,0 +1,93 @@
---
title: "Advanced Features"
description: "Better visibility and security for your GraphQL API"
icon: arrow-progress
---
In this document you can find information about the advanced GraphQL features available in some of our plans (see our [pricing page](https://nhost.io/pricing) for details).
<Note>
If you are interested in other advanced security features or observability capabilities don't hesitate to let us know.
</Note>
## Observability
In addition to resource utilization, as part of this set of advanced GraphQL features, you have extra visibility at the query level:
- CPU and Memory resources consumed by each replica (`hasura-graphi` is the process responsible for these features)
- Rate of requests
- Failure rate
- Requests duration
All of the added GraphQL metrics are broken down per:
- Operation (query/mutation)
- Name of the GraphQL request
- Types included in the request
For instance, below you can see a screenshot from our own staging application:
![observability](/images/guides/graphql/observability.png)
## Security Features
In addition, a few security features are made available.
### Forbid admin secret
This feature will block requests to any endpoint with the header `x-hasura-admin-secret`. To enable it:
```toml
[graphql.security]
forbidAminSecret = true
```
<Warning>
Enabling this can have the side effect of breaking deployments. If you plan to enable this feature we recommend contacting our support first.
</Warning>
### Maximum Query Depth
Deeply nested queries can require lots of resources which, in turn, may lead to resource exhaustion. It can also make your application susceptible to DoS attacks. To prevent this, you can configure the maximum query depth you want to allow. For instance, you can limit the depth of your queries to 4 with:
```
[graphql.security]
maxDepthQueries = 4
```
With the configuration above, the following query will be rejected:
```graphql
query SomeNestedQuery {
a {
b {
c {
d {
e
}
}
}
}
}
```
while:
```graphql
query NotSoNestedQuery {
a {
b {
c
}
}
}
```
will work just fine.
## Logs
In addition to the logs you already have a new service will show in your "Logs" tab; `hasura-graphi`. Here you can see security events in the case you have enabled any of the security features mentioned above.
![logs](/images/guides/graphql/logs.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 KiB

View File

@@ -139,7 +139,11 @@
},
{
"group": "API / Hasura",
"pages": ["guides/api/configuring-hasura", "guides/api/permissions"]
"pages": [
"guides/api/configuring-hasura",
"guides/api/permissions",
"guides/api/advanced_features"
]
},
{
"group": "Storage",

View File

@@ -0,0 +1,730 @@
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "Prometheus",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__elements": {},
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "9.2.0"
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "text",
"name": "Text",
"version": ""
},
{
"type": "panel",
"id": "timeseries",
"name": "Time series",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"gridPos": {
"h": 2,
"w": 24,
"x": 0,
"y": 0
},
"id": 14,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"content": "This dashboard is part of our \"Advanced GraphQL\" product. You can refer to our [documentation](https://docs.nhost.io/guides/api/advanced_features) for further details.",
"mode": "markdown"
},
"pluginVersion": "9.2.0",
"type": "text"
},
{
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 2
},
"id": 9,
"title": "Resource Utilization",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 3
},
"id": 11,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum by(container,pod) (irate(container_cpu_usage_seconds_total{container=~\"hasura|hasura-graphi\"}[$__rate_interval])) * 1000",
"interval": "2m",
"legendFormat": "{{pod}}::{{container}}",
"range": true,
"refId": "A"
}
],
"title": "CPU Utilization per Replica",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "bytes"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 3
},
"id": 12,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum by(container,pod) (container_memory_usage_bytes{container=~\"hasura|hasura-graphi\"})",
"interval": "2m",
"legendFormat": "{{pod}}::{{container}}",
"range": true,
"refId": "A"
}
],
"title": "Memory Utilization Per Replica",
"type": "timeseries"
},
{
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 11
},
"id": 7,
"title": "GraphQL Metrics",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 12
},
"id": 2,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(graphql_requests_total{service=\"hasura-service\"}[$__rate_interval]))",
"interval": "2m",
"legendFormat": "total",
"range": true,
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "rate(graphql_requests_total{service=\"hasura-service\"}[$__rate_interval])",
"hide": false,
"interval": "2m",
"legendFormat": "{{operation}}::{{name}}::{{field}}",
"range": true,
"refId": "B"
}
],
"title": "Request Rate",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 12
},
"id": 3,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "graphql_websocket_connections_started_total{service=\"hasura-service\"} - graphql_websocket_connections_completed_total{service=\"hasura-service\"}",
"interval": "2m",
"legendFormat": "active",
"range": true,
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "rate(graphql_websocket_connections_started_total{service=\"hasura-service\"}[$__rate_interval])",
"hide": false,
"interval": "2m",
"legendFormat": "rate started",
"range": true,
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "rate(graphql_websocket_connections_completed_total{service=\"hasura-service\"}[$__rate_interval])",
"hide": false,
"legendFormat": "rate completed",
"range": true,
"refId": "C"
}
],
"title": "Subscriptions",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "ms"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 20
},
"id": 4,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.95, sum(rate(graphql_requests_duration_miliseconds_bucket{service=\"hasura-service\"}[$__rate_interval])) by (le,operation,name,field))",
"interval": "2m",
"legendFormat": "{{operation}}::{{ name }}::{{field}}",
"range": true,
"refId": "A"
}
],
"title": "Duration",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 20
},
"id": 5,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "rate(graphql_requests_total{service=\"hasura-service\", result=\"failure\"}[$__rate_interval])",
"interval": "2m",
"legendFormat": "{{operation}}::{{ name }}::{{field}}",
"range": true,
"refId": "A"
}
],
"title": "Failure Rate",
"type": "timeseries"
}
],
"schemaVersion": 37,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "GraphQL",
"uid": "LQWllVaSk",
"version": 5,
"weekStart": ""
}