By default, aggregation collapses all labels into one result — a single number.
Usually that's not what you want. You need to preserve some dimensions to keep the result meaningful.
Imagine rate(http_requests_total{job="demo"}[5m]) returns these series:
instance
method
path
status
rate
localhost:8080
GET
/api
200
50
localhost:8080
POST
/api
201
30
localhost:8081
GET
/api
200
45
localhost:8081
GET
/health
200
10
A plain sum(...) would collapse everything into 135. Not very useful.
by() — keep ONLY these labels
by() tells Prometheus: "Group by these labels, throw away everything else."
sum by(instance) (rate(http_requests_total{job="demo"}[5m]))
Result — one value per instance:
instance
rate
localhost:8080
80 (50+30)
localhost:8081
55 (45+10)
All other labels (method, path, status) are discarded. Think of it like SQL's GROUP BY:
SELECT instance, SUM(rate) GROUP BY instance.
without() — remove ONLY these labels
without() is the opposite: "Keep all labels EXCEPT these."
sum without(method, status) (rate(http_requests_total{job="demo"}[5m]))
Result — removes method and status, keeps instance and path:
instance
path
rate
localhost:8080
/api
80 (50+30)
localhost:8081
/api
45
localhost:8081
/health
10
When to use which?
by() — when you care about a few specific labels and want to discard the rest. Example: "give me totals per instance".
without() — when you want to remove a few noisy labels but keep everything else. Example: "I don't care about method or status, keep the rest".
Both produce the same grouping when they specify complementary label sets. Choose whichever is more concise and readable for your use case.