Send raw history events from history to frontend service (#7342)

## What changed?
Change to send raw history blobs from history service to frontend
service. History service returns a new proto message that has a repeated
bytes history field.
This response is wire compatible with the original response which has
temporal.api.history.v1.History type for this field. This allows history
service to not deserialize events from this data blob. This considerably
reduces CPU usage.

History service still needs event_id and version decoded from history
events. For this we use a new proto message StrippedHistoryEvent which
has these two fields only. It takes considerably less CPU to decode
events to this struct.

## Why?
We have seen incidents of high history CPU usage when large number of
GetWorkflowExecutionHistory calls are made to workflows which has large
history. With this change we can reduce the CPU burden on history
service during this API call.

## How did you test it?
Existing unit and functional tests.

## Potential risks

## Documentation

## Is hotfix candidate?
This commit is contained in:
Prathyush PV
2025-02-21 14:21:25 -08:00
committed by GitHub
parent 1ea38fe31d
commit cce06c6b11
30 changed files with 3635 additions and 2640 deletions

View File

@@ -250,3 +250,77 @@ func (this *TaskRange) Equal(that interface{}) bool {
return proto.Equal(this, that1)
}
// Marshal an object of type StrippedHistoryEvent to the protobuf v3 wire format
func (val *StrippedHistoryEvent) Marshal() ([]byte, error) {
return proto.Marshal(val)
}
// Unmarshal an object of type StrippedHistoryEvent from the protobuf v3 wire format
func (val *StrippedHistoryEvent) Unmarshal(buf []byte) error {
return proto.Unmarshal(buf, val)
}
// Size returns the size of the object, in bytes, once serialized
func (val *StrippedHistoryEvent) Size() int {
return proto.Size(val)
}
// Equal returns whether two StrippedHistoryEvent values are equivalent by recursively
// comparing the message's fields.
// For more information see the documentation for
// https://pkg.go.dev/google.golang.org/protobuf/proto#Equal
func (this *StrippedHistoryEvent) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
var that1 *StrippedHistoryEvent
switch t := that.(type) {
case *StrippedHistoryEvent:
that1 = t
case StrippedHistoryEvent:
that1 = &t
default:
return false
}
return proto.Equal(this, that1)
}
// Marshal an object of type StrippedHistoryEvents to the protobuf v3 wire format
func (val *StrippedHistoryEvents) Marshal() ([]byte, error) {
return proto.Marshal(val)
}
// Unmarshal an object of type StrippedHistoryEvents from the protobuf v3 wire format
func (val *StrippedHistoryEvents) Unmarshal(buf []byte) error {
return proto.Unmarshal(buf, val)
}
// Size returns the size of the object, in bytes, once serialized
func (val *StrippedHistoryEvents) Size() int {
return proto.Size(val)
}
// Equal returns whether two StrippedHistoryEvents values are equivalent by recursively
// comparing the message's fields.
// For more information see the documentation for
// https://pkg.go.dev/google.golang.org/protobuf/proto#Equal
func (this *StrippedHistoryEvents) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
var that1 *StrippedHistoryEvents
switch t := that.(type) {
case *StrippedHistoryEvents:
that1 = t
case StrippedHistoryEvents:
that1 = &t
default:
return false
}
return proto.Equal(this, that1)
}

View File

@@ -371,71 +371,185 @@ func (x *TaskRange) GetExclusiveMaxTaskKey() *TaskKey {
return nil
}
// StrippedHistoryEvent is a stripped down version of HistoryEvent that only contains the event_id and version.
type StrippedHistoryEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
EventId int64 `protobuf:"varint,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
Version int64 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"`
}
func (x *StrippedHistoryEvent) Reset() {
*x = StrippedHistoryEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_temporal_server_api_history_v1_message_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StrippedHistoryEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StrippedHistoryEvent) ProtoMessage() {}
func (x *StrippedHistoryEvent) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_history_v1_message_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StrippedHistoryEvent.ProtoReflect.Descriptor instead.
func (*StrippedHistoryEvent) Descriptor() ([]byte, []int) {
return file_temporal_server_api_history_v1_message_proto_rawDescGZIP(), []int{6}
}
func (x *StrippedHistoryEvent) GetEventId() int64 {
if x != nil {
return x.EventId
}
return 0
}
func (x *StrippedHistoryEvent) GetVersion() int64 {
if x != nil {
return x.Version
}
return 0
}
type StrippedHistoryEvents struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Events []*StrippedHistoryEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"`
}
func (x *StrippedHistoryEvents) Reset() {
*x = StrippedHistoryEvents{}
if protoimpl.UnsafeEnabled {
mi := &file_temporal_server_api_history_v1_message_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StrippedHistoryEvents) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StrippedHistoryEvents) ProtoMessage() {}
func (x *StrippedHistoryEvents) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_history_v1_message_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StrippedHistoryEvents.ProtoReflect.Descriptor instead.
func (*StrippedHistoryEvents) Descriptor() ([]byte, []int) {
return file_temporal_server_api_history_v1_message_proto_rawDescGZIP(), []int{7}
}
func (x *StrippedHistoryEvents) GetEvents() []*StrippedHistoryEvent {
if x != nil {
return x.Events
}
return nil
}
var File_temporal_server_api_history_v1_message_proto protoreflect.FileDescriptor
var file_temporal_server_api_history_v1_message_proto_rawDesc = []byte{
0x0a, 0x2c, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x76, 0x31, 0x2f, 0x6d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1e, 0x74, 0x65, 0x6d,
0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67,
0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x25, 0x74, 0x65, 0x6d, 0x70, 0x6f,
0x72, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2f,
0x76, 0x31, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
0x79, 0x0a, 0x19, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x65, 0x6e, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66,
0x72, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x76,
0x31, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x79,
0x0a, 0x19, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x65, 0x6e, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66,
0x6c, 0x6f, 0x77, 0x54, 0x61, 0x73, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x50, 0x0a, 0x0e, 0x68, 0x69,
0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x73, 0x75, 0x66, 0x66, 0x69, 0x78, 0x18, 0x03, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x25, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f,
0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x0d, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79,
0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x42, 0x02, 0x68, 0x00, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x4a,
0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x51, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48,
0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x08, 0x65, 0x76, 0x65,
0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x76, 0x65,
0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x73, 0x75, 0x66, 0x66, 0x69, 0x78, 0x18, 0x03, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x25, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f,
0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x0d, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x53,
0x75, 0x66, 0x66, 0x69, 0x78, 0x42, 0x02, 0x68, 0x00, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04,
0x08, 0x02, 0x10, 0x03, 0x22, 0x51, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x69,
0x73, 0x74, 0x6f, 0x72, 0x79, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1d, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e,
0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e,
0x74, 0x49, 0x64, 0x42, 0x02, 0x68, 0x00, 0x12, 0x1c, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42,
0x02, 0x68, 0x00, 0x22, 0x85, 0x01, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x69,
0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0c, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x5f,
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x72, 0x61, 0x6e,
0x63, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x02, 0x68, 0x00, 0x12, 0x4c, 0x0a, 0x05, 0x69, 0x74,
0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6f,
0x72, 0x61, 0x6c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x68, 0x69,
0x73, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d,
0x73, 0x42, 0x02, 0x68, 0x00, 0x22, 0xab, 0x01, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x1d, 0x63, 0x75, 0x72, 0x72,
0x65, 0x6e, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f,
0x72, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1a,
0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x69, 0x73,
0x74, 0x6f, 0x72, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x02, 0x68, 0x00, 0x12, 0x50, 0x0a, 0x09,
0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e,
0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x09, 0x68, 0x69,
0x73, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x42, 0x02, 0x68, 0x00, 0x22, 0x63, 0x0a, 0x07, 0x54, 0x61,
0x73, 0x6b, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x42, 0x02, 0x68,
0x00, 0x12, 0x3b, 0x0a, 0x09, 0x66, 0x69, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x66,
0x69, 0x72, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x02, 0x68, 0x00, 0x22, 0xcf, 0x01, 0x0a, 0x09,
0x54, 0x61, 0x73, 0x6b, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x60, 0x0a, 0x16, 0x69, 0x6e, 0x63, 0x6c,
0x75, 0x73, 0x69, 0x76, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61,
0x6c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x68, 0x69, 0x73, 0x74,
0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4b, 0x65, 0x79, 0x52, 0x13,
0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x4d, 0x69, 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x4b,
0x65, 0x79, 0x42, 0x02, 0x68, 0x00, 0x12, 0x60, 0x0a, 0x16, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69,
0x76, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72,
0x79, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4b, 0x65, 0x79, 0x52, 0x13, 0x65, 0x78, 0x63,
0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x4d, 0x61, 0x78, 0x54, 0x61, 0x73, 0x6b, 0x4b, 0x65, 0x79, 0x42,
0x02, 0x68, 0x00, 0x22, 0x53, 0x0a, 0x14, 0x53, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x48, 0x69,
0x73, 0x74, 0x6f, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x08, 0x65, 0x76,
0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x65, 0x76, 0x65,
0x6e, 0x74, 0x49, 0x64, 0x42, 0x02, 0x68, 0x00, 0x12, 0x1c, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x42, 0x02, 0x68, 0x00, 0x22, 0x85, 0x01, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48,
0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0c, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68,
0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x72, 0x61,
0x6e, 0x63, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x02, 0x68, 0x00, 0x12, 0x4c, 0x0a, 0x05, 0x69,
0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x74, 0x65, 0x6d,
0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65,
0x6d, 0x73, 0x42, 0x02, 0x68, 0x00, 0x22, 0xab, 0x01, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x1d, 0x63, 0x75,
0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x69, 0x73,
0x74, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52,
0x1a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48,
0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x02, 0x68, 0x00, 0x12, 0x50,
0x0a, 0x09, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x2e, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31,
0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x09,
0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x42, 0x02, 0x68, 0x00, 0x22, 0x63, 0x0a, 0x07,
0x54, 0x61, 0x73, 0x6b, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x42,
0x02, 0x68, 0x00, 0x12, 0x3b, 0x0a, 0x09, 0x66, 0x69, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x52, 0x08, 0x66, 0x69, 0x72, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x02, 0x68, 0x00, 0x22, 0xcf, 0x01,
0x0a, 0x09, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x60, 0x0a, 0x16, 0x69, 0x6e,
0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6d, 0x70,
0x6f, 0x72, 0x61, 0x6c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x68,
0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4b, 0x65, 0x79,
0x52, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x4d, 0x69, 0x6e, 0x54, 0x61,
0x73, 0x6b, 0x4b, 0x65, 0x79, 0x42, 0x02, 0x68, 0x00, 0x12, 0x60, 0x0a, 0x16, 0x65, 0x78, 0x63, 0x6c,
0x75, 0x73, 0x69, 0x76, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x6b, 0x65,
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61,
0x6c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x68, 0x69, 0x73,
0x74, 0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4b, 0x65, 0x79, 0x52, 0x13,
0x65, 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x4d, 0x61, 0x78, 0x54, 0x61, 0x73, 0x6b, 0x4b,
0x65, 0x79, 0x42, 0x02, 0x68, 0x00, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x6f, 0x2e, 0x74, 0x65, 0x6d, 0x70,
0x6f, 0x72, 0x61, 0x6c, 0x2e, 0x69, 0x6f, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x76, 0x31, 0x3b, 0x68, 0x69, 0x73,
0x74, 0x6f, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x42, 0x02, 0x68, 0x00, 0x22, 0x69, 0x0a, 0x15, 0x53, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x48,
0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x06,
0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, 0x65,
0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x70,
0x70, 0x65, 0x64, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52,
0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x02, 0x68, 0x00, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x6f,
0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2e, 0x69, 0x6f, 0x2f, 0x73, 0x65, 0x72, 0x76,
0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x76, 0x31,
0x3b, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -450,7 +564,7 @@ func file_temporal_server_api_history_v1_message_proto_rawDescGZIP() []byte {
return file_temporal_server_api_history_v1_message_proto_rawDescData
}
var file_temporal_server_api_history_v1_message_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_temporal_server_api_history_v1_message_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_temporal_server_api_history_v1_message_proto_goTypes = []interface{}{
(*TransientWorkflowTaskInfo)(nil), // 0: temporal.server.api.history.v1.TransientWorkflowTaskInfo
(*VersionHistoryItem)(nil), // 1: temporal.server.api.history.v1.VersionHistoryItem
@@ -458,21 +572,24 @@ var file_temporal_server_api_history_v1_message_proto_goTypes = []interface{}{
(*VersionHistories)(nil), // 3: temporal.server.api.history.v1.VersionHistories
(*TaskKey)(nil), // 4: temporal.server.api.history.v1.TaskKey
(*TaskRange)(nil), // 5: temporal.server.api.history.v1.TaskRange
(*v1.HistoryEvent)(nil), // 6: temporal.api.history.v1.HistoryEvent
(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
(*StrippedHistoryEvent)(nil), // 6: temporal.server.api.history.v1.StrippedHistoryEvent
(*StrippedHistoryEvents)(nil), // 7: temporal.server.api.history.v1.StrippedHistoryEvents
(*v1.HistoryEvent)(nil), // 8: temporal.api.history.v1.HistoryEvent
(*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp
}
var file_temporal_server_api_history_v1_message_proto_depIdxs = []int32{
6, // 0: temporal.server.api.history.v1.TransientWorkflowTaskInfo.history_suffix:type_name -> temporal.api.history.v1.HistoryEvent
8, // 0: temporal.server.api.history.v1.TransientWorkflowTaskInfo.history_suffix:type_name -> temporal.api.history.v1.HistoryEvent
1, // 1: temporal.server.api.history.v1.VersionHistory.items:type_name -> temporal.server.api.history.v1.VersionHistoryItem
2, // 2: temporal.server.api.history.v1.VersionHistories.histories:type_name -> temporal.server.api.history.v1.VersionHistory
7, // 3: temporal.server.api.history.v1.TaskKey.fire_time:type_name -> google.protobuf.Timestamp
9, // 3: temporal.server.api.history.v1.TaskKey.fire_time:type_name -> google.protobuf.Timestamp
4, // 4: temporal.server.api.history.v1.TaskRange.inclusive_min_task_key:type_name -> temporal.server.api.history.v1.TaskKey
4, // 5: temporal.server.api.history.v1.TaskRange.exclusive_max_task_key:type_name -> temporal.server.api.history.v1.TaskKey
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
6, // 6: temporal.server.api.history.v1.StrippedHistoryEvents.events:type_name -> temporal.server.api.history.v1.StrippedHistoryEvent
7, // [7:7] is the sub-list for method output_type
7, // [7:7] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_temporal_server_api_history_v1_message_proto_init() }
@@ -553,6 +670,30 @@ func file_temporal_server_api_history_v1_message_proto_init() {
return nil
}
}
file_temporal_server_api_history_v1_message_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StrippedHistoryEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_temporal_server_api_history_v1_message_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StrippedHistoryEvents); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -560,7 +701,7 @@ func file_temporal_server_api_history_v1_message_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_temporal_server_api_history_v1_message_proto_rawDesc,
NumEnums: 0,
NumMessages: 6,
NumMessages: 8,
NumExtensions: 0,
NumServices: 0,
},

View File

@@ -4173,6 +4173,43 @@ func (this *GetWorkflowExecutionHistoryResponse) Equal(that interface{}) bool {
return proto.Equal(this, that1)
}
// Marshal an object of type GetWorkflowExecutionHistoryResponseWithRaw to the protobuf v3 wire format
func (val *GetWorkflowExecutionHistoryResponseWithRaw) Marshal() ([]byte, error) {
return proto.Marshal(val)
}
// Unmarshal an object of type GetWorkflowExecutionHistoryResponseWithRaw from the protobuf v3 wire format
func (val *GetWorkflowExecutionHistoryResponseWithRaw) Unmarshal(buf []byte) error {
return proto.Unmarshal(buf, val)
}
// Size returns the size of the object, in bytes, once serialized
func (val *GetWorkflowExecutionHistoryResponseWithRaw) Size() int {
return proto.Size(val)
}
// Equal returns whether two GetWorkflowExecutionHistoryResponseWithRaw values are equivalent by recursively
// comparing the message's fields.
// For more information see the documentation for
// https://pkg.go.dev/google.golang.org/protobuf/proto#Equal
func (this *GetWorkflowExecutionHistoryResponseWithRaw) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
var that1 *GetWorkflowExecutionHistoryResponseWithRaw
switch t := that.(type) {
case *GetWorkflowExecutionHistoryResponseWithRaw:
that1 = t
case GetWorkflowExecutionHistoryResponseWithRaw:
that1 = &t
default:
return false
}
return proto.Equal(this, that1)
}
// Marshal an object of type GetWorkflowExecutionHistoryReverseRequest to the protobuf v3 wire format
func (val *GetWorkflowExecutionHistoryReverseRequest) Marshal() ([]byte, error) {
return proto.Marshal(val)

File diff suppressed because it is too large Load Diff

View File

@@ -1254,7 +1254,7 @@ type HistoryServiceServer interface {
// aip.dev/not-precedent: This service does not follow the update method API --)
PollWorkflowExecutionUpdate(context.Context, *PollWorkflowExecutionUpdateRequest) (*PollWorkflowExecutionUpdateResponse, error)
StreamWorkflowReplicationMessages(HistoryService_StreamWorkflowReplicationMessagesServer) error
GetWorkflowExecutionHistory(context.Context, *GetWorkflowExecutionHistoryRequest) (*GetWorkflowExecutionHistoryResponse, error)
GetWorkflowExecutionHistory(context.Context, *GetWorkflowExecutionHistoryRequest) (*GetWorkflowExecutionHistoryResponseWithRaw, error)
GetWorkflowExecutionHistoryReverse(context.Context, *GetWorkflowExecutionHistoryReverseRequest) (*GetWorkflowExecutionHistoryReverseResponse, error)
GetWorkflowExecutionRawHistoryV2(context.Context, *GetWorkflowExecutionRawHistoryV2Request) (*GetWorkflowExecutionRawHistoryV2Response, error)
GetWorkflowExecutionRawHistory(context.Context, *GetWorkflowExecutionRawHistoryRequest) (*GetWorkflowExecutionRawHistoryResponse, error)
@@ -1504,7 +1504,7 @@ func (UnimplementedHistoryServiceServer) PollWorkflowExecutionUpdate(context.Con
func (UnimplementedHistoryServiceServer) StreamWorkflowReplicationMessages(HistoryService_StreamWorkflowReplicationMessagesServer) error {
return status.Errorf(codes.Unimplemented, "method StreamWorkflowReplicationMessages not implemented")
}
func (UnimplementedHistoryServiceServer) GetWorkflowExecutionHistory(context.Context, *GetWorkflowExecutionHistoryRequest) (*GetWorkflowExecutionHistoryResponse, error) {
func (UnimplementedHistoryServiceServer) GetWorkflowExecutionHistory(context.Context, *GetWorkflowExecutionHistoryRequest) (*GetWorkflowExecutionHistoryResponseWithRaw, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWorkflowExecutionHistory not implemented")
}
func (UnimplementedHistoryServiceServer) GetWorkflowExecutionHistoryReverse(context.Context, *GetWorkflowExecutionHistoryReverseRequest) (*GetWorkflowExecutionHistoryReverseResponse, error) {

View File

@@ -2382,6 +2382,11 @@ that task will be sent to DLQ.`,
0.90,
"History service health check on persistence error ratio",
)
SendRawHistoryBetweenInternalServices = NewGlobalBoolSetting(
"history.sendRawHistoryBetweenInternalServices",
false,
`SendRawHistoryBetweenInternalServices is whether to send raw history events between internal temporal services`,
)
// keys for worker

View File

@@ -824,7 +824,6 @@ func (m *executionManagerImpl) readRawHistoryBranchAndFilter(
token.LastNodeID = lastNode.NodeID
token.LastTransactionID = lastNode.TransactionID
}
return dataBlobs, transactionIDs, nodeIDs, token, dataSize, nil
}

View File

@@ -28,8 +28,13 @@ import (
"context"
"sort"
commonpb "go.temporal.io/api/common/v1"
historypb "go.temporal.io/api/history/v1"
"go.temporal.io/api/serviceerror"
historyspb "go.temporal.io/server/api/history/v1"
persistencespb "go.temporal.io/server/api/persistence/v1"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/log/tag"
)
// ReadFullPageEvents reads a full page of history events from ExecutionManager. Due to storage format of V2 History
@@ -56,6 +61,30 @@ func ReadFullPageEvents(
}
}
// ReadFullPageRawEvents reads a full page of raw history events from ExecutionManager. Due to storage format of V2 History
// it is not guaranteed that pageSize amount of data is returned. Function returns the list of history blobs, the size
// of data read, the next page token, and an error if present.
func ReadFullPageRawEvents(
ctx context.Context,
executionMgr ExecutionManager,
req *ReadHistoryBranchRequest,
) ([]*commonpb.DataBlob, int, []byte, error) {
var blobs []*commonpb.DataBlob
size := 0
for {
response, err := executionMgr.ReadRawHistoryBranch(ctx, req)
if err != nil {
return nil, 0, nil, err
}
blobs = append(blobs, response.HistoryEventBlobs...)
size += response.Size
if len(blobs) >= req.PageSize || len(response.NextPageToken) == 0 {
return blobs, size, response.NextPageToken, nil
}
req.NextPageToken = response.NextPageToken
}
}
// ReadFullPageEventsByBatch reads a full page of history events by batch from ExecutionManager. Due to storage format of V2 History
// it is not guaranteed that pageSize amount of data is returned. Function returns the list of history batches, the size
// of data read, the next page token, and an error if present.
@@ -128,3 +157,41 @@ func sortAncestors(ans []*persistencespb.HistoryBranchRange) {
}
}
}
func ValidateBatch(
batch []*historyspb.StrippedHistoryEvent,
branchToken []byte,
lastEventID int64,
logger log.Logger,
) error {
var firstEvent, lastEvent *historyspb.StrippedHistoryEvent
var eventCount int
dataLossTags := func(cause string) []tag.Tag {
return []tag.Tag{
tag.Cause(cause),
tag.WorkflowBranchToken(branchToken),
tag.WorkflowFirstEventID(firstEvent.GetEventId()),
tag.FirstEventVersion(firstEvent.GetVersion()),
tag.WorkflowNextEventID(lastEvent.GetEventId()),
tag.LastEventVersion(lastEvent.GetVersion()),
tag.Counter(eventCount),
tag.TokenLastEventID(lastEventID),
}
}
firstEvent = batch[0]
eventCount = len(batch)
lastEvent = batch[eventCount-1]
if firstEvent.GetVersion() != lastEvent.GetVersion() || firstEvent.GetEventId()+int64(eventCount-1) != lastEvent.GetEventId() {
// in a single batch, version should be the same, and ID should be contiguous
logger.Error(dataLossMsg, dataLossTags(errWrongVersion)...)
return serviceerror.NewDataLoss(errWrongVersion)
}
// If it is the first batch in the response, we cannot check the first event id here. That information is in the historyPagingToken.
// TODO: PPV refactor to move this check to ExecutionManager so that we can include that check as well.
if lastEventID != 0 && firstEvent.GetEventId() != lastEventID+1 {
logger.Error(dataLossMsg, dataLossTags(errNonContiguousEventID)...)
return serviceerror.NewDataLoss(errNonContiguousEventID)
}
return nil
}

View File

@@ -34,6 +34,7 @@ import (
enumspb "go.temporal.io/api/enums/v1"
historypb "go.temporal.io/api/history/v1"
enumsspb "go.temporal.io/server/api/enums/v1"
historyspb "go.temporal.io/server/api/history/v1"
persistencespb "go.temporal.io/server/api/persistence/v1"
replicationspb "go.temporal.io/server/api/replication/v1"
"go.temporal.io/server/common/codec"
@@ -50,6 +51,7 @@ type (
SerializeEvent(event *historypb.HistoryEvent, encodingType enumspb.EncodingType) (*commonpb.DataBlob, error)
DeserializeEvent(data *commonpb.DataBlob) (*historypb.HistoryEvent, error)
DeserializeStrippedEvents(data *commonpb.DataBlob) ([]*historyspb.StrippedHistoryEvent, error)
SerializeClusterMetadata(icm *persistencespb.ClusterMetadata, encodingType enumspb.EncodingType) (*commonpb.DataBlob, error)
DeserializeClusterMetadata(data *commonpb.DataBlob) (*persistencespb.ClusterMetadata, error)
@@ -177,6 +179,33 @@ func (t *serializerImpl) DeserializeEvents(data *commonpb.DataBlob) ([]*historyp
return events.Events, nil
}
func (t *serializerImpl) DeserializeStrippedEvents(data *commonpb.DataBlob) ([]*historyspb.StrippedHistoryEvent, error) {
if data == nil {
return nil, nil
}
if len(data.Data) == 0 {
return nil, nil
}
events := &historyspb.StrippedHistoryEvents{}
var err error
//nolint:exhaustive
switch data.EncodingType {
case enumspb.ENCODING_TYPE_PROTO3:
// Discard unknown fields to improve performance. StrippedHistoryEvents is usually deserialized from HistoryEvent
// which has extra fields that are not needed for this message.
err = proto.UnmarshalOptions{
DiscardUnknown: true,
}.Unmarshal(data.Data, events)
default:
return nil, NewUnknownEncodingTypeError(data.EncodingType.String(), enumspb.ENCODING_TYPE_PROTO3)
}
if err != nil {
return nil, NewDeserializationError(enumspb.ENCODING_TYPE_PROTO3, err)
}
return events.Events, nil
}
func (t *serializerImpl) SerializeEvent(event *historypb.HistoryEvent, encodingType enumspb.EncodingType) (*commonpb.DataBlob, error) {
if event == nil {
return nil, nil

View File

@@ -32,8 +32,10 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
historypb "go.temporal.io/api/history/v1"
"go.temporal.io/server/api/historyservice/v1"
persistencespb "go.temporal.io/server/api/persistence/v1"
"go.temporal.io/server/common"
"go.temporal.io/server/common/log"
@@ -196,6 +198,85 @@ func (s *temporalSerializerSuite) TestSerializeShardInfo_Random() {
s.ProtoEqual(&shardInfo, deserializedShardInfo)
}
func (s *temporalSerializerSuite) TestDeserializeStrippedEvents() {
// 1. Nil data blob
s.Run("NilDataBlob", func() {
events, err := s.serializer.DeserializeStrippedEvents(nil)
s.NoError(err)
s.Nil(events)
})
// 2. Empty data
s.Run("EmptyDataBlob", func() {
// Data is nil
events, err := s.serializer.DeserializeStrippedEvents(&commonpb.DataBlob{
EncodingType: enumspb.ENCODING_TYPE_PROTO3,
Data: nil,
})
s.NoError(err)
s.Nil(events)
// Data is empty byte array
events, err = s.serializer.DeserializeStrippedEvents(&commonpb.DataBlob{
EncodingType: enumspb.ENCODING_TYPE_PROTO3,
Data: []byte{},
})
s.NoError(err)
s.Nil(events)
})
// 3. Unknown encoding type
s.Run("UnknownEncodingType", func() {
_, err := s.serializer.DeserializeStrippedEvents(&commonpb.DataBlob{
EncodingType: enumspb.ENCODING_TYPE_JSON, // Not handled by our switch
Data: []byte("irrelevant-data"),
})
s.Error(err)
s.Contains(err.Error(), "unknown or unsupported encoding type")
})
// 4. Proper proto decoding, discarding unknown fields
s.Run("ProtoDiscardUnknownFields", func() {
// Build a HistoryEvent that contains fields *not* present in StrippedHistoryEvent
historyEvent := &historypb.HistoryEvent{
EventId: 123,
Version: 456,
EventTime: nil, // or a valid timestamp
// This is an extra field not present in StrippedHistoryEvent
Attributes: &historypb.HistoryEvent_WorkflowExecutionStartedEventAttributes{
WorkflowExecutionStartedEventAttributes: &historypb.WorkflowExecutionStartedEventAttributes{
WorkflowType: &commonpb.WorkflowType{Name: "some-workflow-type"},
},
},
}
historyEvents := &historypb.History{
Events: []*historypb.HistoryEvent{historyEvent},
}
// Marshal to protobuf
data, err := historyEvents.Marshal()
s.Require().NoError(err)
dataBlob := &commonpb.DataBlob{
EncodingType: enumspb.ENCODING_TYPE_PROTO3,
Data: data,
}
// Deserialize into StrippedHistoryEvents (should drop unknown fields)
deserializedEvents, err := s.serializer.DeserializeStrippedEvents(dataBlob)
s.NoError(err)
s.Require().Len(deserializedEvents, 1)
// Known fields should be preserved
s.EqualValues(123, deserializedEvents[0].EventId)
s.EqualValues(456, deserializedEvents[0].Version)
reflectMsg := deserializedEvents[0].ProtoReflect()
s.Empty(reflectMsg.GetUnknown(), "Unknown fields should have been discarded")
})
}
func (s *temporalSerializerSuite) TestSerializeWorkflowExecutionState() {
state := &persistencespb.WorkflowExecutionState{
RequestIds: make(map[string]*persistencespb.RequestIDInfo),
@@ -224,3 +305,80 @@ func (s *temporalSerializerSuite) TestSerializeWorkflowExecutionState() {
s.NotNil(deserializedState)
s.ProtoEqual(state, deserializedState)
}
// HistoryService returns a different GetWorkflowExecutionHistoryResponse GetWorkflowExecutionHistoryResponseWithRaw to
// WorkflowHandler. Since HistoryClient is defined with the response type GetWorkflowExecutionHistoryResponse, grpc
// will deserialize this message to GetWorkflowExecutionHistoryResponse. This is done to avoid the extra CPU usage in
// history service to deserialize event blobs to []*HistoryEvent. This test ensures that
// GetWorkflowExecutionHistoryResponseWithRaw is correctly deserialized to GetWorkflowExecutionHistoryResponse.
func (s *temporalSerializerSuite) TestGetWorkflowExecutionHistoryResponseWithRawHistoryEvents() {
// Create history events and batches
fullHistory := &historypb.History{
Events: []*historypb.HistoryEvent{
{
EventId: 1,
EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED,
Version: 100,
},
{
EventId: 2,
EventType: enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED,
Version: 101,
},
{
EventId: 3,
EventType: enumspb.EVENT_TYPE_ACTIVITY_TASK_COMPLETED,
Version: 102,
},
{
EventId: 4,
EventType: enumspb.EVENT_TYPE_TIMER_FIRED,
Version: 103,
},
},
}
batch1 := &historypb.History{
Events: fullHistory.Events[:2],
}
batch2 := &historypb.History{
Events: fullHistory.Events[2:],
}
// Marshal each batch
rawHistory1, err := batch1.Marshal()
s.Require().NoError(err)
db1 := &commonpb.DataBlob{
EncodingType: enumspb.ENCODING_TYPE_PROTO3,
Data: rawHistory1,
}
rawHistory2, err := batch2.Marshal()
s.Require().NoError(err)
db2 := &commonpb.DataBlob{
EncodingType: enumspb.ENCODING_TYPE_PROTO3,
Data: rawHistory2,
}
rawResp := &historyservice.GetWorkflowExecutionHistoryResponseWithRaw{
History: [][]byte{db1.Data, db2.Data},
}
serializedRawResp, err := rawResp.Marshal()
s.Require().NoError(err)
resp := &historyservice.GetWorkflowExecutionHistoryResponse{}
err = resp.Unmarshal(serializedRawResp)
s.Require().NoError(err)
// Verify resp has same list of history events as fullHistory
for i, event := range resp.History.Events {
s.Equal(fullHistory.Events[i].EventId, event.EventId)
s.Equal(fullHistory.Events[i].Version, event.Version)
s.Equal(fullHistory.Events[i].EventType, event.EventType)
s.Nil(event.Attributes)
}
}

View File

@@ -150,7 +150,7 @@ func (wt *WorkflowTags) extractFromHistoryServiceServerMessage(message any) []ta
tag.WorkflowID(r.GetRequest().GetExecution().GetWorkflowId()),
tag.WorkflowRunID(r.GetRequest().GetExecution().GetRunId()),
}
case *historyservice.GetWorkflowExecutionHistoryResponse:
case *historyservice.GetWorkflowExecutionHistoryResponseWithRaw:
return nil
case *historyservice.GetWorkflowExecutionHistoryReverseRequest:
return []tag.Tag{

View File

@@ -37,6 +37,11 @@ done
color "Update license headers for proto files..."
go run ./cmd/tools/copyright/licensegen.go --scanDir "$new"
color "Modify history service server interface..."
sed -i.bak -e \
's/GetWorkflowExecutionHistory(context\.Context, \*GetWorkflowExecutionHistoryRequest) (\*GetWorkflowExecutionHistoryResponse, error)/GetWorkflowExecutionHistory(context.Context, *GetWorkflowExecutionHistoryRequest) (*GetWorkflowExecutionHistoryResponseWithRaw, error)/g' \
"$new"/temporal/server/api/historyservice/v1/service_grpc.pb.go && rm "$new"/temporal/server/api/historyservice/v1/service_grpc.pb.go.bak
color "Moving proto files into place..."
old=$api.old
[[ -d "$api" ]] && mv -f "$api" "$old"

View File

@@ -65,3 +65,13 @@ message TaskRange {
TaskKey inclusive_min_task_key = 1;
TaskKey exclusive_max_task_key = 2;
}
// StrippedHistoryEvent is a stripped down version of HistoryEvent that only contains the event_id and version.
message StrippedHistoryEvent {
int64 event_id = 1;
int64 version = 4;
}
message StrippedHistoryEvents {
repeated StrippedHistoryEvent events = 1;
}

View File

@@ -982,6 +982,13 @@ message GetWorkflowExecutionHistoryRequest {
message GetWorkflowExecutionHistoryResponse {
temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse response = 1;
temporal.api.history.v1.History history = 2;
}
// This message must be wire compatible with GetWorkflowExecutionHistoryResponse.
message GetWorkflowExecutionHistoryResponseWithRaw {
temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse response = 1;
repeated bytes history = 2;
}
message GetWorkflowExecutionHistoryReverseRequest {

View File

@@ -88,6 +88,7 @@ import (
"go.temporal.io/server/common/tqid"
"go.temporal.io/server/common/util"
"go.temporal.io/server/common/worker_versioning"
"go.temporal.io/server/service/history/api"
"go.temporal.io/server/service/worker/batcher"
"go.temporal.io/server/service/worker/deployment"
"go.temporal.io/server/service/worker/scheduler"
@@ -763,6 +764,21 @@ func (wh *WorkflowHandler) GetWorkflowExecutionHistory(ctx context.Context, requ
if err != nil {
return nil, err
}
isCloseEventOnly := request.HistoryEventFilterType == enumspb.HISTORY_EVENT_FILTER_TYPE_CLOSE_EVENT
err = api.ProcessInternalRawHistory(
ctx,
wh.saProvider,
wh.saMapperProvider,
response,
wh.visibilityMgr,
wh.versionChecker,
namespace.Name(request.GetNamespace()),
isCloseEventOnly,
)
if err != nil {
return nil, err
}
return response.Response, nil
}

View File

@@ -39,6 +39,7 @@ import (
batchpb "go.temporal.io/api/batch/v1"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
failurepb "go.temporal.io/api/failure/v1"
filterpb "go.temporal.io/api/filter/v1"
historypb "go.temporal.io/api/history/v1"
namespacepb "go.temporal.io/api/namespace/v1"
@@ -48,6 +49,7 @@ import (
updatepb "go.temporal.io/api/update/v1"
workflowpb "go.temporal.io/api/workflow/v1"
"go.temporal.io/api/workflowservice/v1"
historyspb "go.temporal.io/server/api/history/v1"
"go.temporal.io/server/api/historyservice/v1"
"go.temporal.io/server/api/historyservicemock/v1"
"go.temporal.io/server/api/matchingservice/v1"
@@ -73,7 +75,8 @@ import (
"go.temporal.io/server/common/rpc/interceptor"
"go.temporal.io/server/common/searchattribute"
"go.temporal.io/server/common/tasktoken"
e "go.temporal.io/server/service/history/events"
"go.temporal.io/server/service/history/api"
"go.temporal.io/server/service/history/tests"
"go.temporal.io/server/service/worker/batcher"
"go.temporal.io/server/service/worker/scheduler"
"go.uber.org/mock/gomock"
@@ -2036,16 +2039,16 @@ func (s *WorkflowHandlerSuite) TestCountWorkflowExecutions() {
}
func (s *WorkflowHandlerSuite) TestVerifyHistoryIsComplete() {
events := make([]*historypb.HistoryEvent, 50)
events := make([]*historyspb.StrippedHistoryEvent, 50)
for i := 0; i < len(events); i++ {
events[i] = &historypb.HistoryEvent{EventId: int64(i + 1)}
events[i] = &historyspb.StrippedHistoryEvent{EventId: int64(i + 1)}
}
var eventsWithHoles []*historypb.HistoryEvent
var eventsWithHoles []*historyspb.StrippedHistoryEvent
eventsWithHoles = append(eventsWithHoles, events[9:12]...)
eventsWithHoles = append(eventsWithHoles, events[20:31]...)
testCases := []struct {
events []*historypb.HistoryEvent
events []*historyspb.StrippedHistoryEvent
firstEventID int64
lastEventID int64
isFirstPage bool
@@ -2079,7 +2082,16 @@ func (s *WorkflowHandlerSuite) TestVerifyHistoryIsComplete() {
}
for i, tc := range testCases {
err := e.VerifyHistoryIsComplete(tc.events, tc.firstEventID, tc.lastEventID, tc.isFirstPage, tc.isLastPage, tc.pageSize)
err := api.VerifyHistoryIsComplete(
tc.events[0],
tc.events[len(tc.events)-1],
len(tc.events),
tc.firstEventID,
tc.lastEventID,
tc.isFirstPage,
tc.isLastPage,
tc.pageSize,
)
if tc.isResultErr {
s.Error(err, "testcase %v failed", i)
} else {
@@ -2802,6 +2814,91 @@ func (s *WorkflowHandlerSuite) TestListBatchOperations_InvalidRerquest() {
s.ErrorAs(err, &invalidArgumentErr)
}
// This test is to make sure that GetWorkflowExecutionHistory returns the correct history when history service sends
// History events in the field response.History. This happens when history.sendRawHistoryBetweenInternalServices is enabled.
// This test verifies that HistoryEventFilterType is applied and EVENT_TYPE_WORKFLOW_EXECUTION_FAILED is converted to
// EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW for older SDKs.
func (s *WorkflowHandlerSuite) TestGetWorkflowExecutionHistory_InternalRawHistoryEnabled() {
config := s.newConfig()
wh := s.getWorkflowHandler(config)
we := commonpb.WorkflowExecution{WorkflowId: "wid1", RunId: uuid.New().String()}
newRunID := uuid.New().String()
s.mockNamespaceCache.EXPECT().GetNamespaceID(tests.Namespace).Return(tests.NamespaceID, nil).Times(2)
s.mockSearchAttributesProvider.EXPECT().GetSearchAttributes(gomock.Any(), gomock.Any()).Return(searchattribute.TestNameTypeMap, nil).Times(2)
req := &workflowservice.GetWorkflowExecutionHistoryRequest{
Namespace: tests.Namespace.String(),
Execution: &we,
MaximumPageSize: 10,
HistoryEventFilterType: enumspb.HISTORY_EVENT_FILTER_TYPE_CLOSE_EVENT,
SkipArchival: true,
}
s.mockHistoryClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), &historyservice.GetWorkflowExecutionHistoryRequest{
NamespaceId: tests.NamespaceID.String(),
Request: req,
}).Return(&historyservice.GetWorkflowExecutionHistoryResponse{
Response: &workflowservice.GetWorkflowExecutionHistoryResponse{
History: &historypb.History{},
},
History: &historypb.History{
Events: []*historypb.HistoryEvent{
{
EventId: int64(5),
EventType: enumspb.EVENT_TYPE_WORKFLOW_TASK_FAILED,
Attributes: &historypb.HistoryEvent_WorkflowTaskFailedEventAttributes{
WorkflowTaskFailedEventAttributes: &historypb.WorkflowTaskFailedEventAttributes{
Failure: &failurepb.Failure{Message: "this workflow task failed"},
},
},
},
{
EventId: int64(5),
EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED,
Attributes: &historypb.HistoryEvent_WorkflowExecutionFailedEventAttributes{
WorkflowExecutionFailedEventAttributes: &historypb.WorkflowExecutionFailedEventAttributes{
Failure: &failurepb.Failure{Message: "this workflow failed"},
RetryState: enumspb.RETRY_STATE_IN_PROGRESS,
WorkflowTaskCompletedEventId: 4,
NewExecutionRunId: newRunID,
},
},
},
},
},
}, nil).Times(2)
oldGoSDKVersion := "1.9.1"
newGoSDKVersion := "1.10.1"
// new sdk: should see failed event
ctx := headers.SetVersionsForTests(context.Background(), newGoSDKVersion, headers.ClientNameGoSDK, headers.SupportedServerVersions, headers.AllFeatures)
resp, err := wh.GetWorkflowExecutionHistory(ctx, req)
s.NoError(err)
s.False(resp.Archived)
event := resp.History.Events[0]
s.Equal(int64(5), event.EventId)
s.Equal(enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED, event.EventType)
attrs := event.GetWorkflowExecutionFailedEventAttributes()
s.Equal("this workflow failed", attrs.Failure.Message)
s.Equal(newRunID, attrs.NewExecutionRunId)
s.Equal(enumspb.RETRY_STATE_IN_PROGRESS, attrs.RetryState)
// old sdk: should see continued-as-new event
// TODO: We can remove this once we no longer support SDK versions prior to around September 2021.
// See comment in workflowHandler.go:GetWorkflowExecutionHistory
ctx = headers.SetVersionsForTests(context.Background(), oldGoSDKVersion, headers.ClientNameGoSDK, headers.SupportedServerVersions, "")
resp, err = wh.GetWorkflowExecutionHistory(ctx, req)
s.NoError(err)
s.False(resp.Archived)
event = resp.History.Events[0]
s.Equal(int64(5), event.EventId)
s.Equal(enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW, event.EventType)
attrs2 := event.GetWorkflowExecutionContinuedAsNewEventAttributes()
s.Equal(newRunID, attrs2.NewExecutionRunId)
s.Equal("this workflow failed", attrs2.Failure.Message)
}
func (s *WorkflowHandlerSuite) newConfig() *Config {
return NewConfig(dc.NewNoopCollection(), numHistoryShards)
}

View File

@@ -33,7 +33,10 @@ import (
historypb "go.temporal.io/api/history/v1"
"go.temporal.io/api/serviceerror"
historyspb "go.temporal.io/server/api/history/v1"
"go.temporal.io/server/api/historyservice/v1"
"go.temporal.io/server/common"
"go.temporal.io/server/common/failure"
"go.temporal.io/server/common/headers"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/metrics"
"go.temporal.io/server/common/namespace"
@@ -43,7 +46,6 @@ import (
"go.temporal.io/server/common/rpc/interceptor"
"go.temporal.io/server/common/searchattribute"
"go.temporal.io/server/service/history/consts"
"go.temporal.io/server/service/history/events"
"go.temporal.io/server/service/history/shard"
)
@@ -55,28 +57,68 @@ func GetRawHistory(
firstEventID int64,
nextEventID int64,
pageSize int32,
nextPageToken []byte,
token []byte,
transientWorkflowTaskInfo *historyspb.TransientWorkflowTaskInfo,
branchToken []byte,
) ([]*commonpb.DataBlob, []byte, error) {
shardID := common.WorkflowIDToHistoryShard(namespaceID.String(), execution.GetWorkflowId(), shard.GetConfig().NumberOfShards)
logger := shard.GetLogger()
rawHistory, size, nextToken, err := persistence.ReadFullPageRawEvents(
ctx, shard.GetExecutionManager(),
&persistence.ReadHistoryBranchRequest{
BranchToken: branchToken,
MinEventID: firstEventID,
MaxEventID: nextEventID,
PageSize: int(pageSize),
NextPageToken: token,
ShardID: shardID,
},
)
persistenceExecutionManager := shard.GetExecutionManager()
resp, err := persistenceExecutionManager.ReadRawHistoryBranch(ctx, &persistence.ReadHistoryBranchRequest{
BranchToken: branchToken,
MinEventID: firstEventID,
MaxEventID: nextEventID,
PageSize: int(pageSize),
NextPageToken: nextPageToken,
ShardID: shardID,
})
if err != nil {
return nil, nil, err
}
rawHistory := resp.HistoryEventBlobs
allEvents := make([]*historyspb.StrippedHistoryEvent, 0)
var lastEventID int64
for _, blob := range rawHistory {
events, err := shard.GetPayloadSerializer().DeserializeStrippedEvents(blob)
if err != nil {
return nil, nil, err
}
err = persistence.ValidateBatch(events, branchToken, lastEventID, logger)
if err != nil {
return nil, nil, err
}
allEvents = append(allEvents, events...)
lastEventID = events[len(events)-1].GetEventId()
}
var firstEvent, lastEvent *historyspb.StrippedHistoryEvent
if len(allEvents) > 0 {
firstEvent = allEvents[0]
lastEvent = allEvents[len(allEvents)-1]
}
if err = VerifyHistoryIsComplete(
firstEvent,
lastEvent,
len(allEvents),
firstEventID,
nextEventID-1,
len(token) == 0,
len(nextToken) == 0,
int(pageSize),
); err != nil {
metricsHandler := interceptor.GetMetricsHandlerFromContext(ctx, logger).WithTags(metrics.OperationTag(metrics.HistoryGetHistoryScope))
metrics.ServiceErrIncompleteHistoryCounter.With(metricsHandler).Record(1)
logger.Error("getHistory: incomplete history",
tag.WorkflowBranchToken(branchToken),
tag.Error(err))
}
if len(resp.NextPageToken) == 0 && transientWorkflowTaskInfo != nil {
metricsHandler := interceptor.GetMetricsHandlerFromContext(ctx, shard.GetLogger()).WithTags(metrics.OperationTag(metrics.HistoryGetHistoryScope))
metrics.HistorySize.With(metricsHandler).Record(int64(size))
if len(nextToken) == 0 && transientWorkflowTaskInfo != nil {
if err := validateTransientWorkflowTaskEvents(nextEventID, transientWorkflowTaskInfo); err != nil {
logger := shard.GetLogger()
metricsHandler := interceptor.GetMetricsHandlerFromContext(ctx, logger).WithTags(metrics.OperationTag(metrics.HistoryGetRawHistoryScope))
@@ -89,16 +131,15 @@ func GetRawHistory(
return nil, nil, err
}
for _, event := range transientWorkflowTaskInfo.HistorySuffix {
blob, err := shard.GetPayloadSerializer().SerializeEvent(event, enumspb.ENCODING_TYPE_PROTO3)
if len(transientWorkflowTaskInfo.HistorySuffix) > 0 {
blob, err := shard.GetPayloadSerializer().SerializeEvents(transientWorkflowTaskInfo.HistorySuffix, enumspb.ENCODING_TYPE_PROTO3)
if err != nil {
return nil, nil, err
}
rawHistory = append(rawHistory, blob)
}
}
return rawHistory, resp.NextPageToken, nil
return rawHistory, nextToken, nil
}
func GetHistory(
@@ -149,8 +190,19 @@ func GetHistory(
metrics.HistorySize.With(metricsHandler).Record(int64(size))
isLastPage := len(nextPageToken) == 0
if err := events.VerifyHistoryIsComplete(
historyEvents,
var firstEvent, lastEvent *historyspb.StrippedHistoryEvent
if len(historyEvents) > 0 {
firstEvent = &historyspb.StrippedHistoryEvent{
EventId: historyEvents[0].GetEventId(),
}
lastEvent = &historyspb.StrippedHistoryEvent{
EventId: historyEvents[len(historyEvents)-1].GetEventId(),
}
}
if err := VerifyHistoryIsComplete(
firstEvent,
lastEvent,
len(historyEvents),
firstEventID,
nextEventID-1,
isFirstPage,
@@ -163,7 +215,6 @@ func GetHistory(
tag.WorkflowRunID(execution.GetRunId()),
tag.Error(err))
}
if len(nextPageToken) == 0 && transientWorkflowTaskInfo != nil {
if err := validateTransientWorkflowTaskEvents(nextEventID, transientWorkflowTaskInfo); err != nil {
metrics.ServiceErrIncompleteHistoryCounter.With(metricsHandler).Record(1)
@@ -177,7 +228,16 @@ func GetHistory(
historyEvents = append(historyEvents, transientWorkflowTaskInfo.HistorySuffix...)
}
if err := ProcessOutgoingSearchAttributes(shard, historyEvents, namespaceID, persistenceVisibilityMgr); err != nil {
ns, err := shard.GetNamespaceRegistry().GetNamespaceName(namespaceID)
if err != nil {
return nil, nil, err
}
if err := ProcessOutgoingSearchAttributes(
shard.GetSearchAttributesProvider(),
shard.GetSearchAttributesMapperProvider(),
historyEvents,
ns,
persistenceVisibilityMgr); err != nil {
return nil, nil, err
}
@@ -228,7 +288,16 @@ func GetHistoryReverse(
metricsHandler := interceptor.GetMetricsHandlerFromContext(ctx, logger).WithTags(metrics.OperationTag(metrics.HistoryGetHistoryReverseScope))
metrics.HistorySize.With(metricsHandler).Record(int64(size))
if err := ProcessOutgoingSearchAttributes(shard, historyEvents, namespaceID, persistenceVisibilityMgr); err != nil {
ns, err := shard.GetNamespaceRegistry().GetNamespaceName(namespaceID)
if err != nil {
return nil, nil, 0, err
}
if err := ProcessOutgoingSearchAttributes(
shard.GetSearchAttributesProvider(),
shard.GetSearchAttributesMapperProvider(),
historyEvents,
ns,
persistenceVisibilityMgr); err != nil {
return nil, nil, 0, err
}
@@ -247,16 +316,13 @@ func GetHistoryReverse(
}
func ProcessOutgoingSearchAttributes(
shardCtx shard.Context,
saProvider searchattribute.Provider,
saMapperProvider searchattribute.MapperProvider,
events []*historypb.HistoryEvent,
namespaceId namespace.ID,
ns namespace.Name,
persistenceVisibilityMgr manager.VisibilityManager,
) error {
ns, err := shardCtx.GetNamespaceRegistry().GetNamespaceName(namespaceId)
if err != nil {
return err
}
saTypeMap, err := shardCtx.GetSearchAttributesProvider().GetSearchAttributes(persistenceVisibilityMgr.GetIndexName(), false)
saTypeMap, err := saProvider.GetSearchAttributes(persistenceVisibilityMgr.GetIndexName(), false)
if err != nil {
return serviceerror.NewUnavailable(fmt.Sprintf(consts.ErrUnableToGetSearchAttributesMessage, err))
}
@@ -274,7 +340,7 @@ func ProcessOutgoingSearchAttributes(
}
if searchAttributes != nil {
searchattribute.ApplyTypeMap(searchAttributes, saTypeMap)
aliasedSas, err := searchattribute.AliasFields(shardCtx.GetSearchAttributesMapperProvider(), searchAttributes, ns.String())
aliasedSas, err := searchattribute.AliasFields(saMapperProvider, searchAttributes, ns.String())
if err != nil {
return err
}
@@ -305,3 +371,181 @@ func validateTransientWorkflowTaskEvents(
return nil
}
func VerifyHistoryIsComplete(
firstEvent *historyspb.StrippedHistoryEvent,
lastEvent *historyspb.StrippedHistoryEvent,
eventCount int,
expectedFirstEventID int64,
expectedLastEventID int64,
isFirstPage bool,
isLastPage bool,
pageSize int,
) error {
if eventCount == 0 {
if isLastPage {
// we seem to be returning a non-nil pageToken on the lastPage which
// in turn cases the client to call getHistory again - only to find
// there are no more events to consume - bail out if this is the case here
return nil
}
return serviceerror.NewDataLoss("History contains zero events.")
}
if !isFirstPage { // at least one page of history has been read previously
if firstEvent.GetEventId() <= expectedFirstEventID {
// not first page and no events have been read in the previous pages - not possible
return serviceerror.NewDataLoss(fmt.Sprintf("Invalid history: expected first eventID to be > %v but got %v", expectedFirstEventID, firstEvent.GetEventId()))
}
expectedFirstEventID = firstEvent.GetEventId()
}
if !isLastPage {
// estimate lastEventID based on pageSize. This is a lower bound
// since the persistence layer counts "batch of events" as a single page
expectedLastEventID = expectedFirstEventID + int64(pageSize) - 1
}
nExpectedEvents := expectedLastEventID - expectedFirstEventID + 1
if firstEvent.GetEventId() == expectedFirstEventID &&
((isLastPage && lastEvent.GetEventId() == expectedLastEventID && int64(eventCount) == nExpectedEvents) ||
(!isLastPage && lastEvent.GetEventId() >= expectedLastEventID && int64(eventCount) >= nExpectedEvents)) {
return nil
}
return serviceerror.NewDataLoss(fmt.Sprintf("Incomplete history: expected events [%v-%v] but got events [%v-%v] of length %v: isFirstPage=%v,isLastPage=%v,pageSize=%v",
expectedFirstEventID,
expectedLastEventID,
firstEvent.GetEventId(),
lastEvent.GetEventId(),
eventCount,
isFirstPage,
isLastPage,
pageSize))
}
// ProcessInternalRawHistory processes history in the field response.History.
// History service can send history events in response.History.Events. In that case, process the events and move them
// to response.Response.History. Usually this is done by history service but when history.sendRawHistoryBetweenInternalServices
// is enabled, history service sends raw history events to frontend without any processing.
func ProcessInternalRawHistory(
requestContext context.Context,
saProvider searchattribute.Provider,
saMapperProvider searchattribute.MapperProvider,
response *historyservice.GetWorkflowExecutionHistoryResponse,
visibilityManager manager.VisibilityManager,
versionChecker headers.VersionChecker,
ns namespace.Name,
isCloseEventOnly bool,
) error {
if response == nil || response.History == nil {
return nil
}
response.Response.History = response.History
if isCloseEventOnly && len(response.Response.History.Events) > 0 {
response.Response.History.Events = response.Response.History.Events[len(response.Response.History.Events)-1:]
}
err := ProcessOutgoingSearchAttributes(
saProvider,
saMapperProvider,
response.Response.History.Events,
ns,
visibilityManager,
)
if err != nil {
return err
}
err = FixFollowEvents(requestContext, versionChecker, isCloseEventOnly, response.Response.History)
if err != nil {
return err
}
return nil
}
func FixFollowEvents(
ctx context.Context,
versionChecker headers.VersionChecker,
isCloseEventOnly bool,
history *historypb.History,
) error {
// Backwards-compatibility fix for retry events after #1866: older SDKs don't know how to "follow"
// subsequent runs linked in WorkflowExecutionFailed or TimedOut events, so they'll get the wrong result
// when trying to "get" the result of a workflow run. (This applies to cron runs also but "get" on a cron
// workflow isn't really sensible.)
//
// To handle this in a backwards-compatible way, we'll pretend the completion event is actually
// ContinuedAsNew, if it's Failed or TimedOut. We want to do this only when the client is looking for a
// completion event, and not when it's getting the history to display for other purposes. The best signal
// for that purpose is `isCloseEventOnly`. (We can't use `isLongPoll` also because in some cases, older
// versions of the Java SDK don't set that flag.)
//
// TODO: We can remove this once we no longer support SDK versions prior to around September 2021.
// Revisit this once we have an SDK deprecation policy.
followsNextRunId := versionChecker.ClientSupportsFeature(ctx, headers.FeatureFollowsNextRunID)
if isCloseEventOnly && !followsNextRunId && len(history.Events) > 0 {
lastEvent := history.Events[len(history.Events)-1]
fakeEvent, err := makeFakeContinuedAsNewEvent(ctx, lastEvent)
if err != nil {
return err
}
if fakeEvent != nil {
history.Events[len(history.Events)-1] = fakeEvent
}
}
return nil
}
func makeFakeContinuedAsNewEvent(
_ context.Context,
lastEvent *historypb.HistoryEvent,
) (*historypb.HistoryEvent, error) {
switch lastEvent.EventType { // nolint:exhaustive
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED:
if lastEvent.GetWorkflowExecutionCompletedEventAttributes().GetNewExecutionRunId() == "" {
return nil, nil
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED:
if lastEvent.GetWorkflowExecutionFailedEventAttributes().GetNewExecutionRunId() == "" {
return nil, nil
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT:
if lastEvent.GetWorkflowExecutionTimedOutEventAttributes().GetNewExecutionRunId() == "" {
return nil, nil
}
default:
return nil, nil
}
// We need to replace the last event with a continued-as-new event that has at least the
// NewExecutionRunId field. We don't actually need any other fields, since that's the only one
// the client looks at in this case, but copy the last result or failure from the real completed
// event just so it's clear what the result was.
newAttrs := &historypb.WorkflowExecutionContinuedAsNewEventAttributes{}
switch lastEvent.EventType { // nolint:exhaustive
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED:
attrs := lastEvent.GetWorkflowExecutionCompletedEventAttributes()
newAttrs.NewExecutionRunId = attrs.NewExecutionRunId
newAttrs.LastCompletionResult = attrs.Result
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED:
attrs := lastEvent.GetWorkflowExecutionFailedEventAttributes()
newAttrs.NewExecutionRunId = attrs.NewExecutionRunId
newAttrs.Failure = attrs.Failure
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT:
attrs := lastEvent.GetWorkflowExecutionTimedOutEventAttributes()
newAttrs.NewExecutionRunId = attrs.NewExecutionRunId
newAttrs.Failure = failure.NewTimeoutFailure("workflow timeout", enumspb.TIMEOUT_TYPE_START_TO_CLOSE)
}
return &historypb.HistoryEvent{
EventId: lastEvent.EventId,
EventTime: lastEvent.EventTime,
EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW,
Version: lastEvent.Version,
TaskId: lastEvent.TaskId,
Attributes: &historypb.HistoryEvent_WorkflowExecutionContinuedAsNewEventAttributes{
WorkflowExecutionContinuedAsNewEventAttributes: newAttrs,
},
}, nil
}

View File

@@ -38,7 +38,6 @@ import (
persistencespb "go.temporal.io/server/api/persistence/v1"
tokenspb "go.temporal.io/server/api/token/v1"
"go.temporal.io/server/common"
"go.temporal.io/server/common/failure"
"go.temporal.io/server/common/headers"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/namespace"
@@ -60,7 +59,7 @@ func Invoke(
eventNotifier events.Notifier,
request *historyservice.GetWorkflowExecutionHistoryRequest,
persistenceVisibilityMgr manager.VisibilityManager,
) (_ *historyservice.GetWorkflowExecutionHistoryResponse, retError error) {
) (_ *historyservice.GetWorkflowExecutionHistoryResponseWithRaw, retError error) {
namespaceID := namespace.ID(request.GetNamespaceId())
err := api.ValidateNamespaceUUID(namespaceID)
if err != nil {
@@ -225,9 +224,12 @@ func Invoke(
history := &historypb.History{}
history.Events = []*historypb.HistoryEvent{}
var historyBlob []*commonpb.DataBlob
config := shardContext.GetConfig()
sendRawHistoryBetweenInternalServices := config.SendRawHistoryBetweenInternalServices()
sendRawWorkflowHistoryForNamespace := config.SendRawWorkflowHistory(request.Request.GetNamespace())
if isCloseEventOnly {
if !isWorkflowRunning {
if shardContext.GetConfig().SendRawWorkflowHistory(request.Request.GetNamespace()) {
if sendRawWorkflowHistoryForNamespace || sendRawHistoryBetweenInternalServices {
historyBlob, _, err = api.GetRawHistory(
ctx,
shardContext,
@@ -243,7 +245,6 @@ func Invoke(
if err != nil {
return nil, err
}
// since getHistory func will not return empty history, so the below is safe
historyBlob = historyBlob[len(historyBlob)-1:]
} else {
@@ -282,7 +283,7 @@ func Invoke(
continuationToken = nil
}
} else {
if shardContext.GetConfig().SendRawWorkflowHistory(request.Request.GetNamespace()) {
if sendRawWorkflowHistoryForNamespace || sendRawHistoryBetweenInternalServices {
historyBlob, continuationToken.PersistenceToken, err = api.GetRawHistory(
ctx,
shardContext,
@@ -329,92 +330,34 @@ func Invoke(
return nil, err
}
// Backwards-compatibility fix for retry events after #1866: older SDKs don't know how to "follow"
// subsequent runs linked in WorkflowExecutionFailed or TimedOut events, so they'll get the wrong result
// when trying to "get" the result of a workflow run. (This applies to cron runs also but "get" on a cron
// workflow isn't really sensible.)
//
// To handle this in a backwards-compatible way, we'll pretend the completion event is actually
// ContinuedAsNew, if it's Failed or TimedOut. We want to do this only when the client is looking for a
// completion event, and not when it's getting the history to display for other purposes. The best signal
// for that purpose is `isCloseEventOnly`. (We can't use `isLongPoll` also because in some cases, older
// versions of the Java SDK don't set that flag.)
//
// TODO: We can remove this once we no longer support SDK versions prior to around September 2021.
// Revisit this once we have an SDK deprecation policy.
followsNextRunId := versionChecker.ClientSupportsFeature(ctx, headers.FeatureFollowsNextRunID)
if isCloseEventOnly && !followsNextRunId && len(history.Events) > 0 {
lastEvent := history.Events[len(history.Events)-1]
fakeEvent, err := makeFakeContinuedAsNewEvent(ctx, lastEvent)
// if SendRawHistoryBetweenInternalServices is enabled, we do this check in frontend service
if len(history.Events) > 0 {
err = api.FixFollowEvents(ctx, versionChecker, isCloseEventOnly, history)
if err != nil {
return nil, err
}
if fakeEvent != nil {
history.Events[len(history.Events)-1] = fakeEvent
}
}
return &historyservice.GetWorkflowExecutionHistoryResponse{
var rawHistory [][]byte
// if sendRawHistoryBetweenInternalServices is true and SendRawWorkflowHistory is not enabled for this namespace,
// send history in raw format in History field of historyservice.GetWorkflowExecutionHistoryResponseWithRaw.
// If SendRawWorkflowHistory is enabled for this namespace, raw history will be appended to RawHistory field in
// workflowservice.GetWorkflowExecutionHistoryResponse.
if sendRawHistoryBetweenInternalServices && !sendRawWorkflowHistoryForNamespace {
rawHistory = make([][]byte, 0, len(historyBlob))
for _, blob := range historyBlob {
rawHistory = append(rawHistory, blob.Data)
}
historyBlob = nil
}
return &historyservice.GetWorkflowExecutionHistoryResponseWithRaw{
Response: &workflowservice.GetWorkflowExecutionHistoryResponse{
History: history,
RawHistory: historyBlob,
NextPageToken: nextToken,
Archived: false,
},
}, nil
}
func makeFakeContinuedAsNewEvent(
_ context.Context,
lastEvent *historypb.HistoryEvent,
) (*historypb.HistoryEvent, error) {
// nolint:exhaustive
switch lastEvent.EventType {
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED:
if lastEvent.GetWorkflowExecutionCompletedEventAttributes().GetNewExecutionRunId() == "" {
return nil, nil
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED:
if lastEvent.GetWorkflowExecutionFailedEventAttributes().GetNewExecutionRunId() == "" {
return nil, nil
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT:
if lastEvent.GetWorkflowExecutionTimedOutEventAttributes().GetNewExecutionRunId() == "" {
return nil, nil
}
default:
return nil, nil
}
// We need to replace the last event with a continued-as-new event that has at least the
// NewExecutionRunId field. We don't actually need any other fields, since that's the only one
// the client looks at in this case, but copy the last result or failure from the real completed
// event just so it's clear what the result was.
newAttrs := &historypb.WorkflowExecutionContinuedAsNewEventAttributes{}
// nolint:exhaustive
switch lastEvent.EventType {
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED:
attrs := lastEvent.GetWorkflowExecutionCompletedEventAttributes()
newAttrs.NewExecutionRunId = attrs.NewExecutionRunId
newAttrs.LastCompletionResult = attrs.Result
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED:
attrs := lastEvent.GetWorkflowExecutionFailedEventAttributes()
newAttrs.NewExecutionRunId = attrs.NewExecutionRunId
newAttrs.Failure = attrs.Failure
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT:
attrs := lastEvent.GetWorkflowExecutionTimedOutEventAttributes()
newAttrs.NewExecutionRunId = attrs.NewExecutionRunId
newAttrs.Failure = failure.NewTimeoutFailure("workflow timeout", enumspb.TIMEOUT_TYPE_START_TO_CLOSE)
}
return &historypb.HistoryEvent{
EventId: lastEvent.EventId,
EventTime: lastEvent.EventTime,
EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW,
Version: lastEvent.Version,
TaskId: lastEvent.TaskId,
Attributes: &historypb.HistoryEvent_WorkflowExecutionContinuedAsNewEventAttributes{
WorkflowExecutionContinuedAsNewEventAttributes: newAttrs,
},
History: rawHistory,
}, nil
}

View File

@@ -730,7 +730,12 @@ func (s *Starter) generateResponse(
}, nil
}
if err := api.ProcessOutgoingSearchAttributes(s.shardContext, historyEvents, s.namespace.ID(), s.visibilityManager); err != nil {
if err := api.ProcessOutgoingSearchAttributes(
shardCtx.GetSearchAttributesProvider(),
shardCtx.GetSearchAttributesMapperProvider(),
historyEvents,
s.namespace.Name(),
s.visibilityManager); err != nil {
return nil, err
}

View File

@@ -372,7 +372,8 @@ type Config struct {
WorkflowExecutionMaxTotalUpdates dynamicconfig.IntPropertyFnWithNamespaceFilter
WorkflowExecutionMaxTotalUpdatesSuggestContinueAsNewThreshold dynamicconfig.FloatPropertyFnWithNamespaceFilter
SendRawWorkflowHistory dynamicconfig.BoolPropertyFnWithNamespaceFilter
SendRawHistoryBetweenInternalServices dynamicconfig.BoolPropertyFn
SendRawWorkflowHistory dynamicconfig.BoolPropertyFnWithNamespaceFilter
WorkflowIdReuseMinimalInterval dynamicconfig.DurationPropertyFnWithNamespaceFilter
EnableWorkflowIdReuseStartTimeValidation dynamicconfig.BoolPropertyFnWithNamespaceFilter
@@ -689,6 +690,7 @@ func NewConfig(
WorkflowExecutionMaxTotalUpdates: dynamicconfig.WorkflowExecutionMaxTotalUpdates.Get(dc),
WorkflowExecutionMaxTotalUpdatesSuggestContinueAsNewThreshold: dynamicconfig.WorkflowExecutionMaxTotalUpdatesSuggestContinueAsNewThreshold.Get(dc),
SendRawHistoryBetweenInternalServices: dynamicconfig.SendRawHistoryBetweenInternalServices.Get(dc),
SendRawWorkflowHistory: dynamicconfig.SendRawWorkflowHistory.Get(dc),
WorkflowIdReuseMinimalInterval: dynamicconfig.WorkflowIdReuseMinimalInterval.Get(dc),
EnableWorkflowIdReuseStartTimeValidation: dynamicconfig.EnableWorkflowIdReuseStartTimeValidation.Get(dc),

View File

@@ -1,88 +0,0 @@
// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package events
import (
"fmt"
historypb "go.temporal.io/api/history/v1"
"go.temporal.io/api/serviceerror"
)
func VerifyHistoryIsComplete(
events []*historypb.HistoryEvent,
expectedFirstEventID int64,
expectedLastEventID int64,
isFirstPage bool,
isLastPage bool,
pageSize int,
) error {
nEvents := len(events)
if nEvents == 0 {
if isLastPage {
// we seem to be returning a non-nil pageToken on the lastPage which
// in turn cases the client to call getHistory again - only to find
// there are no more events to consume - bail out if this is the case here
return nil
}
return serviceerror.NewDataLoss("History contains zero events.")
}
firstEventID := events[0].GetEventId()
lastEventID := events[nEvents-1].GetEventId()
if !isFirstPage { // at least one page of history has been read previously
if firstEventID <= expectedFirstEventID {
// not first page and no events have been read in the previous pages - not possible
return serviceerror.NewDataLoss(fmt.Sprintf("Invalid history: expected first eventID to be > %v but got %v", expectedFirstEventID, firstEventID))
}
expectedFirstEventID = firstEventID
}
if !isLastPage {
// estimate lastEventID based on pageSize. This is a lower bound
// since the persistence layer counts "batch of events" as a single page
expectedLastEventID = expectedFirstEventID + int64(pageSize) - 1
}
nExpectedEvents := expectedLastEventID - expectedFirstEventID + 1
if firstEventID == expectedFirstEventID &&
((isLastPage && lastEventID == expectedLastEventID && int64(nEvents) == nExpectedEvents) ||
(!isLastPage && lastEventID >= expectedLastEventID && int64(nEvents) >= nExpectedEvents)) {
return nil
}
return serviceerror.NewDataLoss(fmt.Sprintf("Incomplete history: expected events [%v-%v] but got events [%v-%v] of length %v: isFirstPage=%v,isLastPage=%v,pageSize=%v",
expectedFirstEventID,
expectedLastEventID,
firstEventID,
lastEventID,
nEvents,
isFirstPage,
isLastPage,
pageSize))
}

View File

@@ -2151,7 +2151,7 @@ func (h *Handler) StreamWorkflowReplicationMessages(
func (h *Handler) GetWorkflowExecutionHistory(
ctx context.Context,
request *historyservice.GetWorkflowExecutionHistoryRequest,
) (_ *historyservice.GetWorkflowExecutionHistoryResponse, retErr error) {
) (_ *historyservice.GetWorkflowExecutionHistoryResponseWithRaw, retErr error) {
defer log.CapturePanic(h.logger, &retErr)
h.startWG.Wait()

View File

@@ -1011,7 +1011,7 @@ func (e *historyEngineImpl) GetReplicationStatus(
func (e *historyEngineImpl) GetWorkflowExecutionHistory(
ctx context.Context,
request *historyservice.GetWorkflowExecutionHistoryRequest,
) (_ *historyservice.GetWorkflowExecutionHistoryResponse, retError error) {
) (_ *historyservice.GetWorkflowExecutionHistoryResponseWithRaw, retError error) {
return getworkflowexecutionhistory.Invoke(ctx, e.shardContext, e.workflowConsistencyChecker, e.versionChecker, e.eventNotifier, request, e.persistenceVisibilityMgr)
}

View File

@@ -5330,7 +5330,6 @@ func (s *engineSuite) TestReapplyEvents_ResetWorkflow() {
func (s *engineSuite) TestEagerWorkflowStart_DoesNotCreateTransferTask() {
var recordedTasks []tasks.Task
s.mockNamespaceCache.EXPECT().GetNamespaceName(gomock.Any()).Return(tests.Namespace, nil)
s.mockVisibilityMgr.EXPECT().GetIndexName().Return("mock")
s.mockSearchAttributesProvider.EXPECT().GetSearchAttributes("mock", false).Return(searchattribute.NameTypeMap{}, nil)
s.mockExecutionMgr.EXPECT().CreateWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, request *persistence.CreateWorkflowExecutionRequest) (*persistence.CreateWorkflowExecutionResponse, error) {
@@ -5587,6 +5586,119 @@ func (s *engineSuite) TestGetWorkflowExecutionHistory() {
s.Equal("this workflow failed", attrs2.Failure.Message)
}
func (s *engineSuite) TestGetWorkflowExecutionHistoryWhenInternalRawHistoryIsEnabled() {
s.config.SendRawHistoryBetweenInternalServices = func() bool { return true }
we := commonpb.WorkflowExecution{WorkflowId: "wid1", RunId: uuid.New()}
namespaceEntry := namespace.NewLocalNamespaceForTest(
&persistencespb.NamespaceInfo{Name: "test-namespace"},
&persistencespb.NamespaceConfig{},
"")
s.mockNamespaceCache.EXPECT().GetNamespaceByID(gomock.Any()).Return(namespaceEntry, nil).AnyTimes()
newRunID := uuid.New()
req := &historyservice.GetWorkflowExecutionHistoryRequest{
NamespaceId: tests.NamespaceID.String(),
Request: &workflowservice.GetWorkflowExecutionHistoryRequest{
Execution: &we,
MaximumPageSize: 10,
NextPageToken: nil,
WaitNewEvent: true,
HistoryEventFilterType: enumspb.HISTORY_EVENT_FILTER_TYPE_CLOSE_EVENT,
SkipArchival: true,
},
}
// set up mocks to simulate a failed workflow with a retry policy. the failure event is id 5.
branchToken := []byte{1, 2, 3}
s.mockNamespaceCache.EXPECT().GetNamespaceName(tests.NamespaceID).Return(tests.Namespace, nil).AnyTimes()
versionHistory := versionhistory.NewVersionHistory(branchToken, []*historyspb.VersionHistoryItem{
versionhistory.NewVersionHistoryItem(int64(10), int64(100)),
})
versionHistories := versionhistory.NewVersionHistories(versionHistory)
mState := &persistencespb.WorkflowMutableState{
ExecutionState: &persistencespb.WorkflowExecutionState{
RunId: we.RunId,
State: enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED,
Status: enumspb.WORKFLOW_EXECUTION_STATUS_FAILED,
},
NextEventId: 6,
ExecutionInfo: &persistencespb.WorkflowExecutionInfo{
NamespaceId: tests.NamespaceID.String(),
WorkflowId: we.WorkflowId,
VersionHistories: versionHistories,
WorkflowTypeName: "mytype",
LastFirstEventId: 5,
LastFirstEventTxnId: 100,
},
}
s.mockExecutionMgr.EXPECT().GetWorkflowExecution(gomock.Any(), &persistence.GetWorkflowExecutionRequest{
ShardID: 1,
NamespaceID: tests.NamespaceID.String(),
WorkflowID: we.WorkflowId,
RunID: we.RunId,
}).Return(&persistence.GetWorkflowExecutionResponse{State: mState}, nil).AnyTimes()
// GetWorkflowExecutionHistory will request the last event
history := historypb.History{
Events: []*historypb.HistoryEvent{
{
EventId: int64(5),
EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED,
Attributes: &historypb.HistoryEvent_WorkflowExecutionFailedEventAttributes{
WorkflowExecutionFailedEventAttributes: &historypb.WorkflowExecutionFailedEventAttributes{
Failure: &failurepb.Failure{Message: "this workflow failed"},
RetryState: enumspb.RETRY_STATE_IN_PROGRESS,
WorkflowTaskCompletedEventId: 4,
NewExecutionRunId: newRunID,
},
},
},
},
}
historyBlob, err := history.Marshal()
s.NoError(err)
s.mockExecutionMgr.EXPECT().ReadRawHistoryBranch(gomock.Any(), &persistence.ReadHistoryBranchRequest{
BranchToken: branchToken,
MinEventID: 5,
MaxEventID: 6,
PageSize: 10,
NextPageToken: nil,
ShardID: 1,
}).Return(&persistence.ReadRawHistoryBranchResponse{
HistoryEventBlobs: []*commonpb.DataBlob{
{
EncodingType: enumspb.ENCODING_TYPE_PROTO3,
Data: historyBlob,
},
},
NextPageToken: []byte{},
Size: 1,
}, nil).Times(1)
s.mockExecutionMgr.EXPECT().TrimHistoryBranch(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
s.mockSearchAttributesProvider.EXPECT().GetSearchAttributes(gomock.Any(), false).Return(searchattribute.TestNameTypeMap, nil).AnyTimes()
s.mockVisibilityMgr.EXPECT().GetIndexName().Return(esIndexName).AnyTimes()
engine, err := s.historyEngine.shardContext.GetEngine(context.Background())
s.NoError(err)
resp, err := engine.GetWorkflowExecutionHistory(context.Background(), req)
s.NoError(err)
s.False(resp.Response.Archived)
s.Len(resp.History, 1)
err = history.Unmarshal(resp.History[0])
s.NoError(err)
event := history.Events[0]
s.Equal(int64(5), event.EventId)
s.Equal(enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED, event.EventType)
attrs := event.GetWorkflowExecutionFailedEventAttributes()
s.Equal("this workflow failed", attrs.Failure.Message)
s.Equal(newRunID, attrs.NewExecutionRunId)
s.Equal(enumspb.RETRY_STATE_IN_PROGRESS, attrs.RetryState)
}
func (s *engineSuite) TestGetWorkflowExecutionHistory_RawHistoryWithTransientDecision() {
we := commonpb.WorkflowExecution{WorkflowId: "wid1", RunId: uuid.New()}
@@ -5629,18 +5741,22 @@ func (s *engineSuite) TestGetWorkflowExecutionHistory_RawHistoryWithTransientDec
}
s.mockNamespaceCache.EXPECT().GetNamespaceID(tests.Namespace).Return(tests.NamespaceID, nil).AnyTimes()
historyBlob1, err := s.mockShard.GetPayloadSerializer().SerializeEvent(
&historypb.HistoryEvent{
EventId: int64(3),
EventType: enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED,
historyBlob1, err := s.mockShard.GetPayloadSerializer().SerializeEvents(
[]*historypb.HistoryEvent{
{
EventId: int64(3),
EventType: enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED,
},
},
enumspb.ENCODING_TYPE_PROTO3,
)
s.NoError(err)
historyBlob2, err := s.mockShard.GetPayloadSerializer().SerializeEvent(
&historypb.HistoryEvent{
EventId: int64(4),
EventType: enumspb.EVENT_TYPE_WORKFLOW_TASK_TIMED_OUT,
historyBlob2, err := s.mockShard.GetPayloadSerializer().SerializeEvents(
[]*historypb.HistoryEvent{
{
EventId: int64(4),
EventType: enumspb.EVENT_TYPE_WORKFLOW_TASK_TIMED_OUT,
},
},
enumspb.ENCODING_TYPE_PROTO3,
)
@@ -5663,13 +5779,11 @@ func (s *engineSuite) TestGetWorkflowExecutionHistory_RawHistoryWithTransientDec
s.NoError(err)
s.False(resp.Response.Archived)
s.Empty(resp.Response.History.Events)
s.Len(resp.Response.RawHistory, 4)
event, err := s.mockShard.GetPayloadSerializer().DeserializeEvent(resp.Response.RawHistory[2])
s.Len(resp.Response.RawHistory, 3)
historyEvents, err := s.mockShard.GetPayloadSerializer().DeserializeEvents(resp.Response.RawHistory[2])
s.NoError(err)
s.Equal(enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED, event.EventType)
event, err = s.mockShard.GetPayloadSerializer().DeserializeEvent(resp.Response.RawHistory[3])
s.NoError(err)
s.Equal(enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED, event.EventType)
s.Equal(enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED, historyEvents[0].EventType)
s.Equal(enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED, historyEvents[1].EventType)
}
func (s *engineSuite) Test_GetWorkflowExecutionRawHistoryV2_FailedOnInvalidWorkflowID() {

View File

@@ -109,7 +109,7 @@ type (
GetReplicationStatus(ctx context.Context, request *historyservice.GetReplicationStatusRequest) (*historyservice.ShardReplicationStatus, error)
UpdateWorkflowExecution(ctx context.Context, request *historyservice.UpdateWorkflowExecutionRequest) (*historyservice.UpdateWorkflowExecutionResponse, error)
PollWorkflowExecutionUpdate(ctx context.Context, request *historyservice.PollWorkflowExecutionUpdateRequest) (*historyservice.PollWorkflowExecutionUpdateResponse, error)
GetWorkflowExecutionHistory(ctx context.Context, request *historyservice.GetWorkflowExecutionHistoryRequest) (*historyservice.GetWorkflowExecutionHistoryResponse, error)
GetWorkflowExecutionHistory(ctx context.Context, request *historyservice.GetWorkflowExecutionHistoryRequest) (*historyservice.GetWorkflowExecutionHistoryResponseWithRaw, error)
GetWorkflowExecutionHistoryReverse(ctx context.Context, request *historyservice.GetWorkflowExecutionHistoryReverseRequest) (*historyservice.GetWorkflowExecutionHistoryReverseResponse, error)
GetWorkflowExecutionRawHistory(ctx context.Context, request *historyservice.GetWorkflowExecutionRawHistoryRequest) (*historyservice.GetWorkflowExecutionRawHistoryResponse, error)
GetWorkflowExecutionRawHistoryV2(ctx context.Context, request *historyservice.GetWorkflowExecutionRawHistoryV2Request) (*historyservice.GetWorkflowExecutionRawHistoryV2Response, error)

View File

@@ -302,10 +302,10 @@ func (mr *MockEngineMockRecorder) GetReplicationTasksIter(ctx, pollingCluster, m
}
// GetWorkflowExecutionHistory mocks base method.
func (m *MockEngine) GetWorkflowExecutionHistory(ctx context.Context, request *historyservice.GetWorkflowExecutionHistoryRequest) (*historyservice.GetWorkflowExecutionHistoryResponse, error) {
func (m *MockEngine) GetWorkflowExecutionHistory(ctx context.Context, request *historyservice.GetWorkflowExecutionHistoryRequest) (*historyservice.GetWorkflowExecutionHistoryResponseWithRaw, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetWorkflowExecutionHistory", ctx, request)
ret0, _ := ret[0].(*historyservice.GetWorkflowExecutionHistoryResponse)
ret0, _ := ret[0].(*historyservice.GetWorkflowExecutionHistoryResponseWithRaw)
ret1, _ := ret[1].(error)
return ret0, ret1
}

View File

@@ -42,6 +42,7 @@ import (
"go.temporal.io/server/common/persistence"
"go.temporal.io/server/common/persistence/visibility/manager"
"go.temporal.io/server/common/resource"
"go.temporal.io/server/common/searchattribute"
"go.temporal.io/server/common/testing/testhooks"
"go.temporal.io/server/common/tqid"
"go.temporal.io/server/service/worker/deployment"
@@ -92,6 +93,8 @@ func NewHandler(
visibilityManager manager.VisibilityManager,
nexusEndpointManager persistence.NexusEndpointManager,
testHooks testhooks.TestHooks,
saProvider searchattribute.Provider,
saMapperProvider searchattribute.MapperProvider,
) *Handler {
handler := &Handler{
config: config,
@@ -116,6 +119,8 @@ func NewHandler(
visibilityManager,
nexusEndpointManager,
testHooks,
saProvider,
saMapperProvider,
),
namespaceRegistry: namespaceRegistry,
}

View File

@@ -69,6 +69,7 @@ import (
"go.temporal.io/server/common/persistence/visibility/manager"
"go.temporal.io/server/common/primitives/timestamp"
"go.temporal.io/server/common/resource"
"go.temporal.io/server/common/searchattribute"
serviceerrors "go.temporal.io/server/common/serviceerror"
"go.temporal.io/server/common/stream_batcher"
"go.temporal.io/server/common/tasktoken"
@@ -76,6 +77,7 @@ import (
"go.temporal.io/server/common/tqid"
"go.temporal.io/server/common/util"
"go.temporal.io/server/common/worker_versioning"
"go.temporal.io/server/service/history/api"
"go.temporal.io/server/service/worker/deployment"
"go.temporal.io/server/service/worker/workerdeployment"
"google.golang.org/protobuf/types/known/durationpb"
@@ -145,11 +147,14 @@ type (
visibilityManager manager.VisibilityManager
nexusEndpointClient *nexusEndpointClient
nexusEndpointsOwnershipLostCh chan struct{}
saMapperProvider searchattribute.MapperProvider
saProvider searchattribute.Provider
metricsHandler metrics.Handler
partitionsLock sync.RWMutex // locks mutation of partitions
partitions map[tqid.PartitionKey]taskQueuePartitionManager
gaugeMetrics gaugeMetrics // per-namespace task queue counters
config *Config
versionChecker headers.VersionChecker
testHooks testhooks.TestHooks
// queryResults maps query TaskID (which is a UUID generated in QueryWorkflow() call) to a channel
// that QueryWorkflow() will block on. The channel is unblocked either by worker sending response through
@@ -221,6 +226,8 @@ func NewEngine(
visibilityManager manager.VisibilityManager,
nexusEndpointManager persistence.NexusEndpointManager,
testHooks testhooks.TestHooks,
saProvider searchattribute.Provider,
saMapperProvider searchattribute.MapperProvider,
) Engine {
scopedMetricsHandler := metricsHandler.WithTags(metrics.OperationTag(metrics.MatchingEngineScope))
e := &matchingEngineImpl{
@@ -243,6 +250,8 @@ func NewEngine(
visibilityManager: visibilityManager,
nexusEndpointClient: newEndpointClient(config.NexusEndpointsRefreshInterval, nexusEndpointManager),
nexusEndpointsOwnershipLostCh: make(chan struct{}),
saProvider: saProvider,
saMapperProvider: saMapperProvider,
metricsHandler: scopedMetricsHandler,
partitions: make(map[tqid.PartitionKey]taskQueuePartitionManager),
gaugeMetrics: gaugeMetrics{
@@ -252,6 +261,7 @@ func NewEngine(
loadedPhysicalTaskQueueCount: make(map[taskQueueCounterKey]int),
},
config: config,
versionChecker: headers.NewDefaultVersionChecker(),
testHooks: testHooks,
queryResults: collection.NewSyncMap[string, chan *queryResult](),
nexusResults: collection.NewSyncMap[string, chan *nexusResult](),
@@ -766,6 +776,23 @@ func (e *matchingEngineImpl) getHistoryForQueryTask(
return nil, nil, err
}
// History service can send history events in response.History.Events. In that case use that directly.
// This happens when history.sendRawHistoryBetweenInternalServices is enabled.
ns, err := e.namespaceRegistry.GetNamespaceName(nsID)
if err != nil {
return nil, nil, err
}
err = api.ProcessInternalRawHistory(
ctx,
e.saProvider,
e.saMapperProvider,
resp,
e.visibilityManager,
e.versionChecker,
ns,
false,
)
hist := resp.GetResponse().GetHistory()
if resp.GetResponse().GetRawHistory() != nil {
historyEvents := make([]*historypb.HistoryEvent, 0, maxPageSize)

View File

@@ -209,6 +209,9 @@ func (s *FunctionalTestBase) SetupSuiteWithCluster(clusterConfigFile string, opt
if s.testClusterConfig.ESConfig != nil {
s.testClusterConfig.DynamicConfigOverrides[dynamicconfig.SecondaryVisibilityWritingMode.Key()] = visibility.SecondaryVisibilityWritingModeDual
}
// Enable raw history for functional tests.
// TODO (prathyush): remove this after setting it to true by default.
s.testClusterConfig.DynamicConfigOverrides[dynamicconfig.SendRawHistoryBetweenInternalServices.Key()] = true
s.testClusterConfig.ServiceFxOptions = params.ServiceOptions
s.testClusterConfig.EnableMetricsCapture = true

View File

@@ -109,6 +109,8 @@ func (s *xdcBaseSuite) setupSuite(opts ...testcore.TestClusterOption) {
s.dynamicConfigOverrides[dynamicconfig.ClusterMetadataRefreshInterval.Key()] = time.Second * 5
s.dynamicConfigOverrides[dynamicconfig.NamespaceCacheRefreshInterval.Key()] = testcore.NamespaceCacheRefreshInterval
s.dynamicConfigOverrides[dynamicconfig.EnableTransitionHistory.Key()] = s.enableTransitionHistory
// TODO (prathyush): remove this after setting it to true by default.
s.dynamicConfigOverrides[dynamicconfig.SendRawHistoryBetweenInternalServices.Key()] = true
fileName := "../testdata/xdc_clusters.yaml"
if testcore.TestFlags.TestClusterConfigFile != "" {