Documentation
¶
Overview ¶
Package migration provides migration progress tracking for corpus partition migration.
Index ¶
- Variables
- type Executor
- type Partition
- type PartitionStatus
- type Result
- type Row
- type Rows
- type SQLExecutor
- func (e *SQLExecutor) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
- func (e *SQLExecutor) QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)
- func (e *SQLExecutor) QueryRowContext(ctx context.Context, query string, args ...interface{}) Row
- type Tracker
- func (t *Tracker) ClaimPartition(ctx context.Context, podID string, claimTimeout time.Duration) (*Partition, error)
- func (t *Tracker) CreatePartition(ctx context.Context, partitionID string) error
- func (t *Tracker) GetMigrationStatus(ctx context.Context) (map[PartitionStatus]int64, error)
- func (t *Tracker) GetPartition(ctx context.Context, partitionID string) (*Partition, error)
- func (t *Tracker) GetStaleClaims(ctx context.Context, timeout time.Duration) ([]*Partition, error)
- func (t *Tracker) ListPartitions(ctx context.Context, status PartitionStatus) ([]*Partition, error)
- func (t *Tracker) ReleasePartition(ctx context.Context, partitionID, podID string) error
- func (t *Tracker) UpdateStatus(ctx context.Context, partitionID string, status PartitionStatus, ...) error
Constants ¶
This section is empty.
Variables ¶
var ErrNoPendingPartition = errors.New("no pending partitions available")
ErrNoPendingPartition is returned when no pending partitions are available.
var ErrPartitionClaimedByAnother = errors.New("partition claimed by another pod")
ErrPartitionClaimedByAnother is returned when trying to release a partition claimed by another pod.
var ErrPartitionNotFound = errors.New("partition not found")
ErrPartitionNotFound is returned when a partition ID doesn't exist.
Functions ¶
This section is empty.
Types ¶
type Executor ¶
type Executor interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) Row
QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)
}
Executor is the database operations interface. This is a subset of database/sql's DB and Conn interfaces, allowing for both transactional and non-transactional use.
type Partition ¶
type Partition struct {
PartitionID string // Hive partition key (e.g., "day=2023-01-01")
Status PartitionStatus // Current status
ClaimedBy *string // Pod identifier claiming this partition (NULL if not claimed)
ClaimedAt *time.Time // When the claim was made (NULL if not claimed)
CompletedAt *time.Time // When the partition completed successfully (NULL if not completed)
ErrorMessage *string // If status='failed', what went wrong (NULL if no error)
CreatedAt time.Time // When the row was created
UpdatedAt time.Time // When the row was last updated
}
Partition represents a row in the migration_progress table.
type PartitionStatus ¶
type PartitionStatus string
PartitionStatus represents the status of a migration partition.
const ( // StatusPending means the partition is waiting to be processed. StatusPending PartitionStatus = "pending" // StatusInProgress means the partition is currently being processed. StatusInProgress PartitionStatus = "in_progress" // StatusCompleted means the partition has been successfully processed. StatusCompleted PartitionStatus = "completed" // StatusFailed means the partition processing failed. StatusFailed PartitionStatus = "failed" )
type Row ¶
type Row interface {
Scan(dest ...interface{}) error
}
Row is the interface returned by QueryRowContext (subset of sql.Row).
type SQLExecutor ¶
type SQLExecutor struct {
// contains filtered or unexported fields
}
SQLExecutor wraps *sql.DB to implement the Executor interface.
func NewSQLExecutor ¶
func NewSQLExecutor(db *sql.DB) *SQLExecutor
NewSQLExecutor creates a new SQLExecutor from *sql.DB.
func (*SQLExecutor) ExecContext ¶
func (*SQLExecutor) QueryContext ¶
func (*SQLExecutor) QueryRowContext ¶
func (e *SQLExecutor) QueryRowContext(ctx context.Context, query string, args ...interface{}) Row
type Tracker ¶
type Tracker struct {
// contains filtered or unexported fields
}
Tracker tracks migration progress for corpus partitions.
func NewTracker ¶
NewTracker creates a new migration progress tracker.
func (*Tracker) ClaimPartition ¶
func (t *Tracker) ClaimPartition(ctx context.Context, podID string, claimTimeout time.Duration) (*Partition, error)
ClaimPartition claims the next pending partition for processing.
This method uses SELECT FOR UPDATE SKIP LOCKED to safely claim a partition in a concurrent environment. If multiple workers call this simultaneously, each will get a different partition (or ErrNoPendingPartition if none available).
The claim is conditional: it only succeeds if the partition is still pending. This prevents race conditions where one worker reads a pending partition but another worker claims it first.
Parameters:
- ctx: Context for the database operation
- podID: Identifier for the pod claiming this partition (e.g., "migration-worker-abc123")
- claimTimeout: Duration after which a claim is considered stale (for crash recovery)
Returns:
- *Partition: The claimed partition with status set to 'in_progress'
- error: ErrNoPendingPartition if no partitions are available, or a database error
Example usage:
partition, err := tracker.ClaimPartition(ctx, "migration-worker-pod-123", 30*time.Minute)
if err != nil {
if errors.Is(err, migration.ErrNoPendingPartition) {
log.Println("No work available - sleeping...")
return nil
}
return fmt.Errorf("failed to claim partition: %w", err)
}
log.Printf("Claimed partition: %s\n", partition.PartitionID)
// Process the partition...
func (*Tracker) CreatePartition ¶
CreatePartition creates a new partition in pending status.
This is used during the seed phase to populate the migration_progress table with all partitions that need to be processed.
Parameters:
- ctx: Context for the database operation
- partitionID: The partition identifier
Returns:
- error: Database error, or error if partition already exists
Example usage:
err := tracker.CreatePartition(ctx, "day=2023-01-01")
if err != nil {
return fmt.Errorf("failed to create partition: %w", err)
}
func (*Tracker) GetMigrationStatus ¶
GetMigrationStatus returns overall migration progress statistics.
Returns counts for each status, useful for monitoring and dashboards.
Parameters:
- ctx: Context for the database operation
Returns:
- map[PartitionStatus]int64: Count of partitions in each status
- error: Database error
Example usage:
stats, err := tracker.GetMigrationStatus(ctx)
if err != nil {
return fmt.Errorf("failed to get migration status: %w", err)
}
log.Printf("Pending: %d, In Progress: %d, Completed: %d, Failed: %d\n",
stats[migration.StatusPending],
stats[migration.StatusInProgress],
stats[migration.StatusCompleted],
stats[migration.StatusFailed])
func (*Tracker) GetPartition ¶
GetPartition retrieves a single partition by ID.
Parameters:
- ctx: Context for the database operation
- partitionID: The partition identifier
Returns:
- *Partition: The partition, or nil if not found
- error: Database error (not ErrPartitionNotFound - use nil check instead)
Example usage:
partition, err := tracker.GetPartition(ctx, "day=2023-01-01")
if err != nil {
return fmt.Errorf("failed to get partition: %w", err)
}
if partition == nil {
log.Println("Partition not found")
return nil
}
func (*Tracker) GetStaleClaims ¶
GetStaleClaims retrieves partitions that have been in_progress for longer than the timeout.
This method is used on worker startup to detect and recover from crashes. Any partition that has been claimed for longer than the timeout is considered abandoned and should be released back to pending.
Parameters:
- ctx: Context for the database operation
- timeout: Duration after which a claim is considered stale
Returns:
- []*Partition: List of stale partitions
- error: Database error
Example usage:
staleClaims, err := tracker.GetStaleClaims(ctx, 30*time.Minute)
if err != nil {
return fmt.Errorf("failed to get stale claims: %w", err)
}
for _, claim := range staleClaims {
log.Printf("Releasing stale claim: %s (claimed by %s since %s)\n",
claim.PartitionID, *claim.ClaimedBy, claim.ClaimedAt)
err := tracker.ReleasePartition(ctx, claim.PartitionID, *claim.ClaimedBy)
if err != nil {
log.Printf("Failed to release: %v\n", err)
}
}
func (*Tracker) ListPartitions ¶
ListPartitions retrieves all partitions with optional filtering.
Parameters:
- ctx: Context for the database operation
- status: Optional status filter (empty string = all statuses)
Returns:
- []*Partition: List of partitions
- error: Database error
Example usage:
// Get all pending partitions pending, err := tracker.ListPartitions(ctx, migration.StatusPending) // Get all partitions regardless of status all, err := tracker.ListPartitions(ctx, "")
func (*Tracker) ReleasePartition ¶
ReleasePartition releases a partition from in_progress back to pending.
This method is used for crash recovery: if a worker crashes while processing a partition, another worker can detect the stale claim and release it back to pending so it can be reprocessed.
The release is conditional: it only succeeds if the partition is currently claimed by the specified pod. This prevents one pod from releasing another pod's active claim.
Parameters:
- ctx: Context for the database operation
- partitionID: The partition identifier
- podID: The pod identifier that originally claimed the partition
Returns:
- error: ErrPartitionNotFound if the partition doesn't exist, ErrPartitionClaimedByAnother if claimed by a different pod, or a database error
Example usage:
// Release stale claims on startup
staleClaims, err := tracker.GetStaleClaims(ctx, 30*time.Minute)
for _, claim := range staleClaims {
err := tracker.ReleasePartition(ctx, claim.PartitionID, *claim.ClaimedBy)
if err != nil {
log.Printf("Failed to release partition %s: %v\n", claim.PartitionID, err)
}
}
func (*Tracker) UpdateStatus ¶
func (t *Tracker) UpdateStatus(ctx context.Context, partitionID string, status PartitionStatus, errorMessage *string) error
UpdateStatus updates the status of a partition.
This method is used to: - Mark a partition as completed after successful processing - Mark a partition as failed if processing encountered an error - Release a failed partition back to pending for retry
Parameters:
- ctx: Context for the database operation
- partitionID: The partition identifier
- status: The new status (completed, failed, or pending for retry)
- errorMessage: Optional error message (required for status='failed', optional otherwise)
Returns:
- error: ErrPartitionNotFound if the partition doesn't exist, or a database error
Example usage:
err := tracker.UpdateStatus(ctx, "day=2023-01-01", migration.StatusCompleted, nil)
if err != nil {
return fmt.Errorf("failed to mark partition completed: %w", err)
}