-
Notifications
You must be signed in to change notification settings - Fork 6
arbitrary shard counts #177
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
Conversation
proxy/adminservice.go
Outdated
| reportStreamValue: reportStreamValue, | ||
| shardCountConfig: shardCountConfig, | ||
| lcmParameters: lcmParameters, | ||
| clusterConnection: clusterConnection, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ClusterConnection is a huge context to pass around (MuxManager, inbound and outbound servers and clients, now ShardManager and intraProxyManager and Send/Ack channels). Can this code be written without this back-reference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored the structure and removed the use of ClusterConnection
develop/config/nginx.conf
Outdated
| @@ -0,0 +1,43 @@ | |||
| worker_processes 1; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we using nginx in this change? I don't see it being started in main.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only for local testing for multi-proxy case. I can remove it.
Move channel management and intra-proxy routing from ClusterConnection to ShardManager.
| uses: azure/[email protected] | ||
| with: | ||
| version: v3.17.3 | ||
| version: v3.19.4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this for? do the change in a separate PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to fix the helm error that I didn't see in main branch:
Error: plugin is installed but unusable: failed to load plugin at "/home/runner/.local/share/helm/plugins/helm-unittest.git/plugin.yaml": error unmarshaling JSON: while decoding JSON: json: unknown field "platformHooks"
Error: Process completed with exit code 1.```
| } | ||
|
|
||
| // Add debug endpoint handler | ||
| http.HandleFunc("/debug/connections", func(w http.ResponseWriter, r *http.Request) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice stuff but prefer having a separate PR for this
| # shardCount: | ||
| # mode: "lcm" | ||
| # localShardCount: 3 | ||
| # remoteShardCount: 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we remove comment code? we can create separate config if needed.
| history.shardUpdateMinInterval: | ||
| - value: 1s | ||
| history.ReplicationStreamSendEmptyTaskDuration: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what are these config for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the sender side keep alive when there is no replication task. This will keep the replication stream alive and make sure receiver gets latest watermark.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that a new mandatory configuration that clients will need to enable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clients do not need to explicit enable it. The default value in OSS is 1 minute.
| ) error { | ||
|
|
||
| i.logger.Debug("InterceptStream", tag.NewAnyTag("method", info.FullMethod)) | ||
| // Skip translation for intra-proxy streams |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Translation already happen at inbound/outbound server. The intraProxy stream will still go through inbound/outbound server. The translation is skipped here to avoid multiple translations.
| if s.proxyB != nil { | ||
| s.proxyB.Stop() | ||
| } | ||
| } else { | ||
| if s.loadBalancerA != nil { | ||
| s.loadBalancerA.Stop() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be if-else if we're already nil-checking everything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated.
| Upstream *Upstream | ||
| } | ||
|
|
||
| TCPProxy struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add some comments describing what this proxy is for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added.
proxy/admin_stream_transfer.go
Outdated
| for i, task := range attr.Messages.ReplicationTasks { | ||
| msg = append(msg, fmt.Sprintf("[%d]: %v", i, task.SourceTaskId)) | ||
| } | ||
| f.logger.Info(fmt.Sprintf("forwarding ReplicationMessages: exclusive %v, tasks: %v", attr.Messages.ExclusiveHighWatermark, strings.Join(msg, ", "))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a large data dump. How often will this log? Do we want to put it behind a ticker?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved to debug level.
proxy/adminservice.go
Outdated
| forwarder := newStreamForwarder( | ||
| s.adminClient, | ||
| targetStreamServer, | ||
| streamServer, | ||
| targetMetadata, | ||
| sourceClusterShardID, | ||
| targetClusterShardID, | ||
| s.metricLabelValues, | ||
| logger, | ||
| ) | ||
| err = forwarder.Run() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Do not try to transfer EOF from the source here. Just returning "nil" is sufficient to terminate the stream | ||
| // to the client. | ||
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the other half of "if s.shardCountConfig.Mode == config.ShardCountLCM" above. Let's put the body of that if statement into a method to match streamRouting and streamIntraProxyRouting, and then we can consolidate the dispatch into a single obvious place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated and move these into another file.
proxy/adminservice.go
Outdated
| return nil | ||
| } | ||
|
|
||
| func (s *adminServiceProxyServer) streamIntraProxyRouting( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For a future PR: The stream routing logic has outgrown this adminservice file, which is supposed to be the adminservice handler. We should put streamIntraProxyRouting, streamRouting,streamLCM, and streamDirect together into a separate file.
proxy/adminservice_test.go
Outdated
| resp, err := server.DescribeCluster(ctx, req) | ||
| s.NoError(err) | ||
| s.Equal(c.expResp, resp) | ||
| s.Equal(c.expResp.FailoverVersionIncrement, resp.FailoverVersionIncrement) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leave a comment that reminds us what parts of the response aren't supposed to match
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated with proto.Equal()
proxy/cluster_connection.go
Outdated
| targetShardCount int32 | ||
| logger log.Logger | ||
|
|
||
| clusterConnection *ClusterConnection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is only used to get a reference to the shardManager. Can we just put that here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated.
| "github.com/temporalio/s2s-proxy/transport/mux" | ||
| "github.com/temporalio/s2s-proxy/transport/mux/session" | ||
| ) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A comment here describing that this is the handler for the proxy debug endpoint would be helpful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added.
| // lastWatermark tracks the last watermark received from source shard for late-registering target shards | ||
| lastWatermarkMu sync.RWMutex | ||
| lastWatermark *replicationv1.WorkflowReplicationMessages |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we replace this with atomic.Pointer? It should be both higher-performance and simpler to use
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revert lastWatermark atomic.Pointer change due to test failure.
We can redo the change after oss ReplicationStreamSendEmptyTaskDuration is available.
proxy/proxy.go
Outdated
| RoutedAck struct { | ||
| TargetShard history.ClusterShardID | ||
| Req *adminservice.StreamWorkflowReplicationMessagesRequest | ||
| } | ||
|
|
||
| // RoutedMessage wraps a replication response with originating client shard info | ||
| RoutedMessage struct { | ||
| SourceShard history.ClusterShardID | ||
| Resp *adminservice.StreamWorkflowReplicationMessagesResponse | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These seem orphaned. Do they belong in another file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved.
| shutdownChan := channel.NewShutdownOnce() | ||
| go func() { | ||
| if err := sender.Run(streamServer, shutdownChan); err != nil { | ||
| logger.Error("intraProxyStreamSender.Run error", tag.Error(err)) | ||
| } | ||
| }() | ||
| <-shutdownChan.Channel() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two problems:
- This shutdownChan doesn't escape anywhere, so this is equivalent to just calling
sender.Run() - A given clusterConnection and all its resources may be closed unilaterally from a config endpoint. Instead of creating a new shutdown channel, pass in the
lifetimecontext when this handler is built duringClusterConnectionand wait on that.
I think passing the lifetime through to sender.Run in place of shutdownChan should accomplish the need for shutdown in that function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The replication stream has different lifetime, not same as clusterConnection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. But, when the clusterConnection is terminated, the replication streams should close. There's a hierarchy of lifetimes:
Proxy -> []ClusterConnection -> MuxManager -> Inbound GRPC Servers -> []inbound AdminService Streams
-> Outbound GRPC server -> []outbound AdminService Streams
-> StreamManager
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wired lifetime.
| shutdownChan := channel.NewShutdownOnce() | ||
| wg := sync.WaitGroup{} | ||
| wg.Add(2) | ||
| go func() { | ||
| defer wg.Done() | ||
| proxyStreamSender.Run(streamServer, shutdownChan) | ||
| }() | ||
| go func() { | ||
| defer wg.Done() | ||
| proxyStreamReceiver.Run(shutdownChan) | ||
| }() | ||
| wg.Wait() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same point as above: Pass in the lifetime context instead of creating a new shutdownChan here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above.
We can redo the change after oss ReplicationStreamSendEmptyTaskDuration is available.
| getLCMParameters := func(shardCountConfig config.ShardCountConfig, inverse bool) LCMParameters { | ||
| if shardCountConfig.Mode != config.ShardCountLCM { | ||
| return LCMParameters{} | ||
| } | ||
| lcm := common.LCM(shardCountConfig.LocalShardCount, shardCountConfig.RemoteShardCount) | ||
| if inverse { | ||
| return LCMParameters{ | ||
| LCM: lcm, | ||
| TargetShardCount: shardCountConfig.LocalShardCount, | ||
| } | ||
| } | ||
| return LCMParameters{ | ||
| LCM: lcm, | ||
| TargetShardCount: shardCountConfig.RemoteShardCount, | ||
| } | ||
| } | ||
| getRoutingParameters := func(shardCountConfig config.ShardCountConfig, inverse bool, directionLabel string) RoutingParameters { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these need to be functions? It seems like you could just construct LCMParameters and RoutingParameters here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see they're called twice, but they each embed an if-else which has custom code for each branch. I'd recommend compressing this into a simple struct definition embedded into the serverConfiguration structs below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These functions can later be extracted (like xxTranslations) into separated file to make this function clean.
| ) { | ||
| // Terminate any previous local receiver for this shard | ||
| if r.shardManager != nil { | ||
| r.shardManager.TerminatePreviousLocalReceiver(r.sourceShardID, r.logger) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For future PR: Is it possible to avoid the need for this using context lifetimes and/or locking?
| } | ||
|
|
||
| // ShardManager manages distributed shard ownership across proxy instances | ||
| ShardManager interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still reading through this. My initial impression is that this is a huge interface. Do we use all of these methods? Is there a higher-level interface we could expose that would remove ~50% of these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated.
Makefile
Outdated
| # Disable cgo by default. | ||
| CGO_ENABLED ?= 0 | ||
| TEST_ARG ?= -race -timeout=5m -tags test_dep | ||
| TEST_ARG ?= -race -timeout=15m -tags test_dep -count=1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do the tests really take 15 minutes to run? I think we need to change or separate them if this is the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test took 11m to finish when replication tests were run in sequence for easy debugging. Updated the tests to run in parallel.
temporal-nick
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Current version is clean enough to continue iterating on in the main branch. Approving!
What was changed
Support replication between Temporal servers with arbitrary shard counts.
Why?
Checklist
Closes
How was this tested: