perf(milestones): refactor (#8788)
Some checks are pending
ZITADEL CI/CD / core (push) Waiting to run
ZITADEL CI/CD / console (push) Waiting to run
ZITADEL CI/CD / version (push) Waiting to run
ZITADEL CI/CD / compile (push) Blocked by required conditions
ZITADEL CI/CD / core-unit-test (push) Blocked by required conditions
ZITADEL CI/CD / core-integration-test (push) Blocked by required conditions
ZITADEL CI/CD / lint (push) Blocked by required conditions
ZITADEL CI/CD / container (push) Blocked by required conditions
ZITADEL CI/CD / e2e (push) Blocked by required conditions
ZITADEL CI/CD / release (push) Blocked by required conditions
Code Scanning / CodeQL-Build (go) (push) Waiting to run
Code Scanning / CodeQL-Build (javascript) (push) Waiting to run

# Which Problems Are Solved

Milestones used existing events from a number of aggregates. OIDC
session is one of them. We noticed in load-tests that the reduction of
the oidc_session.added event into the milestone projection is a costly
business with payload based conditionals. A milestone is reached once,
but even then we remain subscribed to the OIDC events. This requires the
projections.current_states to be updated continuously.


# How the Problems Are Solved

The milestone creation is refactored to use dedicated events instead.
The command side decides when a milestone is reached and creates the
reached event once for each milestone when required.

# Additional Changes

In order to prevent reached milestones being created twice, a migration
script is provided. When the old `projections.milestones` table exist,
the state is read from there and `v2` milestone aggregate events are
created, with the original reached and pushed dates.

# Additional Context

- Closes https://github.com/zitadel/zitadel/issues/8800
This commit is contained in:
Tim Möhlmann
2024-10-28 09:29:34 +01:00
committed by GitHub
parent 54f1c0bc50
commit 32bad3feb3
46 changed files with 1612 additions and 756 deletions

View File

@@ -2,23 +2,88 @@ package milestone
import (
"context"
"time"
"github.com/zitadel/zitadel/internal/eventstore"
)
//go:generate enumer -type Type -json -linecomment -transform=snake
type Type int
const (
eventTypePrefix = eventstore.EventType("milestone.")
PushedEventType = eventTypePrefix + "pushed"
InstanceCreated Type = iota
AuthenticationSucceededOnInstance
ProjectCreated
ApplicationCreated
AuthenticationSucceededOnApplication
InstanceDeleted
)
var _ eventstore.Command = (*PushedEvent)(nil)
const (
eventTypePrefix = "milestone."
ReachedEventType = eventTypePrefix + "reached"
PushedEventType = eventTypePrefix + "pushed"
)
type ReachedEvent struct {
*eventstore.BaseEvent `json:"-"`
MilestoneType Type `json:"type"`
ReachedDate *time.Time `json:"reachedDate,omitempty"` // Defaults to [eventstore.BaseEvent.Creation] when empty
}
// Payload implements eventstore.Command.
func (e *ReachedEvent) Payload() any {
return e
}
func (e *ReachedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
}
func (e *ReachedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
e.BaseEvent = b
}
func (e *ReachedEvent) GetReachedDate() time.Time {
if e.ReachedDate != nil {
return *e.ReachedDate
}
return e.Creation
}
func NewReachedEvent(
ctx context.Context,
aggregate *Aggregate,
typ Type,
) *ReachedEvent {
return NewReachedEventWithDate(ctx, aggregate, typ, nil)
}
// NewReachedEventWithDate creates a [ReachedEvent] with a fixed Reached Date.
func NewReachedEventWithDate(
ctx context.Context,
aggregate *Aggregate,
typ Type,
reachedDate *time.Time,
) *ReachedEvent {
return &ReachedEvent{
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
&aggregate.Aggregate,
ReachedEventType,
),
MilestoneType: typ,
ReachedDate: reachedDate,
}
}
type PushedEvent struct {
*eventstore.BaseEvent `json:"-"`
MilestoneType Type `json:"type"`
ExternalDomain string `json:"externalDomain"`
PrimaryDomain string `json:"primaryDomain"`
Endpoints []string `json:"endpoints"`
MilestoneType Type `json:"type"`
ExternalDomain string `json:"externalDomain"`
PrimaryDomain string `json:"primaryDomain"`
Endpoints []string `json:"endpoints"`
PushedDate *time.Time `json:"pushedDate,omitempty"` // Defaults to [eventstore.BaseEvent.Creation] when empty
}
// Payload implements eventstore.Command.
@@ -34,14 +99,31 @@ func (p *PushedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
p.BaseEvent = b
}
var PushedEventMapper = eventstore.GenericEventMapper[PushedEvent]
func (e *PushedEvent) GetPushedDate() time.Time {
if e.PushedDate != nil {
return *e.PushedDate
}
return e.Creation
}
func NewPushedEvent(
ctx context.Context,
aggregate *Aggregate,
msType Type,
typ Type,
endpoints []string,
externalDomain, primaryDomain string,
externalDomain string,
) *PushedEvent {
return NewPushedEventWithDate(ctx, aggregate, typ, endpoints, externalDomain, nil)
}
// NewPushedEventWithDate creates a [PushedEvent] with a fixed Pushed Date.
func NewPushedEventWithDate(
ctx context.Context,
aggregate *Aggregate,
typ Type,
endpoints []string,
externalDomain string,
pushedDate *time.Time,
) *PushedEvent {
return &PushedEvent{
BaseEvent: eventstore.NewBaseEventForPush(
@@ -49,9 +131,9 @@ func NewPushedEvent(
&aggregate.Aggregate,
PushedEventType,
),
MilestoneType: msType,
MilestoneType: typ,
Endpoints: endpoints,
ExternalDomain: externalDomain,
PrimaryDomain: primaryDomain,
PushedDate: pushedDate,
}
}