fix(permissions): chunked synchronization of role permission events (#9403)

# Which Problems Are Solved

Setup fails to push all role permission events when running Zitadel with
CockroachDB. `TransactionRetryError`s were visible in logs which finally
times out the setup job with `timeout: context deadline exceeded`

# How the Problems Are Solved

As suggested in the [Cockroach documentation](timeout: context deadline
exceeded), _"break down larger transactions"_. The commands to be pushed
for the role permissions are chunked in 50 events per push. This
chunking is only done with CockroachDB.

# Additional Changes

- gci run fixed some unrelated imports
- access to `command.Commands` for the setup job, so we can reuse the
sync logic.

# Additional Context

Closes #9293

---------

Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
This commit is contained in:
Tim Möhlmann
2025-02-26 18:06:50 +02:00
committed by GitHub
parent 77499ce603
commit e670b9126c
13 changed files with 461 additions and 169 deletions

View File

@@ -218,6 +218,33 @@ func (c *Commands) pushAppendAndReduce(ctx context.Context, object AppendReducer
return AppendAndReduce(object, events...)
}
// pushChunked pushes the commands in chunks of size to the eventstore.
// This can be used to reduce the amount of events in a single transaction.
// When an error occurs, the events that have been pushed so far will be returned.
//
// Warning: chunks are pushed in separate transactions.
// Successful pushes will not be rolled back if a later chunk fails.
// Only use this function when the caller is able to handle partial success
// and is able to consolidate the state on errors.
func (c *Commands) pushChunked(ctx context.Context, size uint16, cmds ...eventstore.Command) (_ []eventstore.Event, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
events := make([]eventstore.Event, 0, len(cmds))
for i := 0; i < len(cmds); i += int(size) {
end := i + int(size)
if end > len(cmds) {
end = len(cmds)
}
chunk, err := c.eventstore.Push(ctx, cmds[i:end]...)
if err != nil {
return events, err
}
events = append(events, chunk...)
}
return events, nil
}
type AppendReducerDetails interface {
AppendEvents(...eventstore.Event)
// TODO: Why is it allowed to return an error here?