Skip to content

Implement size estimation function for structs used in history event cache #6714

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

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

timl3136
Copy link
Member

@timl3136 timl3136 commented Mar 6, 2025

Still in drafting mode due to git commit is combined with another PR, main change is in the shared.go

What changed?
Implement Size() for structs referred in history event cache

Why?
We want to modernize existing cadence common cache implement to a bytes-based system. That means we need to have a method to measure each entry (which is currently accepting any generic interface). We found the "Reflect" package provides a measuring function but runtime is too slow to be used in cache operations. Therefore, we will require all usages to implement the Size() function in their cache logic if they want to migrate to the new bytes-based system.

In order to seamless transition from the current cache system with an entry-based model, the implementation and rollout will be done in following phases:

Define the Sizeable interface
Implement Sizeable for cadence-history service
a. Implement Size() for ExecutionCache
b. Implement Size() for EventCache <-- This PR
Implement bytes-based cache system
Enable new cache system for usage in cadence-history service
Implement and enable new cache system for the remaining usages

How did you test it?
Unit tests

Potential risks
No risk since this PR only adds read-only function that is not used.

Release notes

Documentation Changes

size += 8
}

if v.RetryPolicy != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RetryPolicy can also implement Size(). It shouldn't be hardcoded to 24. It has a string list field

if v.RetryPolicy != nil {
size += 24
}
if v.Header != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Header size is not fixed to 16. Let's calculate it on the fly based on map content

Comment on lines +697 to +703
size += 24
if v.WorkflowExecution.WorkflowID != "" {
size += uint64(16 + len(v.WorkflowExecution.WorkflowID))
}
if v.WorkflowExecution.RunID != "" {
size += uint64(16 + len(v.WorkflowExecution.RunID))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are doing this calculation on WorkflowExecution type field in multiple places. Just expose Size() on that type and call it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's make sure we avoid redundant calculations like this for other typed fields

Comment on lines +1924 to +1926
if v.Cause != nil {
size += 8
}
Copy link
Member

@taylanisikdemir taylanisikdemir Mar 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can have a util function which checks type of the passed argument and returns size if it's a known type.
e.g.

func SizeEstimate(in any) uint64 {
  switch in.(type) {
   case int64: return 8, true
   case string: return 16 + len(in)
    ...
    }
  return 0 
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can eliminate all the hardcoded numbers spread all over. also simplifies the caller side by eliminating != nil checks. you can do that check in the helper function

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect the unsafe.Sizeof() package (credit to Ignat for pointing that out) might help some calcualtions here.

@@ -3060,6 +3684,185 @@ func (v *HistoryEvent) GetUpsertWorkflowSearchAttributesEventAttributes() (o *Up
return
}

// Size is an internal method to get the estimated size of the event
func (v *HistoryEvent) Size() uint64 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should somehow make this future proof so when someone introduces a new attribute the corresponding unit test fails because of size mismatch.

@@ -63,6 +63,21 @@ func (v *ActivityTaskCancelRequestedEventAttributes) GetActivityID() (o string)
return
}

// Size returns the approximate memory used in bytes
func (v *ActivityTaskCancelRequestedEventAttributes) Size() uint64 {
if v == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A question: Can we generate this code (with gowrap or something else) for all the structs instead of writing it manually?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants