diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0b0a24239..0c8bde005 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -295,7 +295,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - migrate-strategy: ["latest", "penultimate"] + migrate-strategy: ["latest"] rabbitmq-enabled: ["true"] pg-version: ["15-alpine"] @@ -331,7 +331,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - migrate-strategy: ["latest", "penultimate"] + migrate-strategy: ["latest"] rabbitmq-enabled: ["true"] pg-version: ["15-alpine"] diff --git a/api-contracts/events/events.proto b/api-contracts/events/events.proto index 0b6643555..e1d10b21b 100644 --- a/api-contracts/events/events.proto +++ b/api-contracts/events/events.proto @@ -97,6 +97,8 @@ message PushEventRequest { // metadata for the event optional string additionalMetadata = 4; + + optional int32 priority = 5; } message ReplayEventRequest { diff --git a/api-contracts/openapi/components/schemas/event.yaml b/api-contracts/openapi/components/schemas/event.yaml index 39602fb49..fc230b4ea 100644 --- a/api-contracts/openapi/components/schemas/event.yaml +++ b/api-contracts/openapi/components/schemas/event.yaml @@ -54,6 +54,10 @@ CreateEventRequest: additionalMetadata: type: object description: Additional metadata for the event. + priority: + type: integer + description: The priority of the event. + format: int32 required: - key - data diff --git a/api/v1/server/handlers/events/bulk_create.go b/api/v1/server/handlers/events/bulk_create.go index d4fbc5069..8fef4e7b7 100644 --- a/api/v1/server/handlers/events/bulk_create.go +++ b/api/v1/server/handlers/events/bulk_create.go @@ -42,6 +42,7 @@ func (t *EventService) EventCreateBulk(ctx echo.Context, request gen.EventCreate Key: event.Key, Data: dataBytes, AdditionalMetadata: additionalMetadata, + Priority: event.Priority, } } events, err := t.config.Ingestor.BulkIngestEvent(ctx.Request().Context(), tenant, eventOpts) diff --git a/api/v1/server/handlers/events/create.go b/api/v1/server/handlers/events/create.go index 40c725b03..5dcac0b80 100644 --- a/api/v1/server/handlers/events/create.go +++ b/api/v1/server/handlers/events/create.go @@ -32,7 +32,7 @@ func (t *EventService) EventCreate(ctx echo.Context, request gen.EventCreateRequ } } - newEvent, err := t.config.Ingestor.IngestEvent(ctx.Request().Context(), tenant, request.Body.Key, dataBytes, additionalMetadata) + newEvent, err := t.config.Ingestor.IngestEvent(ctx.Request().Context(), tenant, request.Body.Key, dataBytes, additionalMetadata, request.Body.Priority) if err != nil { if err == metered.ErrResourceExhausted { diff --git a/api/v1/server/handlers/ingestors/sns.go b/api/v1/server/handlers/ingestors/sns.go index 882b5eb4a..e623aa317 100644 --- a/api/v1/server/handlers/ingestors/sns.go +++ b/api/v1/server/handlers/ingestors/sns.go @@ -64,7 +64,7 @@ func (i *IngestorsService) SnsUpdate(ctx echo.Context, req gen.SnsUpdateRequestO return nil, err } default: - _, err := i.config.Ingestor.IngestEvent(ctx.Request().Context(), tenant, req.Event, body, nil) + _, err := i.config.Ingestor.IngestEvent(ctx.Request().Context(), tenant, req.Event, body, nil, nil) if err != nil { return nil, err diff --git a/api/v1/server/oas/gen/openapi.gen.go b/api/v1/server/oas/gen/openapi.gen.go index 2184f23ec..6484a2d21 100644 --- a/api/v1/server/oas/gen/openapi.gen.go +++ b/api/v1/server/oas/gen/openapi.gen.go @@ -426,6 +426,9 @@ type CreateEventRequest struct { // Key The key for the event. Key string `json:"key"` + + // Priority The priority of the event. + Priority *int32 `json:"priority,omitempty"` } // CreateSNSIntegrationRequest defines model for CreateSNSIntegrationRequest. @@ -12933,225 +12936,226 @@ var swaggerSpec = []string{ "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", + "+f8FAAD//w1v4W5pegIA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/cmd/hatchet-migrate/migrate/migrations/20250428175026_v1_0_17.sql b/cmd/hatchet-migrate/migrate/migrations/20250428175026_v1_0_17.sql new file mode 100644 index 000000000..211f74aa1 --- /dev/null +++ b/cmd/hatchet-migrate/migrate/migrations/20250428175026_v1_0_17.sql @@ -0,0 +1,36 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE v1_events_olap ( + tenant_id UUID NOT NULL, + id UUID NOT NULL, + seen_at TIMESTAMPTZ NOT NULL, + key TEXT NOT NULL, + payload JSONB NOT NULL, + additional_metadata JSONB, + + PRIMARY KEY (tenant_id, id, seen_at) +) PARTITION BY RANGE(seen_at); + +CREATE INDEX v1_events_olap_key_idx ON v1_events_olap (tenant_id, key); + +CREATE TABLE v1_event_to_run_olap ( + run_id BIGINT NOT NULL, + run_inserted_at TIMESTAMPTZ NOT NULL, + event_id UUID NOT NULL, + event_seen_at TIMESTAMPTZ NOT NULL, + + PRIMARY KEY (event_id, event_seen_at, run_id, run_inserted_at) +) PARTITION BY RANGE(event_seen_at); + + +SELECT create_v1_range_partition('v1_events_olap', DATE 'today'); +SELECT create_v1_range_partition('v1_event_to_run_olap', DATE 'today'); + + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE v1_event_to_run_olap; +DROP TABLE v1_events_olap; +-- +goose StatementEnd diff --git a/frontend/app/src/lib/api/generated/data-contracts.ts b/frontend/app/src/lib/api/generated/data-contracts.ts index c902c4fd4..0691c6164 100644 --- a/frontend/app/src/lib/api/generated/data-contracts.ts +++ b/frontend/app/src/lib/api/generated/data-contracts.ts @@ -1137,6 +1137,11 @@ export interface CreateEventRequest { data: object; /** Additional metadata for the event. */ additionalMetadata?: object; + /** + * The priority of the event. + * @format int32 + */ + priority?: number; } export interface BulkCreateEventRequest { diff --git a/internal/services/controllers/v1/olap/controller.go b/internal/services/controllers/v1/olap/controller.go index 9ad2aebf7..160baebeb 100644 --- a/internal/services/controllers/v1/olap/controller.go +++ b/internal/services/controllers/v1/olap/controller.go @@ -292,6 +292,8 @@ func (tc *OLAPControllerImpl) handleBufferedMsgs(tenantId, msgId string, payload return tc.handleCreatedDAG(context.Background(), tenantId, payloads) case "create-monitoring-event": return tc.handleCreateMonitoringEvent(context.Background(), tenantId, payloads) + case "created-event-trigger": + return tc.handleCreateEventTriggers(context.Background(), tenantId, payloads) } return fmt.Errorf("unknown message id: %s", msgId) @@ -332,6 +334,50 @@ func (tc *OLAPControllerImpl) handleCreatedDAG(ctx context.Context, tenantId str return tc.repo.OLAP().CreateDAGs(ctx, tenantId, createDAGOpts) } +func (tc *OLAPControllerImpl) handleCreateEventTriggers(ctx context.Context, tenantId string, payloads [][]byte) error { + msgs := msgqueue.JSONConvert[tasktypes.CreatedEventTriggerPayload](payloads) + + seenEventKeysSet := make(map[string]bool) + + bulkCreateEventParams := make([]sqlcv1.BulkCreateEventsParams, 0) + bulkCreateTriggersParams := make([]sqlcv1.BulkCreateEventTriggersParams, 0) + + for _, msg := range msgs { + for _, payload := range msg.Payloads { + if payload.MaybeRunId != nil && payload.MaybeRunInsertedAt != nil { + bulkCreateTriggersParams = append(bulkCreateTriggersParams, sqlcv1.BulkCreateEventTriggersParams{ + RunID: *payload.MaybeRunId, + RunInsertedAt: sqlchelpers.TimestamptzFromTime(*payload.MaybeRunInsertedAt), + EventID: sqlchelpers.UUIDFromStr(payload.EventId), + EventSeenAt: sqlchelpers.TimestamptzFromTime(payload.EventSeenAt), + }) + } + + _, eventAlreadySeen := seenEventKeysSet[payload.EventId] + + if eventAlreadySeen { + continue + } + + seenEventKeysSet[payload.EventId] = true + bulkCreateEventParams = append(bulkCreateEventParams, sqlcv1.BulkCreateEventsParams{ + TenantID: sqlchelpers.UUIDFromStr(tenantId), + ID: sqlchelpers.UUIDFromStr(payload.EventId), + SeenAt: sqlchelpers.TimestamptzFromTime(payload.EventSeenAt), + Key: payload.EventKey, + Payload: payload.EventPayload, + AdditionalMetadata: payload.EventAdditionalMetadata, + }) + } + } + + return tc.repo.OLAP().BulkCreateEventsAndTriggers( + ctx, + bulkCreateEventParams, + bulkCreateTriggersParams, + ) +} + // handleCreateMonitoringEvent is responsible for sending a group of monitoring events to the OLAP repository func (tc *OLAPControllerImpl) handleCreateMonitoringEvent(ctx context.Context, tenantId string, payloads [][]byte) error { msgs := msgqueue.JSONConvert[tasktypes.CreateMonitoringEventPayload](payloads) diff --git a/internal/services/controllers/v1/task/controller.go b/internal/services/controllers/v1/task/controller.go index dde747247..0891e7724 100644 --- a/internal/services/controllers/v1/task/controller.go +++ b/internal/services/controllers/v1/task/controller.go @@ -809,17 +809,71 @@ func (tc *TasksControllerImpl) handleProcessUserEvents(ctx context.Context, tena // handleProcessEventTrigger is responsible for inserting tasks into the database based on event triggers. func (tc *TasksControllerImpl) handleProcessUserEventTrigger(ctx context.Context, tenantId string, msgs []*tasktypes.UserEventTaskPayload) error { opts := make([]v1.EventTriggerOpts, 0, len(msgs)) + eventIdToOpts := make(map[string]v1.EventTriggerOpts) for _, msg := range msgs { - opts = append(opts, v1.EventTriggerOpts{ + opt := v1.EventTriggerOpts{ EventId: msg.EventId, Key: msg.EventKey, Data: msg.EventData, AdditionalMetadata: msg.EventAdditionalMetadata, - }) + Priority: msg.EventPriority, + } + + opts = append(opts, opt) + + eventIdToOpts[msg.EventId] = opt } - tasks, dags, err := tc.repov1.Triggers().TriggerFromEvents(ctx, tenantId, opts) + result, err := tc.repov1.Triggers().TriggerFromEvents(ctx, tenantId, opts) + + if err != nil { + return fmt.Errorf("could not trigger tasks from events: %w", err) + } + + eventTriggerOpts := make([]tasktypes.CreatedEventTriggerPayloadSingleton, 0) + + // FIXME: Should `SeenAt` be set on the SDK when the event is created? + eventSeenAt := time.Now() + + for eventId, runs := range result.EventIdToRuns { + opts := eventIdToOpts[eventId] + + if len(runs) == 0 { + eventTriggerOpts = append(eventTriggerOpts, tasktypes.CreatedEventTriggerPayloadSingleton{ + EventSeenAt: eventSeenAt, + EventKey: opts.Key, + EventId: opts.EventId, + EventPayload: opts.Data, + EventAdditionalMetadata: opts.AdditionalMetadata, + }) + } else { + for _, run := range runs { + eventTriggerOpts = append(eventTriggerOpts, tasktypes.CreatedEventTriggerPayloadSingleton{ + MaybeRunId: &run.Id, + MaybeRunInsertedAt: &run.InsertedAt, + EventSeenAt: eventSeenAt, + EventKey: opts.Key, + EventId: opts.EventId, + EventPayload: opts.Data, + EventAdditionalMetadata: opts.AdditionalMetadata, + }) + } + } + } + + msg, err := tasktypes.CreatedEventTriggerMessage( + tenantId, + tasktypes.CreatedEventTriggerPayload{ + Payloads: eventTriggerOpts, + }, + ) + + if err != nil { + return fmt.Errorf("could not create event trigger message: %w", err) + } + + err = tc.pubBuffer.Pub(ctx, msgqueue.OLAP_QUEUE, msg, false) if err != nil { return fmt.Errorf("could not trigger tasks from events: %w", err) @@ -828,11 +882,11 @@ func (tc *TasksControllerImpl) handleProcessUserEventTrigger(ctx context.Context eg := &errgroup.Group{} eg.Go(func() error { - return tc.signalTasksCreated(ctx, tenantId, tasks) + return tc.signalTasksCreated(ctx, tenantId, result.Tasks) }) eg.Go(func() error { - return tc.signalDAGsCreated(ctx, tenantId, dags) + return tc.signalDAGsCreated(ctx, tenantId, result.Dags) }) return eg.Wait() diff --git a/internal/services/ingestor/contracts/events.pb.go b/internal/services/ingestor/contracts/events.pb.go index 16f4933aa..d1d65ab33 100644 --- a/internal/services/ingestor/contracts/events.pb.go +++ b/internal/services/ingestor/contracts/events.pb.go @@ -465,6 +465,7 @@ type PushEventRequest struct { EventTimestamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=eventTimestamp,proto3" json:"eventTimestamp,omitempty"` // metadata for the event AdditionalMetadata *string `protobuf:"bytes,4,opt,name=additionalMetadata,proto3,oneof" json:"additionalMetadata,omitempty"` + Priority *int32 `protobuf:"varint,5,opt,name=priority,proto3,oneof" json:"priority,omitempty"` } func (x *PushEventRequest) Reset() { @@ -527,6 +528,13 @@ func (x *PushEventRequest) GetAdditionalMetadata() string { return "" } +func (x *PushEventRequest) GetPriority() int32 { + if x != nil && x.Priority != nil { + return *x.Priority + } + return 0 +} + type ReplayEventRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -633,7 +641,7 @@ var file_events_proto_rawDesc = []byte{ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x22, 0xce, 0x01, 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, + 0x73, 0x22, 0xfc, 0x01, 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, @@ -644,33 +652,36 @@ var file_events_proto_rawDesc = []byte{ 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x33, 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x15, 0x0a, 0x13, 0x5f, - 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x22, 0x2e, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x49, 0x64, 0x32, 0x88, 0x02, 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x23, 0x0a, 0x04, 0x50, 0x75, 0x73, 0x68, 0x12, 0x11, 0x2e, 0x50, - 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x2c, 0x0a, 0x08, 0x42, 0x75, 0x6c, - 0x6b, 0x50, 0x75, 0x73, 0x68, 0x12, 0x15, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x50, 0x75, 0x73, 0x68, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x07, 0x2e, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x00, 0x12, 0x32, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6c, 0x61, - 0x79, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x52, - 0x65, 0x70, 0x6c, 0x61, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x06, 0x50, - 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x12, 0x0e, 0x2e, 0x50, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x50, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x2e, 0x50, 0x75, 0x74, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x45, 0x5a, - 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x74, 0x63, - 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2f, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x70, + 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, + 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x42, 0x15, 0x0a, 0x13, + 0x5f, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x22, 0x2e, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, + 0x32, 0x88, 0x02, 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x23, 0x0a, 0x04, 0x50, 0x75, 0x73, 0x68, 0x12, 0x11, 0x2e, 0x50, 0x75, 0x73, + 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x2e, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x2c, 0x0a, 0x08, 0x42, 0x75, 0x6c, 0x6b, 0x50, + 0x75, 0x73, 0x68, 0x12, 0x15, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x07, 0x2e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x73, 0x22, 0x00, 0x12, 0x32, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x53, + 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x52, 0x65, 0x70, + 0x6c, 0x61, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x06, 0x50, 0x75, 0x74, + 0x4c, 0x6f, 0x67, 0x12, 0x0e, 0x2e, 0x50, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x50, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x45, 0x5a, 0x43, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, + 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, + 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, + 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/services/ingestor/ingestor.go b/internal/services/ingestor/ingestor.go index 0fcca3e42..c02f74898 100644 --- a/internal/services/ingestor/ingestor.go +++ b/internal/services/ingestor/ingestor.go @@ -22,7 +22,7 @@ import ( type Ingestor interface { contracts.EventsServiceServer - IngestEvent(ctx context.Context, tenant *dbsqlc.Tenant, eventName string, data []byte, metadata []byte) (*dbsqlc.Event, error) + IngestEvent(ctx context.Context, tenant *dbsqlc.Tenant, eventName string, data []byte, metadata []byte, priority *int32) (*dbsqlc.Event, error) BulkIngestEvent(ctx context.Context, tenant *dbsqlc.Tenant, eventOpts []*repository.CreateEventOpts) ([]*dbsqlc.Event, error) IngestReplayedEvent(ctx context.Context, tenant *dbsqlc.Tenant, replayedEvent *dbsqlc.Event) (*dbsqlc.Event, error) } @@ -163,12 +163,12 @@ func NewIngestor(fs ...IngestorOptFunc) (Ingestor, error) { }, nil } -func (i *IngestorImpl) IngestEvent(ctx context.Context, tenant *dbsqlc.Tenant, key string, data []byte, metadata []byte) (*dbsqlc.Event, error) { +func (i *IngestorImpl) IngestEvent(ctx context.Context, tenant *dbsqlc.Tenant, key string, data []byte, metadata []byte, priority *int32) (*dbsqlc.Event, error) { switch tenant.Version { case dbsqlc.TenantMajorEngineVersionV0: return i.ingestEventV0(ctx, tenant, key, data, metadata) case dbsqlc.TenantMajorEngineVersionV1: - return i.ingestEventV1(ctx, tenant, key, data, metadata) + return i.ingestEventV1(ctx, tenant, key, data, metadata, priority) default: return nil, fmt.Errorf("unsupported tenant version: %s", tenant.Version) } diff --git a/internal/services/ingestor/ingestor_v1.go b/internal/services/ingestor/ingestor_v1.go index 35328a297..62e1f357c 100644 --- a/internal/services/ingestor/ingestor_v1.go +++ b/internal/services/ingestor/ingestor_v1.go @@ -25,7 +25,7 @@ type EventResult struct { AdditionalMetadata string } -func (i *IngestorImpl) ingestEventV1(ctx context.Context, tenant *dbsqlc.Tenant, key string, data []byte, metadata []byte) (*dbsqlc.Event, error) { +func (i *IngestorImpl) ingestEventV1(ctx context.Context, tenant *dbsqlc.Tenant, key string, data []byte, metadata []byte, priority *int32) (*dbsqlc.Event, error) { ctx, span := telemetry.NewSpan(ctx, "ingest-event") defer span.End() @@ -49,10 +49,10 @@ func (i *IngestorImpl) ingestEventV1(ctx context.Context, tenant *dbsqlc.Tenant, ) } - return i.ingestSingleton(tenantId, key, data, metadata) + return i.ingestSingleton(tenantId, key, data, metadata, priority) } -func (i *IngestorImpl) ingestSingleton(tenantId, key string, data []byte, metadata []byte) (*dbsqlc.Event, error) { +func (i *IngestorImpl) ingestSingleton(tenantId, key string, data []byte, metadata []byte, priority *int32) (*dbsqlc.Event, error) { eventId := uuid.New().String() msg, err := eventToTaskV1( @@ -61,6 +61,7 @@ func (i *IngestorImpl) ingestSingleton(tenantId, key string, data []byte, metada key, data, metadata, + priority, ) if err != nil { @@ -115,7 +116,7 @@ func (i *IngestorImpl) bulkIngestEventV1(ctx context.Context, tenant *dbsqlc.Ten results := make([]*dbsqlc.Event, 0, len(eventOpts)) for _, event := range eventOpts { - res, err := i.ingestSingleton(tenantId, event.Key, event.Data, event.AdditionalMetadata) + res, err := i.ingestSingleton(tenantId, event.Key, event.Data, event.AdditionalMetadata, event.Priority) if err != nil { return nil, fmt.Errorf("could not ingest event: %w", err) @@ -133,15 +134,16 @@ func (i *IngestorImpl) ingestReplayedEventV1(ctx context.Context, tenant *dbsqlc tenantId := sqlchelpers.UUIDToStr(tenant.ID) - return i.ingestSingleton(tenantId, replayedEvent.Key, replayedEvent.Data, replayedEvent.AdditionalMetadata) + return i.ingestSingleton(tenantId, replayedEvent.Key, replayedEvent.Data, replayedEvent.AdditionalMetadata, nil) } -func eventToTaskV1(tenantId, eventId, key string, data, additionalMeta []byte) (*msgqueue.Message, error) { +func eventToTaskV1(tenantId, eventId, key string, data, additionalMeta []byte, priority *int32) (*msgqueue.Message, error) { payloadTyped := tasktypes.UserEventTaskPayload{ EventId: eventId, EventKey: key, EventData: data, EventAdditionalMetadata: additionalMeta, + EventPriority: priority, } return msgqueue.NewTenantMessage( diff --git a/internal/services/ingestor/server.go b/internal/services/ingestor/server.go index 6886d0bd3..6c9bceb7b 100644 --- a/internal/services/ingestor/server.go +++ b/internal/services/ingestor/server.go @@ -27,7 +27,7 @@ func (i *IngestorImpl) Push(ctx context.Context, req *contracts.PushEventRequest if req.AdditionalMetadata != nil { additionalMeta = []byte(*req.AdditionalMetadata) } - event, err := i.IngestEvent(ctx, tenant, req.Key, []byte(req.Payload), additionalMeta) + event, err := i.IngestEvent(ctx, tenant, req.Key, []byte(req.Payload), additionalMeta, req.Priority) if err == metered.ErrResourceExhausted { return nil, status.Errorf(codes.ResourceExhausted, "resource exhausted: event limit exceeded for tenant") @@ -72,6 +72,7 @@ func (i *IngestorImpl) BulkPush(ctx context.Context, req *contracts.BulkPushEven Key: e.Key, Data: []byte(e.Payload), AdditionalMetadata: additionalMeta, + Priority: e.Priority, }) } diff --git a/internal/services/shared/tasktypes/v1/event.go b/internal/services/shared/tasktypes/v1/event.go index 2e90ef08b..5bc1afe79 100644 --- a/internal/services/shared/tasktypes/v1/event.go +++ b/internal/services/shared/tasktypes/v1/event.go @@ -12,6 +12,7 @@ type UserEventTaskPayload struct { EventKey string `json:"event_key" validate:"required"` EventData []byte `json:"event_data" validate:"required"` EventAdditionalMetadata []byte `json:"event_additional_metadata"` + EventPriority *int32 `json:"event_priority,omitempty"` } func NewInternalEventMessage(tenantId string, timestamp time.Time, events ...v1.InternalTaskEvent) (*msgqueue.Message, error) { diff --git a/internal/services/shared/tasktypes/v1/olap.go b/internal/services/shared/tasktypes/v1/olap.go index 835eb93fe..839acd46a 100644 --- a/internal/services/shared/tasktypes/v1/olap.go +++ b/internal/services/shared/tasktypes/v1/olap.go @@ -44,6 +44,30 @@ func CreatedDAGMessage(tenantId string, dag *v1.DAGWithData) (*msgqueue.Message, ) } +type CreatedEventTriggerPayloadSingleton struct { + MaybeRunId *int64 `json:"run_id"` + MaybeRunInsertedAt *time.Time `json:"run_inserted_at"` + EventSeenAt time.Time `json:"event_seen_at"` + EventKey string `json:"event_key"` + EventId string `json:"event_id"` + EventPayload []byte `json:"event_payload"` + EventAdditionalMetadata []byte `json:"event_additional_metadata,omitempty"` +} + +type CreatedEventTriggerPayload struct { + Payloads []CreatedEventTriggerPayloadSingleton `json:"payloads"` +} + +func CreatedEventTriggerMessage(tenantId string, eventTriggers CreatedEventTriggerPayload) (*msgqueue.Message, error) { + return msgqueue.NewTenantMessage( + tenantId, + "created-event-trigger", + false, + true, + eventTriggers, + ) +} + type CreateMonitoringEventPayload struct { TaskId int64 `json:"task_id"` diff --git a/pkg/client/rest/gen.go b/pkg/client/rest/gen.go index ea58af687..8c9d89499 100644 --- a/pkg/client/rest/gen.go +++ b/pkg/client/rest/gen.go @@ -423,6 +423,9 @@ type CreateEventRequest struct { // Key The key for the event. Key string `json:"key"` + + // Priority The priority of the event. + Priority *int32 `json:"priority,omitempty"` } // CreateSNSIntegrationRequest defines model for CreateSNSIntegrationRequest. diff --git a/pkg/repository/event.go b/pkg/repository/event.go index d179bf213..ac07c532b 100644 --- a/pkg/repository/event.go +++ b/pkg/repository/event.go @@ -27,6 +27,9 @@ type CreateEventOpts struct { // (optional) the event metadata AdditionalMetadata []byte + + // (optional) the event priority + Priority *int32 `validate:"omitempty,min=1,max=3"` } type ListEventOpts struct { diff --git a/pkg/repository/v1/olap.go b/pkg/repository/v1/olap.go index a7da89376..db73e1bd0 100644 --- a/pkg/repository/v1/olap.go +++ b/pkg/repository/v1/olap.go @@ -212,6 +212,7 @@ type OLAPRepository interface { ListTasksByExternalIds(ctx context.Context, tenantId string, externalIds []string) ([]*sqlcv1.FlattenTasksByExternalIdsRow, error) GetTaskTimings(ctx context.Context, tenantId string, workflowRunId pgtype.UUID, depth int32) ([]*sqlcv1.PopulateTaskRunDataRow, map[string]int32, error) + BulkCreateEventsAndTriggers(ctx context.Context, events []sqlcv1.BulkCreateEventsParams, triggers []sqlcv1.BulkCreateEventTriggersParams) error } type OLAPRepositoryImpl struct { @@ -1348,3 +1349,31 @@ func (r *OLAPRepositoryImpl) GetTaskTimings(ctx context.Context, tenantId string return tasksWithData, idsToDepth, nil } + +func (r *OLAPRepositoryImpl) BulkCreateEventsAndTriggers(ctx context.Context, events []sqlcv1.BulkCreateEventsParams, triggers []sqlcv1.BulkCreateEventTriggersParams) error { + tx, commit, rollback, err := sqlchelpers.PrepareTx(ctx, r.pool, r.l, 5000) + + if err != nil { + return fmt.Errorf("error beginning transaction: %v", err) + } + + defer rollback() + + _, err = r.queries.BulkCreateEvents(ctx, tx, events) + + if err != nil { + return fmt.Errorf("error creating events: %v", err) + } + + _, err = r.queries.BulkCreateEventTriggers(ctx, tx, triggers) + + if err != nil { + return fmt.Errorf("error creating event triggers: %v", err) + } + + if err := commit(ctx); err != nil { + return fmt.Errorf("error committing transaction: %v", err) + } + + return nil +} diff --git a/pkg/repository/v1/sqlcv1/copyfrom.go b/pkg/repository/v1/sqlcv1/copyfrom.go index 80bcd58fa..cad1759b5 100644 --- a/pkg/repository/v1/sqlcv1/copyfrom.go +++ b/pkg/repository/v1/sqlcv1/copyfrom.go @@ -9,6 +9,78 @@ import ( "context" ) +// iteratorForBulkCreateEventTriggers implements pgx.CopyFromSource. +type iteratorForBulkCreateEventTriggers struct { + rows []BulkCreateEventTriggersParams + skippedFirstNextCall bool +} + +func (r *iteratorForBulkCreateEventTriggers) Next() bool { + if len(r.rows) == 0 { + return false + } + if !r.skippedFirstNextCall { + r.skippedFirstNextCall = true + return true + } + r.rows = r.rows[1:] + return len(r.rows) > 0 +} + +func (r iteratorForBulkCreateEventTriggers) Values() ([]interface{}, error) { + return []interface{}{ + r.rows[0].RunID, + r.rows[0].RunInsertedAt, + r.rows[0].EventID, + r.rows[0].EventSeenAt, + }, nil +} + +func (r iteratorForBulkCreateEventTriggers) Err() error { + return nil +} + +func (q *Queries) BulkCreateEventTriggers(ctx context.Context, db DBTX, arg []BulkCreateEventTriggersParams) (int64, error) { + return db.CopyFrom(ctx, []string{"v1_event_to_run_olap"}, []string{"run_id", "run_inserted_at", "event_id", "event_seen_at"}, &iteratorForBulkCreateEventTriggers{rows: arg}) +} + +// iteratorForBulkCreateEvents implements pgx.CopyFromSource. +type iteratorForBulkCreateEvents struct { + rows []BulkCreateEventsParams + skippedFirstNextCall bool +} + +func (r *iteratorForBulkCreateEvents) Next() bool { + if len(r.rows) == 0 { + return false + } + if !r.skippedFirstNextCall { + r.skippedFirstNextCall = true + return true + } + r.rows = r.rows[1:] + return len(r.rows) > 0 +} + +func (r iteratorForBulkCreateEvents) Values() ([]interface{}, error) { + return []interface{}{ + r.rows[0].TenantID, + r.rows[0].ID, + r.rows[0].SeenAt, + r.rows[0].Key, + r.rows[0].Payload, + r.rows[0].AdditionalMetadata, + }, nil +} + +func (r iteratorForBulkCreateEvents) Err() error { + return nil +} + +func (q *Queries) BulkCreateEvents(ctx context.Context, db DBTX, arg []BulkCreateEventsParams) (int64, error) { + return db.CopyFrom(ctx, []string{"v1_events_olap"}, []string{"tenant_id", "id", "seen_at", "key", "payload", "additional_metadata"}, &iteratorForBulkCreateEvents{rows: arg}) +} + // iteratorForCreateDAGData implements pgx.CopyFromSource. type iteratorForCreateDAGData struct { rows []CreateDAGDataParams diff --git a/pkg/repository/v1/sqlcv1/models.go b/pkg/repository/v1/sqlcv1/models.go index 07187176f..b97f5bc5e 100644 --- a/pkg/repository/v1/sqlcv1/models.go +++ b/pkg/repository/v1/sqlcv1/models.go @@ -2479,6 +2479,22 @@ type V1DurableSleep struct { SleepDuration string `json:"sleep_duration"` } +type V1EventToRunOlap struct { + RunID int64 `json:"run_id"` + RunInsertedAt pgtype.Timestamptz `json:"run_inserted_at"` + EventID pgtype.UUID `json:"event_id"` + EventSeenAt pgtype.Timestamptz `json:"event_seen_at"` +} + +type V1EventsOlap struct { + TenantID pgtype.UUID `json:"tenant_id"` + ID pgtype.UUID `json:"id"` + SeenAt pgtype.Timestamptz `json:"seen_at"` + Key string `json:"key"` + Payload []byte `json:"payload"` + AdditionalMetadata []byte `json:"additional_metadata"` +} + type V1LogLine struct { ID int64 `json:"id"` CreatedAt pgtype.Timestamptz `json:"created_at"` diff --git a/pkg/repository/v1/sqlcv1/olap.sql b/pkg/repository/v1/sqlcv1/olap.sql index ba1417cd6..d8150623c 100644 --- a/pkg/repository/v1/sqlcv1/olap.sql +++ b/pkg/repository/v1/sqlcv1/olap.sql @@ -4,7 +4,9 @@ SELECT create_v1_hash_partitions('v1_task_status_updates_tmp'::text, @partitions::int), create_v1_olap_partition_with_date_and_status('v1_tasks_olap'::text, @date::date), create_v1_olap_partition_with_date_and_status('v1_runs_olap'::text, @date::date), - create_v1_olap_partition_with_date_and_status('v1_dags_olap'::text, @date::date); + create_v1_olap_partition_with_date_and_status('v1_dags_olap'::text, @date::date), + create_v1_range_partition('v1_events_olap'::text, @date::date), + create_v1_range_partition('v1_event_to_run_olap'::text, @date::date); -- name: ListOLAPPartitionsBeforeDate :many WITH task_partitions AS ( @@ -13,7 +15,12 @@ WITH task_partitions AS ( SELECT 'v1_dags_olap' AS parent_table, p::text as partition_name FROM get_v1_partitions_before_date('v1_dags_olap', @date::date) AS p ), runs_partitions AS ( SELECT 'v1_runs_olap' AS parent_table, p::text as partition_name FROM get_v1_partitions_before_date('v1_runs_olap', @date::date) AS p +), events_partitions AS ( + SELECT 'v1_events_olap' AS parent_table, p::TEXT AS partition_name FROM get_v1_partitions_before_date('v1_events_olap', @date::date) AS p +), event_trigger_partitions AS ( + SELECT 'v1_event_to_run_olap' AS parent_table, p::TEXT AS partition_name FROM get_v1_partitions_before_date('v1_event_to_run_olap', @date::date) AS p ) + SELECT * FROM @@ -31,7 +38,22 @@ UNION ALL SELECT * FROM - runs_partitions; + runs_partitions + +UNION ALL + +SELECT + * +FROM + events_partitions + +UNION ALL + +SELECT + * +FROM + event_trigger_partitions +; -- name: CreateTasksOLAP :copyfrom INSERT INTO v1_tasks_olap ( @@ -1265,3 +1287,38 @@ FROM all_runs WHERE tenant_id = @tenantId::uuid; + + +-- name: BulkCreateEvents :copyfrom +INSERT INTO v1_events_olap ( + tenant_id, + id, + seen_at, + key, + payload, + additional_metadata +) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6 +) +; + +-- name: BulkCreateEventTriggers :copyfrom +INSERT INTO v1_event_to_run_olap( + run_id, + run_inserted_at, + event_id, + event_seen_at +) +VALUES ( + $1, + $2, + $3, + $4 +) +; diff --git a/pkg/repository/v1/sqlcv1/olap.sql.go b/pkg/repository/v1/sqlcv1/olap.sql.go index 6a9cfef70..cf59eb78e 100644 --- a/pkg/repository/v1/sqlcv1/olap.sql.go +++ b/pkg/repository/v1/sqlcv1/olap.sql.go @@ -11,6 +11,22 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +type BulkCreateEventTriggersParams struct { + RunID int64 `json:"run_id"` + RunInsertedAt pgtype.Timestamptz `json:"run_inserted_at"` + EventID pgtype.UUID `json:"event_id"` + EventSeenAt pgtype.Timestamptz `json:"event_seen_at"` +} + +type BulkCreateEventsParams struct { + TenantID pgtype.UUID `json:"tenant_id"` + ID pgtype.UUID `json:"id"` + SeenAt pgtype.Timestamptz `json:"seen_at"` + Key string `json:"key"` + Payload []byte `json:"payload"` + AdditionalMetadata []byte `json:"additional_metadata"` +} + type CreateDAGsOLAPParams struct { TenantID pgtype.UUID `json:"tenant_id"` ID int64 `json:"id"` @@ -31,7 +47,9 @@ SELECT create_v1_hash_partitions('v1_task_status_updates_tmp'::text, $1::int), create_v1_olap_partition_with_date_and_status('v1_tasks_olap'::text, $2::date), create_v1_olap_partition_with_date_and_status('v1_runs_olap'::text, $2::date), - create_v1_olap_partition_with_date_and_status('v1_dags_olap'::text, $2::date) + create_v1_olap_partition_with_date_and_status('v1_dags_olap'::text, $2::date), + create_v1_range_partition('v1_events_olap'::text, $2::date), + create_v1_range_partition('v1_event_to_run_olap'::text, $2::date) ` type CreateOLAPPartitionsParams struct { @@ -439,7 +457,12 @@ WITH task_partitions AS ( SELECT 'v1_dags_olap' AS parent_table, p::text as partition_name FROM get_v1_partitions_before_date('v1_dags_olap', $1::date) AS p ), runs_partitions AS ( SELECT 'v1_runs_olap' AS parent_table, p::text as partition_name FROM get_v1_partitions_before_date('v1_runs_olap', $1::date) AS p +), events_partitions AS ( + SELECT 'v1_events_olap' AS parent_table, p::TEXT AS partition_name FROM get_v1_partitions_before_date('v1_events_olap', $1::date) AS p +), event_trigger_partitions AS ( + SELECT 'v1_event_to_run_olap' AS parent_table, p::TEXT AS partition_name FROM get_v1_partitions_before_date('v1_event_to_run_olap', $1::date) AS p ) + SELECT parent_table, partition_name FROM @@ -458,6 +481,20 @@ SELECT parent_table, partition_name FROM runs_partitions + +UNION ALL + +SELECT + parent_table, partition_name +FROM + events_partitions + +UNION ALL + +SELECT + parent_table, partition_name +FROM + event_trigger_partitions ` type ListOLAPPartitionsBeforeDateRow struct { diff --git a/pkg/repository/v1/trigger.go b/pkg/repository/v1/trigger.go index 4317715a6..cca461cd3 100644 --- a/pkg/repository/v1/trigger.go +++ b/pkg/repository/v1/trigger.go @@ -25,6 +25,8 @@ type EventTriggerOpts struct { Data []byte AdditionalMetadata []byte + + Priority *int32 } type TriggerTaskData struct { @@ -84,7 +86,7 @@ type createDAGOpts struct { } type TriggerRepository interface { - TriggerFromEvents(ctx context.Context, tenantId string, opts []EventTriggerOpts) ([]*sqlcv1.V1Task, []*DAGWithData, error) + TriggerFromEvents(ctx context.Context, tenantId string, opts []EventTriggerOpts) (*TriggerFromEventsResult, error) TriggerFromWorkflowNames(ctx context.Context, tenantId string, opts []*WorkflowNameTriggerOpts) ([]*sqlcv1.V1Task, []*DAGWithData, error) @@ -103,18 +105,33 @@ func newTriggerRepository(s *sharedRepository) TriggerRepository { } } -func (r *TriggerRepositoryImpl) TriggerFromEvents(ctx context.Context, tenantId string, opts []EventTriggerOpts) ([]*sqlcv1.V1Task, []*DAGWithData, error) { +type Run struct { + Id int64 + InsertedAt time.Time +} + +type TriggerFromEventsResult struct { + Tasks []*sqlcv1.V1Task + Dags []*DAGWithData + EventIdToRuns map[string][]*Run +} + +func (r *TriggerRepositoryImpl) TriggerFromEvents(ctx context.Context, tenantId string, opts []EventTriggerOpts) (*TriggerFromEventsResult, error) { pre, post := r.m.Meter(ctx, dbsqlc.LimitResourceEVENT, tenantId, int32(len(opts))) // nolint: gosec if err := pre(); err != nil { - return nil, nil, err + return nil, err } - eventKeys := make([]string, 0, len(opts)) eventKeysToOpts := make(map[string][]EventTriggerOpts) + eventIdToRuns := make(map[string][]*Run) + + eventKeys := make([]string, 0, len(opts)) uniqueEventKeys := make(map[string]struct{}) for _, opt := range opts { + eventIdToRuns[opt.EventId] = []*Run{} + eventKeysToOpts[opt.Key] = append(eventKeysToOpts[opt.Key], opt) if _, ok := uniqueEventKeys[opt.Key]; ok { @@ -132,12 +149,14 @@ func (r *TriggerRepositoryImpl) TriggerFromEvents(ctx context.Context, tenantId }) if err != nil { - return nil, nil, fmt.Errorf("failed to list workflows for events: %w", err) + return nil, fmt.Errorf("failed to list workflows for events: %w", err) } // each (workflowVersionId, eventKey, opt) is a separate workflow that we need to create triggerOpts := make([]triggerTuple, 0) + externalIdToEventId := make(map[string]string) + for _, workflow := range workflowVersionIdsAndEventKeys { opts, ok := eventKeysToOpts[workflow.EventKey] @@ -153,27 +172,65 @@ func (r *TriggerRepositoryImpl) TriggerFromEvents(ctx context.Context, tenantId } additionalMetadata := triggerConverter.ToMetadata(opt.AdditionalMetadata) + externalId := uuid.NewString() triggerOpts = append(triggerOpts, triggerTuple{ workflowVersionId: sqlchelpers.UUIDToStr(workflow.WorkflowVersionId), workflowId: sqlchelpers.UUIDToStr(workflow.WorkflowId), workflowName: workflow.WorkflowName, - externalId: uuid.NewString(), + externalId: externalId, input: opt.Data, additionalMetadata: additionalMetadata, + priority: opt.Priority, }) + + externalIdToEventId[externalId] = opt.EventId } } tasks, dags, err := r.triggerWorkflows(ctx, tenantId, triggerOpts) if err != nil { - return nil, nil, err + return nil, fmt.Errorf("failed to trigger workflows: %w", err) + } + + for _, task := range tasks { + externalId := task.ExternalID + + eventId, ok := externalIdToEventId[externalId.String()] + + if !ok { + continue + } + + eventIdToRuns[eventId] = append(eventIdToRuns[eventId], &Run{ + Id: task.ID, + InsertedAt: task.InsertedAt.Time, + }) + } + + for _, dag := range dags { + externalId := dag.ExternalID + + eventId, ok := externalIdToEventId[externalId.String()] + + if !ok { + continue + } + + eventIdToRuns[eventId] = append(eventIdToRuns[eventId], &Run{ + Id: dag.ID, + InsertedAt: dag.InsertedAt.Time, + }) } post() - return tasks, dags, nil + return &TriggerFromEventsResult{ + Tasks: tasks, + Dags: dags, + EventIdToRuns: eventIdToRuns, + }, nil } func (r *TriggerRepositoryImpl) TriggerFromWorkflowNames(ctx context.Context, tenantId string, opts []*WorkflowNameTriggerOpts) ([]*sqlcv1.V1Task, []*DAGWithData, error) { diff --git a/sql/schema/v1-olap.sql b/sql/schema/v1-olap.sql index f34c22693..3bf90ac5d 100644 --- a/sql/schema/v1-olap.sql +++ b/sql/schema/v1-olap.sql @@ -373,6 +373,30 @@ BEGIN END; $$; +-- Events tables +CREATE TABLE v1_events_olap ( + tenant_id UUID NOT NULL, + id UUID NOT NULL, + seen_at TIMESTAMPTZ NOT NULL, + key TEXT NOT NULL, + payload JSONB NOT NULL, + additional_metadata JSONB, + + PRIMARY KEY (tenant_id, id, seen_at) +) PARTITION BY RANGE(seen_at); + +CREATE INDEX v1_events_olap_key_idx ON v1_events_olap (tenant_id, key); + +CREATE TABLE v1_event_to_run_olap ( + run_id BIGINT NOT NULL, + run_inserted_at TIMESTAMPTZ NOT NULL, + event_id UUID NOT NULL, + event_seen_at TIMESTAMPTZ NOT NULL, + + PRIMARY KEY (event_id, event_seen_at, run_id, run_inserted_at) +) PARTITION BY RANGE(event_seen_at); + + -- TRIGGERS TO LINK TASKS, DAGS AND EVENTS -- CREATE OR REPLACE FUNCTION v1_tasks_olap_insert_function() RETURNS TRIGGER AS