Files
tasks/common/persistence/cassandra/matching_task_store_v2.go
David ReissandGitHub fad1ac13a4 Consolidate cassandra task queue management queries (#8126)
## What changed?
Remove some code duplication for cassandra matching task queue
persistence by rewriting v2 to v1 queries.
Switch same scheme for SQL queries to use the v2 table name in the base
query and rewriting v1.
Use panic instead of returning an invalid query.

## Why?
Remove code duplication.

## How did you test it?
- [x] covered by existing tests
2025-07-31 14:08:56 -07:00

231 lines
6.6 KiB
Go

package cassandra
import (
"context"
"fmt"
"math"
"go.temporal.io/api/serviceerror"
p "go.temporal.io/server/common/persistence"
"go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql"
)
const (
templateCreateTaskQuery_v2 = `INSERT INTO tasks_v2 (` +
`namespace_id, task_queue_name, task_queue_type, type, pass, task_id, task, task_encoding) ` +
`VALUES(?, ?, ?, ?, ?, ?, ?, ?)`
templateGetTasksQuery_v2 = `SELECT task_id, task, task_encoding ` +
`FROM tasks_v2 ` +
`WHERE namespace_id = ? ` +
`and task_queue_name = ? ` +
`and task_queue_type = ? ` +
`and type = ? ` +
`and (pass, task_id) >= (?, ?)`
templateGetTasksQuery_v2_limit = `SELECT task_id, task, task_encoding ` +
`FROM tasks_v2 ` +
`WHERE namespace_id = ? ` +
`and task_queue_name = ? ` +
`and task_queue_type = ? ` +
`and type = ? ` +
`and (pass, task_id) >= (?, ?) ` +
`LIMIT ?`
templateCompleteTasksLessThanQuery_v2 = `DELETE FROM tasks_v2 ` +
`WHERE namespace_id = ? ` +
`AND task_queue_name = ? ` +
`AND task_queue_type = ? ` +
`AND type = ? ` +
`and (pass, task_id) < (?, ?)`
)
// matchingTaskStoreV2 is a fork of matchingTaskStoreV1 that uses a new task schema.
// Task queue operations are now unified with V1 through query rewriting.
type matchingTaskStoreV2 struct {
Session gocql.Session
userDataStore
taskQueueStore
}
func newMatchingTaskStoreV2(
session gocql.Session,
) *matchingTaskStoreV2 {
return &matchingTaskStoreV2{
Session: session,
userDataStore: userDataStore{Session: session},
taskQueueStore: taskQueueStore{Session: session, version: matchingTaskVersion2},
}
}
// CreateTasks add tasks
func (d *matchingTaskStoreV2) CreateTasks(
ctx context.Context,
request *p.InternalCreateTasksRequest,
) (*p.CreateTasksResponse, error) {
batch := d.Session.NewBatch(gocql.LoggedBatch).WithContext(ctx)
namespaceID := request.NamespaceID
taskQueue := request.TaskQueue
taskQueueType := request.TaskType
for _, task := range request.Tasks {
if task.TaskPass == 0 {
return nil, serviceerror.NewInternal("invalid fair queue task missing pass number")
}
batch.Query(templateCreateTaskQuery_v2,
namespaceID,
taskQueue,
taskQueueType,
rowTypeTaskInSubqueue(task.Subqueue),
task.TaskPass,
task.TaskId,
task.Task.Data,
task.Task.EncodingType.String())
}
// The following query is used to ensure that range_id didn't change
batch.Query(switchTasksTable(templateUpdateTaskQueueQuery, matchingTaskVersion2),
request.RangeID,
request.TaskQueueInfo.Data,
request.TaskQueueInfo.EncodingType.String(),
namespaceID,
taskQueue,
taskQueueType,
rowTypeTaskQueue,
taskQueueTaskID,
request.RangeID,
)
previous := make(map[string]interface{})
applied, _, err := d.Session.MapExecuteBatchCAS(batch, previous)
if err != nil {
return nil, gocql.ConvertError("CreateTasks", err)
}
if !applied {
rangeID := previous["range_id"]
return nil, &p.ConditionFailedError{
Msg: fmt.Sprintf("Failed to create task. TaskQueue: %v, taskQueueType: %v, rangeID: %v, db rangeID: %v",
taskQueue, taskQueueType, request.RangeID, rangeID),
}
}
return &p.CreateTasksResponse{UpdatedMetadata: true}, nil
}
// GetTasks get a task
func (d *matchingTaskStoreV2) GetTasks(
ctx context.Context,
request *p.GetTasksRequest,
) (*p.InternalGetTasksResponse, error) {
if request.InclusiveMinPass < 1 {
return nil, serviceerror.NewInternal("invalid GetTasks request on fair queue: InclusiveMinPass must be >= 1")
}
if request.ExclusiveMaxTaskID != math.MaxInt64 {
// ExclusiveMaxTaskID is not supported in fair queue.
return nil, serviceerror.NewInternal("invalid GetTasks request on fair queue: ExclusiveMaxTaskID is not supported")
}
// Reading taskqueue tasks need to be quorum level consistent, otherwise we could lose tasks
var query gocql.Query
if request.UseLimit {
query = d.Session.Query(templateGetTasksQuery_v2_limit,
request.NamespaceID,
request.TaskQueue,
request.TaskType,
rowTypeTaskInSubqueue(request.Subqueue),
request.InclusiveMinPass,
request.InclusiveMinTaskID,
request.PageSize,
)
} else {
query = d.Session.Query(templateGetTasksQuery_v2,
request.NamespaceID,
request.TaskQueue,
request.TaskType,
rowTypeTaskInSubqueue(request.Subqueue),
request.InclusiveMinPass,
request.InclusiveMinTaskID,
)
}
iter := query.WithContext(ctx).PageSize(request.PageSize).PageState(request.NextPageToken).Iter()
response := &p.InternalGetTasksResponse{}
task := make(map[string]interface{})
for iter.MapScan(task) {
_, ok := task["task_id"]
if !ok { // no tasks, but static column record returned
continue
}
rawTask, ok := task["task"]
if !ok {
return nil, newFieldNotFoundError("task", task)
}
taskVal, ok := rawTask.([]byte)
if !ok {
var byteSliceType []byte
return nil, newPersistedTypeMismatchError("task", byteSliceType, rawTask, task)
}
rawEncoding, ok := task["task_encoding"]
if !ok {
return nil, newFieldNotFoundError("task_encoding", task)
}
encodingVal, ok := rawEncoding.(string)
if !ok {
var byteSliceType []byte
return nil, newPersistedTypeMismatchError("task_encoding", byteSliceType, rawEncoding, task)
}
response.Tasks = append(response.Tasks, p.NewDataBlob(taskVal, encodingVal))
task = make(map[string]interface{}) // Reinitialize map as initialized fails on unmarshalling
}
if len(iter.PageState()) > 0 {
response.NextPageToken = iter.PageState()
}
if err := iter.Close(); err != nil {
return nil, serviceerror.NewUnavailablef("GetTasks operation failed. Error: %v", err)
}
return response, nil
}
// CompleteTasksLessThan deletes all tasks less than the given task id. This API ignores the
// Limit request parameter i.e. either all tasks leq the task_id will be deleted or an error will
// be returned to the caller
func (d *matchingTaskStoreV2) CompleteTasksLessThan(
ctx context.Context,
request *p.CompleteTasksLessThanRequest,
) (int, error) {
// Require starting from pass 1.
if request.ExclusiveMaxPass < 1 {
return 0, serviceerror.NewInternal("invalid CompleteTasksLessThan request on fair queue")
}
query := d.Session.Query(
templateCompleteTasksLessThanQuery_v2,
request.NamespaceID,
request.TaskQueueName,
request.TaskType,
rowTypeTaskInSubqueue(request.Subqueue),
request.ExclusiveMaxPass,
request.ExclusiveMaxTaskID,
).WithContext(ctx)
err := query.Exec()
if err != nil {
return 0, gocql.ConvertError("CompleteTasksLessThan", err)
}
return p.UnknownNumRowsAffected, nil
}
func (d *matchingTaskStoreV2) GetName() string {
return cassandraPersistenceName
}
func (d *matchingTaskStoreV2) Close() {
if d.Session != nil {
d.Session.Close()
}
}