-
Notifications
You must be signed in to change notification settings - Fork 171
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
proto: Problem differentiating not-set value from default value #183
Comments
Yeah, that's definitely a problem in general. For the exemplar, only the rare case of an exemplar with a value of 0 and without any labels and timestamp (or a default timestamp, see below) would be indistinguishable from a non-existing exemplar, but it's still a corner case that might strike eventually. IIUC, the Using the new I don't think going back to proto2 is an acceptable option at this time. |
Hey thanks for opening this to discuss. This is actually not an issue for Timestamp or Exemplars since they are of message type, and it's very easy to detect if the field was set on the wire. This is outlined in the documentation you linked for proto3 field presence: https://github.com/protocolbuffers/protobuf/blob/v3.12.0/docs/field_presence.md#presence-in-proto3-apis. Here's an example of this, you can see very clearly if those fields are set or not: package main
import (
"fmt"
"github.com/golang/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)
func main() {
data, err := proto.Marshal(&CounterValue{})
if err != nil {
panic(err)
}
var msg CounterValue
if err := proto.Unmarshal(data, &msg); err != nil {
panic(err)
}
fmt.Printf("msg: %v\n", msg.String())
fmt.Printf("value.Created: %v\n", msg.Created)
fmt.Printf("value.Exemplar: %v\n", msg.Exemplar)
data, err = proto.Marshal(&CounterValue{
Created: ×tamppb.Timestamp{Seconds: 123},
})
if err != nil {
panic(err)
}
if err := proto.Unmarshal(data, &msg); err != nil {
panic(err)
}
fmt.Printf("msg: %v\n", msg.String())
fmt.Printf("value.Created: %v\n", msg.Created)
fmt.Printf("value.Exemplar: %v\n", msg.Exemplar)
data, err = proto.Marshal(&CounterValue{
Created: ×tamppb.Timestamp{Seconds: 123},
Exemplar: &Exemplar{Value: 42},
})
if err != nil {
panic(err)
}
if err := proto.Unmarshal(data, &msg); err != nil {
panic(err)
}
fmt.Printf("msg: %v\n", msg.String())
fmt.Printf("value.Created: %v\n", msg.Created)
fmt.Printf("value.Exemplar: %v\n", msg.Exemplar)
} Output: $ go run bin/proto/*.go
msg:
value.Created: <nil>
value.Exemplar: <nil>
msg: created:{seconds:123}
value.Created: seconds:123
value.Exemplar: <nil>
msg: created:{seconds:123} exemplar:{value:42}
value.Created: seconds:123
value.Exemplar: value:42 |
Good to learn about the singular message case. That will indeed defuse many cases. I guess we still need to go through all singular numeric, enum, and string fields to see if there is any issue with not-set vs. default. I assume that we never have the necessity to distinguish between a not-set repeated field and a repeated field with zero repetitions (but if anyone has contrary evidence, please speak up). We don't have maps or bytes in the current proto file. |
Problem
The OpenMetrics Protobuf specification is using the
proto3
syntax.With the
proto3
syntax all fields are optional by default. This is not to be confused with null-able. While one can choose not to specify a field it is then represented by its default value. Someone receiving aproto3
encoded payload has no way to tell the difference between a field explicitly set to its default value or not set at all.The most prominent case where this can be problematic is for boolean fields where both the default value and the not-set value is
false
. In the context of OpenMetrics, as far as I can tell, one problematic example would be theexamplar
field in theCounterValue
message. A use can not tell the difference between a not-set examplar and an examplar set to0
.https://github.com/OpenObservability/OpenMetrics/blob/1ff04f252cec929acd9a569fce87b8ec4f72b086/proto/openmetrics_data_model.proto#L109-L123
Solution
To solve the above I see 3 solutions:
optional
keyword, which would allow differentiating between a default value and a not-set value, was initially removed with theproto3
syntax, but has been revived with v3.12.0. In case requiring protocv3.12.0
is fine, one could just make use of the revivedoptional
keyword.optional
keyword is just syntactic sugar foroneof
. Thus, in case requiring protocv3.12.0
is not an option, one can useoneof
directly. See this stackoverflow answer.proto2
instead ofproto3
(see original Prometheus proto definition).Further reading
optional
inproto3
.optional
toproto3
.What do people think? Do you see alternative approaches?
Given that I don't have much expertise with Protobuf I might be missing something. If so, I would appreciate an answer pointing out my mistake.
The text was updated successfully, but these errors were encountered: