Fix MySQL UUID generation in task_instance migration - #54814

Merged
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids
Aug 22, 2025
Merged

Fix MySQL UUID generation in task_instance migration#54814
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids

Conversation

@kaxil

@kaxilkaxil commented Aug 22, 2025

Copy link
Copy Markdown
Member

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs that fail Pydantic validation when the scheduler attempts to enqueue task instances.

Problem

The original MySQL function had two critical issues:

  1. Generated only 16 random hex characters instead of the required 20
  2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final segments instead of the required 12 characters

This resulted in malformed UUIDs like:

  • Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
  • Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)

Simple Reproduction

The issue can be demonstrated with pure Python and the malformed UUIDs:

frompydanticimportBaseModelfromuuidimportUUIDclassTaskInstanceDemo(BaseModel):
id: UUID# This fails with the exact error from the issuebad_uuid="0198cf6d-fb98-4555-7301-e29b8403"# 32 charsTaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8# This works fine good_uuid="0198cf6d-fb98-4555-7301-e29b8403abcd"# 36 charsTaskInstanceDemo(id=good_uuid) # ✓ Success

When This Issue Occurs

The validation error happens when:

  1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
  2. These tasks receive malformed UUIDs during migration
  3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
  4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'

Users with no scheduled tasks during migration or who create new DAG runs typically don't encounter this issue since new task instances get proper UUIDs from the Python uuid7() function.

Solution

Updated the MySQL uuid_generate_v7 function to:

  • Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
  • Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
  • Mark function as NOT DETERMINISTIC (correct for random functions)
  • Use CHAR(20) declaration matching actual usage

Why No New Data Migration

I decided against creating a separate migration to fix existing malformed UUIDs because:

  1. Limited scope - Only affects task instances in 'scheduled' state during migration
  2. Self-healing - System recovers as old tasks complete and new ones are created
  3. Risk mitigation - Avoid complex primary key modifications in production
  4. Alternative available - Manual fix script provided below for affected users
  5. Prevention focus - Fixing root cause prevents future occurrences

Manual Fix for Affected Users

If you encounter the UUID validation error, you can fix existing malformed UUIDs:

-- Fix malformed UUIDs by extending them to proper lengthUPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) =8; -- Find 8-char final segments-- Verify the fixSELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) !=12LIMIT5;

Testing

Error in Scheduler:

[2025-08-22T02:17:43.203+0000] {scheduler_job_runner.py:710} INFO - Trying to enqueue tasks: [<TaskInstance: as.simplest_dag manual__2025-08-22T01:39:07.403055+00:00 [scheduled]>, <TaskInstance: as.simplest_dag scheduled__2025-08-22T02:15:00+00:00 [scheduled]>] for executor: LocalExecutor(parallelism=32)
[2025-08-22T02:17:43.207+0000] {scheduler_job_runner.py:984} ERROR - Exception when executing SchedulerJob._run_scheduler_loop
Traceback (most recent call last):
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
[2025-08-22T02:17:43.208+0000] {local_executor.py:230} INFO - Shutting down LocalExecutor; waiting for running tasks to finish. Signal again if you don't want to wait.
[2025-08-22T02:17:43.208+0000] {scheduler_job_runner.py:996} INFO - Exited execute loop
Traceback (most recent call last):
File "/usr/local/bin/airflow", line 10, in <module>
sys.exit(main())
File "/opt/airflow/airflow-core/src/airflow/__main__.py", line 55, in main
args.func(args)
File "/opt/airflow/airflow-core/src/airflow/cli/cli_config.py", line 49, in command
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/cli.py", line 114, in wrapper
return f(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/providers_configuration_loader.py", line 54, in wrapped_function
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 52, in scheduler
run_command_with_daemon_option(
File "/opt/airflow/airflow-core/src/airflow/cli/commands/daemon_utils.py", line 86, in run_command_with_daemon_option
callback()
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 55, in <lambda>
callback=lambda: _run_scheduler_job(args),
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 43, in _run_scheduler_job
run_job(job=job_runner.job, execute_callable=job_runner._execute)
File "/opt/airflow/airflow-core/src/airflow/utils/session.py", line 100, in wrapper
return func(*args, session=session, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 368, in run_job
return execute_job(job, execute_callable=execute_callable)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 397, in execute_job
ret = execute_callable()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
image

Closes#54554

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs
that fail Pydantic validation when the scheduler attempts to enqueue task instances.
## Problem
The original MySQL function had two critical issues:
1. Generated only 16 random hex characters instead of the required 20
2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final
segments instead of the required 12 characters
This resulted in malformed UUIDs like:
- Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
- Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)
## Simple Reproduction
The issue can be demonstrated with pure Python and the malformed UUIDs:
```python
from pydantic import BaseModel
from uuid import UUID
class TaskInstanceDemo(BaseModel):
id: UUID
# This fails with the exact error from the issue
bad_uuid = "0198cf6d-fb98-4555-7301-e29b8403" # 32 chars
TaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8
# This works fine
good_uuid = "0198cf6d-fb98-4555-7301-e29b8403abcd" # 36 chars
TaskInstanceDemo(id=good_uuid) # ✓ Success
```
## When This Issue Occurs
The validation error happens when:
1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
2. These tasks receive malformed UUIDs during migration
3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'
Users with no scheduled tasks during migration or who create new DAG runs typically
don't encounter this issue since new task instances get proper UUIDs from the
Python uuid7() function.
## Solution
Updated the MySQL uuid_generate_v7 function to:
- Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
- Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
- Mark function as NOT DETERMINISTIC (correct for random functions)
- Use CHAR(20) declaration matching actual usage
## Why No Data Migration
We decided against creating a separate migration to fix existing malformed UUIDs because:
1. **Limited scope** - Only affects task instances in 'scheduled' state during migration
2. **Self-healing** - System recovers as old tasks complete and new ones are created
3. **Risk mitigation** - Avoid complex primary key modifications in production
4. **Alternative available** - Manual fix script provided below for affected users
5. **Prevention focus** - Fixing root cause prevents future occurrences
## Manual Fix for Affected Users
If you encounter the UUID validation error, you can fix existing malformed UUIDs:
```sql
-- Fix malformed UUIDs by extending them to proper length
UPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) = 8; -- Find 8-char final segments
-- Verify the fix
SELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) != 12
LIMIT 5;
```
## Testing
Verified the fix generates valid UUIDs:
- Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (36 chars total)
- Final segment: 12 characters (not 8)
- Passes standard UUID validation patterns
Fixesapache#54554
@kaxil
kaxilforce-pushed the fix/mysql-uuid-migration-malformed-ids branch from debe60a to c48b1efCompareAugust 22, 2025 03:57
@kaxil
kaxil merged commit 600716f into apache:mainAug 22, 2025
57 checks passed
@kaxil
kaxil deleted the fix/mysql-uuid-migration-malformed-ids branch August 22, 2025 04:42
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-0-test. View the failure log Run details

StatusBranchResult
v3-0-testCommit Link

You can attempt to backport this manually by running:

cherry_picker 600716f v3-0-test

This should apply the commit to the v3-0-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

kaxil added a commit that referenced this pull request Aug 22, 2025
mangal-vairalkar pushed a commit to mangal-vairalkar/airflow that referenced this pull request Aug 30, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

taskInstance Id format wrong while migrating to 3.0.4 and using MySQL as database

2 participants

@kaxil@vatsrahul1001
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix MySQL UUID generation in task_instance migration - #54814

Merged
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids
Aug 22, 2025
Merged

Fix MySQL UUID generation in task_instance migration#54814
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids

Conversation

@kaxil

@kaxilkaxil commented Aug 22, 2025

Copy link
Copy Markdown
Member

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs that fail Pydantic validation when the scheduler attempts to enqueue task instances.

Problem

The original MySQL function had two critical issues:

  1. Generated only 16 random hex characters instead of the required 20
  2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final segments instead of the required 12 characters

This resulted in malformed UUIDs like:

  • Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
  • Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)

Simple Reproduction

The issue can be demonstrated with pure Python and the malformed UUIDs:

frompydanticimportBaseModelfromuuidimportUUIDclassTaskInstanceDemo(BaseModel):
id: UUID# This fails with the exact error from the issuebad_uuid="0198cf6d-fb98-4555-7301-e29b8403"# 32 charsTaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8# This works fine good_uuid="0198cf6d-fb98-4555-7301-e29b8403abcd"# 36 charsTaskInstanceDemo(id=good_uuid) # ✓ Success

When This Issue Occurs

The validation error happens when:

  1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
  2. These tasks receive malformed UUIDs during migration
  3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
  4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'

Users with no scheduled tasks during migration or who create new DAG runs typically don't encounter this issue since new task instances get proper UUIDs from the Python uuid7() function.

Solution

Updated the MySQL uuid_generate_v7 function to:

  • Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
  • Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
  • Mark function as NOT DETERMINISTIC (correct for random functions)
  • Use CHAR(20) declaration matching actual usage

Why No New Data Migration

I decided against creating a separate migration to fix existing malformed UUIDs because:

  1. Limited scope - Only affects task instances in 'scheduled' state during migration
  2. Self-healing - System recovers as old tasks complete and new ones are created
  3. Risk mitigation - Avoid complex primary key modifications in production
  4. Alternative available - Manual fix script provided below for affected users
  5. Prevention focus - Fixing root cause prevents future occurrences

Manual Fix for Affected Users

If you encounter the UUID validation error, you can fix existing malformed UUIDs:

-- Fix malformed UUIDs by extending them to proper lengthUPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) =8; -- Find 8-char final segments-- Verify the fixSELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) !=12LIMIT5;

Testing

Error in Scheduler:

[2025-08-22T02:17:43.203+0000] {scheduler_job_runner.py:710} INFO - Trying to enqueue tasks: [<TaskInstance: as.simplest_dag manual__2025-08-22T01:39:07.403055+00:00 [scheduled]>, <TaskInstance: as.simplest_dag scheduled__2025-08-22T02:15:00+00:00 [scheduled]>] for executor: LocalExecutor(parallelism=32)
[2025-08-22T02:17:43.207+0000] {scheduler_job_runner.py:984} ERROR - Exception when executing SchedulerJob._run_scheduler_loop
Traceback (most recent call last):
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
[2025-08-22T02:17:43.208+0000] {local_executor.py:230} INFO - Shutting down LocalExecutor; waiting for running tasks to finish. Signal again if you don't want to wait.
[2025-08-22T02:17:43.208+0000] {scheduler_job_runner.py:996} INFO - Exited execute loop
Traceback (most recent call last):
File "/usr/local/bin/airflow", line 10, in <module>
sys.exit(main())
File "/opt/airflow/airflow-core/src/airflow/__main__.py", line 55, in main
args.func(args)
File "/opt/airflow/airflow-core/src/airflow/cli/cli_config.py", line 49, in command
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/cli.py", line 114, in wrapper
return f(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/providers_configuration_loader.py", line 54, in wrapped_function
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 52, in scheduler
run_command_with_daemon_option(
File "/opt/airflow/airflow-core/src/airflow/cli/commands/daemon_utils.py", line 86, in run_command_with_daemon_option
callback()
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 55, in <lambda>
callback=lambda: _run_scheduler_job(args),
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 43, in _run_scheduler_job
run_job(job=job_runner.job, execute_callable=job_runner._execute)
File "/opt/airflow/airflow-core/src/airflow/utils/session.py", line 100, in wrapper
return func(*args, session=session, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 368, in run_job
return execute_job(job, execute_callable=execute_callable)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 397, in execute_job
ret = execute_callable()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
image

Closes#54554

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs
that fail Pydantic validation when the scheduler attempts to enqueue task instances.
## Problem
The original MySQL function had two critical issues:
1. Generated only 16 random hex characters instead of the required 20
2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final
segments instead of the required 12 characters
This resulted in malformed UUIDs like:
- Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
- Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)
## Simple Reproduction
The issue can be demonstrated with pure Python and the malformed UUIDs:
```python
from pydantic import BaseModel
from uuid import UUID
class TaskInstanceDemo(BaseModel):
id: UUID
# This fails with the exact error from the issue
bad_uuid = "0198cf6d-fb98-4555-7301-e29b8403" # 32 chars
TaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8
# This works fine
good_uuid = "0198cf6d-fb98-4555-7301-e29b8403abcd" # 36 chars
TaskInstanceDemo(id=good_uuid) # ✓ Success
```
## When This Issue Occurs
The validation error happens when:
1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
2. These tasks receive malformed UUIDs during migration
3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'
Users with no scheduled tasks during migration or who create new DAG runs typically
don't encounter this issue since new task instances get proper UUIDs from the
Python uuid7() function.
## Solution
Updated the MySQL uuid_generate_v7 function to:
- Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
- Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
- Mark function as NOT DETERMINISTIC (correct for random functions)
- Use CHAR(20) declaration matching actual usage
## Why No Data Migration
We decided against creating a separate migration to fix existing malformed UUIDs because:
1. **Limited scope** - Only affects task instances in 'scheduled' state during migration
2. **Self-healing** - System recovers as old tasks complete and new ones are created
3. **Risk mitigation** - Avoid complex primary key modifications in production
4. **Alternative available** - Manual fix script provided below for affected users
5. **Prevention focus** - Fixing root cause prevents future occurrences
## Manual Fix for Affected Users
If you encounter the UUID validation error, you can fix existing malformed UUIDs:
```sql
-- Fix malformed UUIDs by extending them to proper length
UPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) = 8; -- Find 8-char final segments
-- Verify the fix
SELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) != 12
LIMIT 5;
```
## Testing
Verified the fix generates valid UUIDs:
- Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (36 chars total)
- Final segment: 12 characters (not 8)
- Passes standard UUID validation patterns
Fixesapache#54554
@kaxil
kaxilforce-pushed the fix/mysql-uuid-migration-malformed-ids branch from debe60a to c48b1efCompareAugust 22, 2025 03:57
@kaxil
kaxil merged commit 600716f into apache:mainAug 22, 2025
57 checks passed
@kaxil
kaxil deleted the fix/mysql-uuid-migration-malformed-ids branch August 22, 2025 04:42
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-0-test. View the failure log Run details

StatusBranchResult
v3-0-testCommit Link

You can attempt to backport this manually by running:

cherry_picker 600716f v3-0-test

This should apply the commit to the v3-0-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

kaxil added a commit that referenced this pull request Aug 22, 2025
mangal-vairalkar pushed a commit to mangal-vairalkar/airflow that referenced this pull request Aug 30, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

taskInstance Id format wrong while migrating to 3.0.4 and using MySQL as database

2 participants

@kaxil@vatsrahul1001
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix MySQL UUID generation in task_instance migration - #54814

Merged
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids
Aug 22, 2025
Merged

Fix MySQL UUID generation in task_instance migration#54814
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids

Conversation

@kaxil

@kaxilkaxil commented Aug 22, 2025

Copy link
Copy Markdown
Member

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs that fail Pydantic validation when the scheduler attempts to enqueue task instances.

Problem

The original MySQL function had two critical issues:

  1. Generated only 16 random hex characters instead of the required 20
  2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final segments instead of the required 12 characters

This resulted in malformed UUIDs like:

  • Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
  • Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)

Simple Reproduction

The issue can be demonstrated with pure Python and the malformed UUIDs:

frompydanticimportBaseModelfromuuidimportUUIDclassTaskInstanceDemo(BaseModel):
id: UUID# This fails with the exact error from the issuebad_uuid="0198cf6d-fb98-4555-7301-e29b8403"# 32 charsTaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8# This works fine good_uuid="0198cf6d-fb98-4555-7301-e29b8403abcd"# 36 charsTaskInstanceDemo(id=good_uuid) # ✓ Success

When This Issue Occurs

The validation error happens when:

  1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
  2. These tasks receive malformed UUIDs during migration
  3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
  4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'

Users with no scheduled tasks during migration or who create new DAG runs typically don't encounter this issue since new task instances get proper UUIDs from the Python uuid7() function.

Solution

Updated the MySQL uuid_generate_v7 function to:

  • Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
  • Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
  • Mark function as NOT DETERMINISTIC (correct for random functions)
  • Use CHAR(20) declaration matching actual usage

Why No New Data Migration

I decided against creating a separate migration to fix existing malformed UUIDs because:

  1. Limited scope - Only affects task instances in 'scheduled' state during migration
  2. Self-healing - System recovers as old tasks complete and new ones are created
  3. Risk mitigation - Avoid complex primary key modifications in production
  4. Alternative available - Manual fix script provided below for affected users
  5. Prevention focus - Fixing root cause prevents future occurrences

Manual Fix for Affected Users

If you encounter the UUID validation error, you can fix existing malformed UUIDs:

-- Fix malformed UUIDs by extending them to proper lengthUPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) =8; -- Find 8-char final segments-- Verify the fixSELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) !=12LIMIT5;

Testing

Error in Scheduler:

[2025-08-22T02:17:43.203+0000] {scheduler_job_runner.py:710} INFO - Trying to enqueue tasks: [<TaskInstance: as.simplest_dag manual__2025-08-22T01:39:07.403055+00:00 [scheduled]>, <TaskInstance: as.simplest_dag scheduled__2025-08-22T02:15:00+00:00 [scheduled]>] for executor: LocalExecutor(parallelism=32)
[2025-08-22T02:17:43.207+0000] {scheduler_job_runner.py:984} ERROR - Exception when executing SchedulerJob._run_scheduler_loop
Traceback (most recent call last):
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
[2025-08-22T02:17:43.208+0000] {local_executor.py:230} INFO - Shutting down LocalExecutor; waiting for running tasks to finish. Signal again if you don't want to wait.
[2025-08-22T02:17:43.208+0000] {scheduler_job_runner.py:996} INFO - Exited execute loop
Traceback (most recent call last):
File "/usr/local/bin/airflow", line 10, in <module>
sys.exit(main())
File "/opt/airflow/airflow-core/src/airflow/__main__.py", line 55, in main
args.func(args)
File "/opt/airflow/airflow-core/src/airflow/cli/cli_config.py", line 49, in command
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/cli.py", line 114, in wrapper
return f(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/providers_configuration_loader.py", line 54, in wrapped_function
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 52, in scheduler
run_command_with_daemon_option(
File "/opt/airflow/airflow-core/src/airflow/cli/commands/daemon_utils.py", line 86, in run_command_with_daemon_option
callback()
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 55, in <lambda>
callback=lambda: _run_scheduler_job(args),
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 43, in _run_scheduler_job
run_job(job=job_runner.job, execute_callable=job_runner._execute)
File "/opt/airflow/airflow-core/src/airflow/utils/session.py", line 100, in wrapper
return func(*args, session=session, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 368, in run_job
return execute_job(job, execute_callable=execute_callable)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 397, in execute_job
ret = execute_callable()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
image

Closes#54554

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs
that fail Pydantic validation when the scheduler attempts to enqueue task instances.
## Problem
The original MySQL function had two critical issues:
1. Generated only 16 random hex characters instead of the required 20
2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final
segments instead of the required 12 characters
This resulted in malformed UUIDs like:
- Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
- Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)
## Simple Reproduction
The issue can be demonstrated with pure Python and the malformed UUIDs:
```python
from pydantic import BaseModel
from uuid import UUID
class TaskInstanceDemo(BaseModel):
id: UUID
# This fails with the exact error from the issue
bad_uuid = "0198cf6d-fb98-4555-7301-e29b8403" # 32 chars
TaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8
# This works fine
good_uuid = "0198cf6d-fb98-4555-7301-e29b8403abcd" # 36 chars
TaskInstanceDemo(id=good_uuid) # ✓ Success
```
## When This Issue Occurs
The validation error happens when:
1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
2. These tasks receive malformed UUIDs during migration
3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'
Users with no scheduled tasks during migration or who create new DAG runs typically
don't encounter this issue since new task instances get proper UUIDs from the
Python uuid7() function.
## Solution
Updated the MySQL uuid_generate_v7 function to:
- Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
- Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
- Mark function as NOT DETERMINISTIC (correct for random functions)
- Use CHAR(20) declaration matching actual usage
## Why No Data Migration
We decided against creating a separate migration to fix existing malformed UUIDs because:
1. **Limited scope** - Only affects task instances in 'scheduled' state during migration
2. **Self-healing** - System recovers as old tasks complete and new ones are created
3. **Risk mitigation** - Avoid complex primary key modifications in production
4. **Alternative available** - Manual fix script provided below for affected users
5. **Prevention focus** - Fixing root cause prevents future occurrences
## Manual Fix for Affected Users
If you encounter the UUID validation error, you can fix existing malformed UUIDs:
```sql
-- Fix malformed UUIDs by extending them to proper length
UPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) = 8; -- Find 8-char final segments
-- Verify the fix
SELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) != 12
LIMIT 5;
```
## Testing
Verified the fix generates valid UUIDs:
- Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (36 chars total)
- Final segment: 12 characters (not 8)
- Passes standard UUID validation patterns
Fixesapache#54554
@kaxil
kaxilforce-pushed the fix/mysql-uuid-migration-malformed-ids branch from debe60a to c48b1efCompareAugust 22, 2025 03:57
@kaxil
kaxil merged commit 600716f into apache:mainAug 22, 2025
57 checks passed
@kaxil
kaxil deleted the fix/mysql-uuid-migration-malformed-ids branch August 22, 2025 04:42
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-0-test. View the failure log Run details

StatusBranchResult
v3-0-testCommit Link

You can attempt to backport this manually by running:

cherry_picker 600716f v3-0-test

This should apply the commit to the v3-0-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

kaxil added a commit that referenced this pull request Aug 22, 2025
mangal-vairalkar pushed a commit to mangal-vairalkar/airflow that referenced this pull request Aug 30, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

taskInstance Id format wrong while migrating to 3.0.4 and using MySQL as database

2 participants

@kaxil@vatsrahul1001
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix MySQL UUID generation in task_instance migration - #54814

Merged
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids
Aug 22, 2025
Merged

Fix MySQL UUID generation in task_instance migration#54814
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids

Conversation

@kaxil

@kaxilkaxil commented Aug 22, 2025

Copy link
Copy Markdown
Member

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs that fail Pydantic validation when the scheduler attempts to enqueue task instances.

Problem

The original MySQL function had two critical issues:

  1. Generated only 16 random hex characters instead of the required 20
  2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final segments instead of the required 12 characters

This resulted in malformed UUIDs like:

  • Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
  • Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)

Simple Reproduction

The issue can be demonstrated with pure Python and the malformed UUIDs:

frompydanticimportBaseModelfromuuidimportUUIDclassTaskInstanceDemo(BaseModel):
id: UUID# This fails with the exact error from the issuebad_uuid="0198cf6d-fb98-4555-7301-e29b8403"# 32 charsTaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8# This works fine good_uuid="0198cf6d-fb98-4555-7301-e29b8403abcd"# 36 charsTaskInstanceDemo(id=good_uuid) # ✓ Success

When This Issue Occurs

The validation error happens when:

  1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
  2. These tasks receive malformed UUIDs during migration
  3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
  4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'

Users with no scheduled tasks during migration or who create new DAG runs typically don't encounter this issue since new task instances get proper UUIDs from the Python uuid7() function.

Solution

Updated the MySQL uuid_generate_v7 function to:

  • Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
  • Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
  • Mark function as NOT DETERMINISTIC (correct for random functions)
  • Use CHAR(20) declaration matching actual usage

Why No New Data Migration

I decided against creating a separate migration to fix existing malformed UUIDs because:

  1. Limited scope - Only affects task instances in 'scheduled' state during migration
  2. Self-healing - System recovers as old tasks complete and new ones are created
  3. Risk mitigation - Avoid complex primary key modifications in production
  4. Alternative available - Manual fix script provided below for affected users
  5. Prevention focus - Fixing root cause prevents future occurrences

Manual Fix for Affected Users

If you encounter the UUID validation error, you can fix existing malformed UUIDs:

-- Fix malformed UUIDs by extending them to proper lengthUPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) =8; -- Find 8-char final segments-- Verify the fixSELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) !=12LIMIT5;

Testing

Error in Scheduler:

[2025-08-22T02:17:43.203+0000] {scheduler_job_runner.py:710} INFO - Trying to enqueue tasks: [<TaskInstance: as.simplest_dag manual__2025-08-22T01:39:07.403055+00:00 [scheduled]>, <TaskInstance: as.simplest_dag scheduled__2025-08-22T02:15:00+00:00 [scheduled]>] for executor: LocalExecutor(parallelism=32)
[2025-08-22T02:17:43.207+0000] {scheduler_job_runner.py:984} ERROR - Exception when executing SchedulerJob._run_scheduler_loop
Traceback (most recent call last):
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
[2025-08-22T02:17:43.208+0000] {local_executor.py:230} INFO - Shutting down LocalExecutor; waiting for running tasks to finish. Signal again if you don't want to wait.
[2025-08-22T02:17:43.208+0000] {scheduler_job_runner.py:996} INFO - Exited execute loop
Traceback (most recent call last):
File "/usr/local/bin/airflow", line 10, in <module>
sys.exit(main())
File "/opt/airflow/airflow-core/src/airflow/__main__.py", line 55, in main
args.func(args)
File "/opt/airflow/airflow-core/src/airflow/cli/cli_config.py", line 49, in command
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/cli.py", line 114, in wrapper
return f(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/providers_configuration_loader.py", line 54, in wrapped_function
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 52, in scheduler
run_command_with_daemon_option(
File "/opt/airflow/airflow-core/src/airflow/cli/commands/daemon_utils.py", line 86, in run_command_with_daemon_option
callback()
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 55, in <lambda>
callback=lambda: _run_scheduler_job(args),
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 43, in _run_scheduler_job
run_job(job=job_runner.job, execute_callable=job_runner._execute)
File "/opt/airflow/airflow-core/src/airflow/utils/session.py", line 100, in wrapper
return func(*args, session=session, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 368, in run_job
return execute_job(job, execute_callable=execute_callable)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 397, in execute_job
ret = execute_callable()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
image

Closes#54554

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs
that fail Pydantic validation when the scheduler attempts to enqueue task instances.
## Problem
The original MySQL function had two critical issues:
1. Generated only 16 random hex characters instead of the required 20
2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final
segments instead of the required 12 characters
This resulted in malformed UUIDs like:
- Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
- Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)
## Simple Reproduction
The issue can be demonstrated with pure Python and the malformed UUIDs:
```python
from pydantic import BaseModel
from uuid import UUID
class TaskInstanceDemo(BaseModel):
id: UUID
# This fails with the exact error from the issue
bad_uuid = "0198cf6d-fb98-4555-7301-e29b8403" # 32 chars
TaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8
# This works fine
good_uuid = "0198cf6d-fb98-4555-7301-e29b8403abcd" # 36 chars
TaskInstanceDemo(id=good_uuid) # ✓ Success
```
## When This Issue Occurs
The validation error happens when:
1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
2. These tasks receive malformed UUIDs during migration
3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'
Users with no scheduled tasks during migration or who create new DAG runs typically
don't encounter this issue since new task instances get proper UUIDs from the
Python uuid7() function.
## Solution
Updated the MySQL uuid_generate_v7 function to:
- Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
- Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
- Mark function as NOT DETERMINISTIC (correct for random functions)
- Use CHAR(20) declaration matching actual usage
## Why No Data Migration
We decided against creating a separate migration to fix existing malformed UUIDs because:
1. **Limited scope** - Only affects task instances in 'scheduled' state during migration
2. **Self-healing** - System recovers as old tasks complete and new ones are created
3. **Risk mitigation** - Avoid complex primary key modifications in production
4. **Alternative available** - Manual fix script provided below for affected users
5. **Prevention focus** - Fixing root cause prevents future occurrences
## Manual Fix for Affected Users
If you encounter the UUID validation error, you can fix existing malformed UUIDs:
```sql
-- Fix malformed UUIDs by extending them to proper length
UPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) = 8; -- Find 8-char final segments
-- Verify the fix
SELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) != 12
LIMIT 5;
```
## Testing
Verified the fix generates valid UUIDs:
- Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (36 chars total)
- Final segment: 12 characters (not 8)
- Passes standard UUID validation patterns
Fixesapache#54554
@kaxil
kaxilforce-pushed the fix/mysql-uuid-migration-malformed-ids branch from debe60a to c48b1efCompareAugust 22, 2025 03:57
@kaxil
kaxil merged commit 600716f into apache:mainAug 22, 2025
57 checks passed
@kaxil
kaxil deleted the fix/mysql-uuid-migration-malformed-ids branch August 22, 2025 04:42
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-0-test. View the failure log Run details

StatusBranchResult
v3-0-testCommit Link

You can attempt to backport this manually by running:

cherry_picker 600716f v3-0-test

This should apply the commit to the v3-0-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

kaxil added a commit that referenced this pull request Aug 22, 2025
mangal-vairalkar pushed a commit to mangal-vairalkar/airflow that referenced this pull request Aug 30, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

taskInstance Id format wrong while migrating to 3.0.4 and using MySQL as database

2 participants

@kaxil@vatsrahul1001
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix MySQL UUID generation in task_instance migration - #54814

Merged
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids
Aug 22, 2025
Merged

Fix MySQL UUID generation in task_instance migration#54814
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids

Conversation

@kaxil

@kaxilkaxil commented Aug 22, 2025

Copy link
Copy Markdown
Member

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs that fail Pydantic validation when the scheduler attempts to enqueue task instances.

Problem

The original MySQL function had two critical issues:

  1. Generated only 16 random hex characters instead of the required 20
  2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final segments instead of the required 12 characters

This resulted in malformed UUIDs like:

  • Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
  • Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)

Simple Reproduction

The issue can be demonstrated with pure Python and the malformed UUIDs:

frompydanticimportBaseModelfromuuidimportUUIDclassTaskInstanceDemo(BaseModel):
id: UUID# This fails with the exact error from the issuebad_uuid="0198cf6d-fb98-4555-7301-e29b8403"# 32 charsTaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8# This works fine good_uuid="0198cf6d-fb98-4555-7301-e29b8403abcd"# 36 charsTaskInstanceDemo(id=good_uuid) # ✓ Success

When This Issue Occurs

The validation error happens when:

  1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
  2. These tasks receive malformed UUIDs during migration
  3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
  4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'

Users with no scheduled tasks during migration or who create new DAG runs typically don't encounter this issue since new task instances get proper UUIDs from the Python uuid7() function.

Solution

Updated the MySQL uuid_generate_v7 function to:

  • Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
  • Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
  • Mark function as NOT DETERMINISTIC (correct for random functions)
  • Use CHAR(20) declaration matching actual usage

Why No New Data Migration

I decided against creating a separate migration to fix existing malformed UUIDs because:

  1. Limited scope - Only affects task instances in 'scheduled' state during migration
  2. Self-healing - System recovers as old tasks complete and new ones are created
  3. Risk mitigation - Avoid complex primary key modifications in production
  4. Alternative available - Manual fix script provided below for affected users
  5. Prevention focus - Fixing root cause prevents future occurrences

Manual Fix for Affected Users

If you encounter the UUID validation error, you can fix existing malformed UUIDs:

-- Fix malformed UUIDs by extending them to proper lengthUPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) =8; -- Find 8-char final segments-- Verify the fixSELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) !=12LIMIT5;

Testing

Error in Scheduler:

[2025-08-22T02:17:43.203+0000] {scheduler_job_runner.py:710} INFO - Trying to enqueue tasks: [<TaskInstance: as.simplest_dag manual__2025-08-22T01:39:07.403055+00:00 [scheduled]>, <TaskInstance: as.simplest_dag scheduled__2025-08-22T02:15:00+00:00 [scheduled]>] for executor: LocalExecutor(parallelism=32)
[2025-08-22T02:17:43.207+0000] {scheduler_job_runner.py:984} ERROR - Exception when executing SchedulerJob._run_scheduler_loop
Traceback (most recent call last):
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
[2025-08-22T02:17:43.208+0000] {local_executor.py:230} INFO - Shutting down LocalExecutor; waiting for running tasks to finish. Signal again if you don't want to wait.
[2025-08-22T02:17:43.208+0000] {scheduler_job_runner.py:996} INFO - Exited execute loop
Traceback (most recent call last):
File "/usr/local/bin/airflow", line 10, in <module>
sys.exit(main())
File "/opt/airflow/airflow-core/src/airflow/__main__.py", line 55, in main
args.func(args)
File "/opt/airflow/airflow-core/src/airflow/cli/cli_config.py", line 49, in command
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/cli.py", line 114, in wrapper
return f(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/providers_configuration_loader.py", line 54, in wrapped_function
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 52, in scheduler
run_command_with_daemon_option(
File "/opt/airflow/airflow-core/src/airflow/cli/commands/daemon_utils.py", line 86, in run_command_with_daemon_option
callback()
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 55, in <lambda>
callback=lambda: _run_scheduler_job(args),
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 43, in _run_scheduler_job
run_job(job=job_runner.job, execute_callable=job_runner._execute)
File "/opt/airflow/airflow-core/src/airflow/utils/session.py", line 100, in wrapper
return func(*args, session=session, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 368, in run_job
return execute_job(job, execute_callable=execute_callable)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 397, in execute_job
ret = execute_callable()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
image

Closes#54554

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs
that fail Pydantic validation when the scheduler attempts to enqueue task instances.
## Problem
The original MySQL function had two critical issues:
1. Generated only 16 random hex characters instead of the required 20
2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final
segments instead of the required 12 characters
This resulted in malformed UUIDs like:
- Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
- Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)
## Simple Reproduction
The issue can be demonstrated with pure Python and the malformed UUIDs:
```python
from pydantic import BaseModel
from uuid import UUID
class TaskInstanceDemo(BaseModel):
id: UUID
# This fails with the exact error from the issue
bad_uuid = "0198cf6d-fb98-4555-7301-e29b8403" # 32 chars
TaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8
# This works fine
good_uuid = "0198cf6d-fb98-4555-7301-e29b8403abcd" # 36 chars
TaskInstanceDemo(id=good_uuid) # ✓ Success
```
## When This Issue Occurs
The validation error happens when:
1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
2. These tasks receive malformed UUIDs during migration
3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'
Users with no scheduled tasks during migration or who create new DAG runs typically
don't encounter this issue since new task instances get proper UUIDs from the
Python uuid7() function.
## Solution
Updated the MySQL uuid_generate_v7 function to:
- Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
- Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
- Mark function as NOT DETERMINISTIC (correct for random functions)
- Use CHAR(20) declaration matching actual usage
## Why No Data Migration
We decided against creating a separate migration to fix existing malformed UUIDs because:
1. **Limited scope** - Only affects task instances in 'scheduled' state during migration
2. **Self-healing** - System recovers as old tasks complete and new ones are created
3. **Risk mitigation** - Avoid complex primary key modifications in production
4. **Alternative available** - Manual fix script provided below for affected users
5. **Prevention focus** - Fixing root cause prevents future occurrences
## Manual Fix for Affected Users
If you encounter the UUID validation error, you can fix existing malformed UUIDs:
```sql
-- Fix malformed UUIDs by extending them to proper length
UPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) = 8; -- Find 8-char final segments
-- Verify the fix
SELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) != 12
LIMIT 5;
```
## Testing
Verified the fix generates valid UUIDs:
- Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (36 chars total)
- Final segment: 12 characters (not 8)
- Passes standard UUID validation patterns
Fixesapache#54554
@kaxil
kaxilforce-pushed the fix/mysql-uuid-migration-malformed-ids branch from debe60a to c48b1efCompareAugust 22, 2025 03:57
@kaxil
kaxil merged commit 600716f into apache:mainAug 22, 2025
57 checks passed
@kaxil
kaxil deleted the fix/mysql-uuid-migration-malformed-ids branch August 22, 2025 04:42
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-0-test. View the failure log Run details

StatusBranchResult
v3-0-testCommit Link

You can attempt to backport this manually by running:

cherry_picker 600716f v3-0-test

This should apply the commit to the v3-0-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

kaxil added a commit that referenced this pull request Aug 22, 2025
mangal-vairalkar pushed a commit to mangal-vairalkar/airflow that referenced this pull request Aug 30, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

taskInstance Id format wrong while migrating to 3.0.4 and using MySQL as database

2 participants

@kaxil@vatsrahul1001
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix MySQL UUID generation in task_instance migration - #54814

Merged
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids
Aug 22, 2025
Merged

Fix MySQL UUID generation in task_instance migration#54814
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids

Conversation

@kaxil

@kaxilkaxil commented Aug 22, 2025

Copy link
Copy Markdown
Member

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs that fail Pydantic validation when the scheduler attempts to enqueue task instances.

Problem

The original MySQL function had two critical issues:

  1. Generated only 16 random hex characters instead of the required 20
  2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final segments instead of the required 12 characters

This resulted in malformed UUIDs like:

  • Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
  • Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)

Simple Reproduction

The issue can be demonstrated with pure Python and the malformed UUIDs:

frompydanticimportBaseModelfromuuidimportUUIDclassTaskInstanceDemo(BaseModel):
id: UUID# This fails with the exact error from the issuebad_uuid="0198cf6d-fb98-4555-7301-e29b8403"# 32 charsTaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8# This works fine good_uuid="0198cf6d-fb98-4555-7301-e29b8403abcd"# 36 charsTaskInstanceDemo(id=good_uuid) # ✓ Success

When This Issue Occurs

The validation error happens when:

  1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
  2. These tasks receive malformed UUIDs during migration
  3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
  4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'

Users with no scheduled tasks during migration or who create new DAG runs typically don't encounter this issue since new task instances get proper UUIDs from the Python uuid7() function.

Solution

Updated the MySQL uuid_generate_v7 function to:

  • Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
  • Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
  • Mark function as NOT DETERMINISTIC (correct for random functions)
  • Use CHAR(20) declaration matching actual usage

Why No New Data Migration

I decided against creating a separate migration to fix existing malformed UUIDs because:

  1. Limited scope - Only affects task instances in 'scheduled' state during migration
  2. Self-healing - System recovers as old tasks complete and new ones are created
  3. Risk mitigation - Avoid complex primary key modifications in production
  4. Alternative available - Manual fix script provided below for affected users
  5. Prevention focus - Fixing root cause prevents future occurrences

Manual Fix for Affected Users

If you encounter the UUID validation error, you can fix existing malformed UUIDs:

-- Fix malformed UUIDs by extending them to proper lengthUPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) =8; -- Find 8-char final segments-- Verify the fixSELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) !=12LIMIT5;

Testing

Error in Scheduler:

[2025-08-22T02:17:43.203+0000] {scheduler_job_runner.py:710} INFO - Trying to enqueue tasks: [<TaskInstance: as.simplest_dag manual__2025-08-22T01:39:07.403055+00:00 [scheduled]>, <TaskInstance: as.simplest_dag scheduled__2025-08-22T02:15:00+00:00 [scheduled]>] for executor: LocalExecutor(parallelism=32)
[2025-08-22T02:17:43.207+0000] {scheduler_job_runner.py:984} ERROR - Exception when executing SchedulerJob._run_scheduler_loop
Traceback (most recent call last):
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
[2025-08-22T02:17:43.208+0000] {local_executor.py:230} INFO - Shutting down LocalExecutor; waiting for running tasks to finish. Signal again if you don't want to wait.
[2025-08-22T02:17:43.208+0000] {scheduler_job_runner.py:996} INFO - Exited execute loop
Traceback (most recent call last):
File "/usr/local/bin/airflow", line 10, in <module>
sys.exit(main())
File "/opt/airflow/airflow-core/src/airflow/__main__.py", line 55, in main
args.func(args)
File "/opt/airflow/airflow-core/src/airflow/cli/cli_config.py", line 49, in command
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/cli.py", line 114, in wrapper
return f(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/providers_configuration_loader.py", line 54, in wrapped_function
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 52, in scheduler
run_command_with_daemon_option(
File "/opt/airflow/airflow-core/src/airflow/cli/commands/daemon_utils.py", line 86, in run_command_with_daemon_option
callback()
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 55, in <lambda>
callback=lambda: _run_scheduler_job(args),
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 43, in _run_scheduler_job
run_job(job=job_runner.job, execute_callable=job_runner._execute)
File "/opt/airflow/airflow-core/src/airflow/utils/session.py", line 100, in wrapper
return func(*args, session=session, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 368, in run_job
return execute_job(job, execute_callable=execute_callable)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 397, in execute_job
ret = execute_callable()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
image

Closes#54554

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs
that fail Pydantic validation when the scheduler attempts to enqueue task instances.
## Problem
The original MySQL function had two critical issues:
1. Generated only 16 random hex characters instead of the required 20
2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final
segments instead of the required 12 characters
This resulted in malformed UUIDs like:
- Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
- Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)
## Simple Reproduction
The issue can be demonstrated with pure Python and the malformed UUIDs:
```python
from pydantic import BaseModel
from uuid import UUID
class TaskInstanceDemo(BaseModel):
id: UUID
# This fails with the exact error from the issue
bad_uuid = "0198cf6d-fb98-4555-7301-e29b8403" # 32 chars
TaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8
# This works fine
good_uuid = "0198cf6d-fb98-4555-7301-e29b8403abcd" # 36 chars
TaskInstanceDemo(id=good_uuid) # ✓ Success
```
## When This Issue Occurs
The validation error happens when:
1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
2. These tasks receive malformed UUIDs during migration
3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'
Users with no scheduled tasks during migration or who create new DAG runs typically
don't encounter this issue since new task instances get proper UUIDs from the
Python uuid7() function.
## Solution
Updated the MySQL uuid_generate_v7 function to:
- Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
- Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
- Mark function as NOT DETERMINISTIC (correct for random functions)
- Use CHAR(20) declaration matching actual usage
## Why No Data Migration
We decided against creating a separate migration to fix existing malformed UUIDs because:
1. **Limited scope** - Only affects task instances in 'scheduled' state during migration
2. **Self-healing** - System recovers as old tasks complete and new ones are created
3. **Risk mitigation** - Avoid complex primary key modifications in production
4. **Alternative available** - Manual fix script provided below for affected users
5. **Prevention focus** - Fixing root cause prevents future occurrences
## Manual Fix for Affected Users
If you encounter the UUID validation error, you can fix existing malformed UUIDs:
```sql
-- Fix malformed UUIDs by extending them to proper length
UPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) = 8; -- Find 8-char final segments
-- Verify the fix
SELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) != 12
LIMIT 5;
```
## Testing
Verified the fix generates valid UUIDs:
- Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (36 chars total)
- Final segment: 12 characters (not 8)
- Passes standard UUID validation patterns
Fixesapache#54554
@kaxil
kaxilforce-pushed the fix/mysql-uuid-migration-malformed-ids branch from debe60a to c48b1efCompareAugust 22, 2025 03:57
@kaxil
kaxil merged commit 600716f into apache:mainAug 22, 2025
57 checks passed
@kaxil
kaxil deleted the fix/mysql-uuid-migration-malformed-ids branch August 22, 2025 04:42
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-0-test. View the failure log Run details

StatusBranchResult
v3-0-testCommit Link

You can attempt to backport this manually by running:

cherry_picker 600716f v3-0-test

This should apply the commit to the v3-0-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

kaxil added a commit that referenced this pull request Aug 22, 2025
mangal-vairalkar pushed a commit to mangal-vairalkar/airflow that referenced this pull request Aug 30, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

taskInstance Id format wrong while migrating to 3.0.4 and using MySQL as database

2 participants

@kaxil@vatsrahul1001
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix MySQL UUID generation in task_instance migration - #54814

Merged
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids
Aug 22, 2025
Merged

Fix MySQL UUID generation in task_instance migration#54814
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids

Conversation

@kaxil

@kaxilkaxil commented Aug 22, 2025

Copy link
Copy Markdown
Member

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs that fail Pydantic validation when the scheduler attempts to enqueue task instances.

Problem

The original MySQL function had two critical issues:

  1. Generated only 16 random hex characters instead of the required 20
  2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final segments instead of the required 12 characters

This resulted in malformed UUIDs like:

  • Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
  • Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)

Simple Reproduction

The issue can be demonstrated with pure Python and the malformed UUIDs:

frompydanticimportBaseModelfromuuidimportUUIDclassTaskInstanceDemo(BaseModel):
id: UUID# This fails with the exact error from the issuebad_uuid="0198cf6d-fb98-4555-7301-e29b8403"# 32 charsTaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8# This works fine good_uuid="0198cf6d-fb98-4555-7301-e29b8403abcd"# 36 charsTaskInstanceDemo(id=good_uuid) # ✓ Success

When This Issue Occurs

The validation error happens when:

  1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
  2. These tasks receive malformed UUIDs during migration
  3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
  4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'

Users with no scheduled tasks during migration or who create new DAG runs typically don't encounter this issue since new task instances get proper UUIDs from the Python uuid7() function.

Solution

Updated the MySQL uuid_generate_v7 function to:

  • Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
  • Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
  • Mark function as NOT DETERMINISTIC (correct for random functions)
  • Use CHAR(20) declaration matching actual usage

Why No New Data Migration

I decided against creating a separate migration to fix existing malformed UUIDs because:

  1. Limited scope - Only affects task instances in 'scheduled' state during migration
  2. Self-healing - System recovers as old tasks complete and new ones are created
  3. Risk mitigation - Avoid complex primary key modifications in production
  4. Alternative available - Manual fix script provided below for affected users
  5. Prevention focus - Fixing root cause prevents future occurrences

Manual Fix for Affected Users

If you encounter the UUID validation error, you can fix existing malformed UUIDs:

-- Fix malformed UUIDs by extending them to proper lengthUPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) =8; -- Find 8-char final segments-- Verify the fixSELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) !=12LIMIT5;

Testing

Error in Scheduler:

[2025-08-22T02:17:43.203+0000] {scheduler_job_runner.py:710} INFO - Trying to enqueue tasks: [<TaskInstance: as.simplest_dag manual__2025-08-22T01:39:07.403055+00:00 [scheduled]>, <TaskInstance: as.simplest_dag scheduled__2025-08-22T02:15:00+00:00 [scheduled]>] for executor: LocalExecutor(parallelism=32)
[2025-08-22T02:17:43.207+0000] {scheduler_job_runner.py:984} ERROR - Exception when executing SchedulerJob._run_scheduler_loop
Traceback (most recent call last):
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
[2025-08-22T02:17:43.208+0000] {local_executor.py:230} INFO - Shutting down LocalExecutor; waiting for running tasks to finish. Signal again if you don't want to wait.
[2025-08-22T02:17:43.208+0000] {scheduler_job_runner.py:996} INFO - Exited execute loop
Traceback (most recent call last):
File "/usr/local/bin/airflow", line 10, in <module>
sys.exit(main())
File "/opt/airflow/airflow-core/src/airflow/__main__.py", line 55, in main
args.func(args)
File "/opt/airflow/airflow-core/src/airflow/cli/cli_config.py", line 49, in command
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/cli.py", line 114, in wrapper
return f(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/providers_configuration_loader.py", line 54, in wrapped_function
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 52, in scheduler
run_command_with_daemon_option(
File "/opt/airflow/airflow-core/src/airflow/cli/commands/daemon_utils.py", line 86, in run_command_with_daemon_option
callback()
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 55, in <lambda>
callback=lambda: _run_scheduler_job(args),
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 43, in _run_scheduler_job
run_job(job=job_runner.job, execute_callable=job_runner._execute)
File "/opt/airflow/airflow-core/src/airflow/utils/session.py", line 100, in wrapper
return func(*args, session=session, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 368, in run_job
return execute_job(job, execute_callable=execute_callable)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 397, in execute_job
ret = execute_callable()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
image

Closes#54554

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs
that fail Pydantic validation when the scheduler attempts to enqueue task instances.
## Problem
The original MySQL function had two critical issues:
1. Generated only 16 random hex characters instead of the required 20
2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final
segments instead of the required 12 characters
This resulted in malformed UUIDs like:
- Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
- Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)
## Simple Reproduction
The issue can be demonstrated with pure Python and the malformed UUIDs:
```python
from pydantic import BaseModel
from uuid import UUID
class TaskInstanceDemo(BaseModel):
id: UUID
# This fails with the exact error from the issue
bad_uuid = "0198cf6d-fb98-4555-7301-e29b8403" # 32 chars
TaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8
# This works fine
good_uuid = "0198cf6d-fb98-4555-7301-e29b8403abcd" # 36 chars
TaskInstanceDemo(id=good_uuid) # ✓ Success
```
## When This Issue Occurs
The validation error happens when:
1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
2. These tasks receive malformed UUIDs during migration
3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'
Users with no scheduled tasks during migration or who create new DAG runs typically
don't encounter this issue since new task instances get proper UUIDs from the
Python uuid7() function.
## Solution
Updated the MySQL uuid_generate_v7 function to:
- Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
- Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
- Mark function as NOT DETERMINISTIC (correct for random functions)
- Use CHAR(20) declaration matching actual usage
## Why No Data Migration
We decided against creating a separate migration to fix existing malformed UUIDs because:
1. **Limited scope** - Only affects task instances in 'scheduled' state during migration
2. **Self-healing** - System recovers as old tasks complete and new ones are created
3. **Risk mitigation** - Avoid complex primary key modifications in production
4. **Alternative available** - Manual fix script provided below for affected users
5. **Prevention focus** - Fixing root cause prevents future occurrences
## Manual Fix for Affected Users
If you encounter the UUID validation error, you can fix existing malformed UUIDs:
```sql
-- Fix malformed UUIDs by extending them to proper length
UPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) = 8; -- Find 8-char final segments
-- Verify the fix
SELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) != 12
LIMIT 5;
```
## Testing
Verified the fix generates valid UUIDs:
- Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (36 chars total)
- Final segment: 12 characters (not 8)
- Passes standard UUID validation patterns
Fixesapache#54554
@kaxil
kaxilforce-pushed the fix/mysql-uuid-migration-malformed-ids branch from debe60a to c48b1efCompareAugust 22, 2025 03:57
@kaxil
kaxil merged commit 600716f into apache:mainAug 22, 2025
57 checks passed
@kaxil
kaxil deleted the fix/mysql-uuid-migration-malformed-ids branch August 22, 2025 04:42
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-0-test. View the failure log Run details

StatusBranchResult
v3-0-testCommit Link

You can attempt to backport this manually by running:

cherry_picker 600716f v3-0-test

This should apply the commit to the v3-0-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

kaxil added a commit that referenced this pull request Aug 22, 2025
mangal-vairalkar pushed a commit to mangal-vairalkar/airflow that referenced this pull request Aug 30, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

taskInstance Id format wrong while migrating to 3.0.4 and using MySQL as database

2 participants

@kaxil@vatsrahul1001
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix MySQL UUID generation in task_instance migration - #54814

Merged
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids
Aug 22, 2025
Merged

Fix MySQL UUID generation in task_instance migration#54814
kaxil merged 1 commit into
apache:mainfrom
astronomer:fix/mysql-uuid-migration-malformed-ids

Conversation

@kaxil

@kaxilkaxil commented Aug 22, 2025

Copy link
Copy Markdown
Member

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs that fail Pydantic validation when the scheduler attempts to enqueue task instances.

Problem

The original MySQL function had two critical issues:

  1. Generated only 16 random hex characters instead of the required 20
  2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final segments instead of the required 12 characters

This resulted in malformed UUIDs like:

  • Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
  • Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)

Simple Reproduction

The issue can be demonstrated with pure Python and the malformed UUIDs:

frompydanticimportBaseModelfromuuidimportUUIDclassTaskInstanceDemo(BaseModel):
id: UUID# This fails with the exact error from the issuebad_uuid="0198cf6d-fb98-4555-7301-e29b8403"# 32 charsTaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8# This works fine good_uuid="0198cf6d-fb98-4555-7301-e29b8403abcd"# 36 charsTaskInstanceDemo(id=good_uuid) # ✓ Success

When This Issue Occurs

The validation error happens when:

  1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
  2. These tasks receive malformed UUIDs during migration
  3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
  4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'

Users with no scheduled tasks during migration or who create new DAG runs typically don't encounter this issue since new task instances get proper UUIDs from the Python uuid7() function.

Solution

Updated the MySQL uuid_generate_v7 function to:

  • Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
  • Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
  • Mark function as NOT DETERMINISTIC (correct for random functions)
  • Use CHAR(20) declaration matching actual usage

Why No New Data Migration

I decided against creating a separate migration to fix existing malformed UUIDs because:

  1. Limited scope - Only affects task instances in 'scheduled' state during migration
  2. Self-healing - System recovers as old tasks complete and new ones are created
  3. Risk mitigation - Avoid complex primary key modifications in production
  4. Alternative available - Manual fix script provided below for affected users
  5. Prevention focus - Fixing root cause prevents future occurrences

Manual Fix for Affected Users

If you encounter the UUID validation error, you can fix existing malformed UUIDs:

-- Fix malformed UUIDs by extending them to proper lengthUPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) =8; -- Find 8-char final segments-- Verify the fixSELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) !=12LIMIT5;

Testing

Error in Scheduler:

[2025-08-22T02:17:43.203+0000] {scheduler_job_runner.py:710} INFO - Trying to enqueue tasks: [<TaskInstance: as.simplest_dag manual__2025-08-22T01:39:07.403055+00:00 [scheduled]>, <TaskInstance: as.simplest_dag scheduled__2025-08-22T02:15:00+00:00 [scheduled]>] for executor: LocalExecutor(parallelism=32)
[2025-08-22T02:17:43.207+0000] {scheduler_job_runner.py:984} ERROR - Exception when executing SchedulerJob._run_scheduler_loop
Traceback (most recent call last):
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
[2025-08-22T02:17:43.208+0000] {local_executor.py:230} INFO - Shutting down LocalExecutor; waiting for running tasks to finish. Signal again if you don't want to wait.
[2025-08-22T02:17:43.208+0000] {scheduler_job_runner.py:996} INFO - Exited execute loop
Traceback (most recent call last):
File "/usr/local/bin/airflow", line 10, in <module>
sys.exit(main())
File "/opt/airflow/airflow-core/src/airflow/__main__.py", line 55, in main
args.func(args)
File "/opt/airflow/airflow-core/src/airflow/cli/cli_config.py", line 49, in command
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/cli.py", line 114, in wrapper
return f(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/utils/providers_configuration_loader.py", line 54, in wrapped_function
return func(*args, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 52, in scheduler
run_command_with_daemon_option(
File "/opt/airflow/airflow-core/src/airflow/cli/commands/daemon_utils.py", line 86, in run_command_with_daemon_option
callback()
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 55, in <lambda>
callback=lambda: _run_scheduler_job(args),
File "/opt/airflow/airflow-core/src/airflow/cli/commands/scheduler_command.py", line 43, in _run_scheduler_job
run_job(job=job_runner.job, execute_callable=job_runner._execute)
File "/opt/airflow/airflow-core/src/airflow/utils/session.py", line 100, in wrapper
return func(*args, session=session, **kwargs)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 368, in run_job
return execute_job(job, execute_callable=execute_callable)
File "/opt/airflow/airflow-core/src/airflow/jobs/job.py", line 397, in execute_job
ret = execute_callable()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 980, in _execute
self._run_scheduler_loop()
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1266, in _run_scheduler_loop
num_queued_tis = self._do_scheduling(session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 1408, in _do_scheduling
num_queued_tis = self._critical_section_enqueue_task_instances(session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 716, in _critical_section_enqueue_task_instances
self._enqueue_task_instances_with_queued_state(queued_tis_per_executor, executor, session=session)
File "/opt/airflow/airflow-core/src/airflow/jobs/scheduler_job_runner.py", line 665, in _enqueue_task_instances_with_queued_state
workload = workloads.ExecuteTask.make(ti, generator=executor.jwt_generator)
File "/opt/airflow/airflow-core/src/airflow/executors/workloads.py", line 114, in make
ser_ti = TaskInstance.model_validate(ti, from_attributes=True)
File "/usr/local/lib/python3.10/site-packages/pydantic/main.py", line 705, in model_validate
return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for TaskInstance
id
Input should be a valid UUID, invalid group length in group 4: expected 12, found 8 [type=uuid_parsing, input_value='0198cf6e-670d-3797-7578-d9f380de', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/uuid_parsing
image

Closes#54554

The MySQL UUID v7 generation function in migration 0042 was creating malformed UUIDs
that fail Pydantic validation when the scheduler attempts to enqueue task instances.
## Problem
The original MySQL function had two critical issues:
1. Generated only 16 random hex characters instead of the required 20
2. Used SUBSTRING(rand_hex, 9) without length limit, producing 8-character final
segments instead of the required 12 characters
This resulted in malformed UUIDs like:
- Bad: 0198cf6d-fb98-4555-7301-e29b8403 (32 chars, last segment: 8 chars)
- Good: 0198cf6d-fb98-4555-7301-e29b8403abcd (36 chars, last segment: 12 chars)
## Simple Reproduction
The issue can be demonstrated with pure Python and the malformed UUIDs:
```python
from pydantic import BaseModel
from uuid import UUID
class TaskInstanceDemo(BaseModel):
id: UUID
# This fails with the exact error from the issue
bad_uuid = "0198cf6d-fb98-4555-7301-e29b8403" # 32 chars
TaskInstanceDemo(id=bad_uuid)
# ValidationError: Input should be a valid UUID, invalid group length in group 4: expected 12, found 8
# This works fine
good_uuid = "0198cf6d-fb98-4555-7301-e29b8403abcd" # 36 chars
TaskInstanceDemo(id=good_uuid) # ✓ Success
```
## When This Issue Occurs
The validation error happens when:
1. Task instances exist in 'scheduled' state before migrating from 2.10 to 3.0.x
2. These tasks receive malformed UUIDs during migration
3. Scheduler tries to enqueue these tasks via ExecuteTask.make()
4. Pydantic validation fails: 'invalid group length in group 4: expected 12, found 8'
Users with no scheduled tasks during migration or who create new DAG runs typically
don't encounter this issue since new task instances get proper UUIDs from the
Python uuid7() function.
## Solution
Updated the MySQL uuid_generate_v7 function to:
- Use RANDOM_BYTES(10) for cryptographically secure 20-character hex data
- Apply explicit SUBSTRING(rand_hex, 9, 12) to ensure 12-character final segment
- Mark function as NOT DETERMINISTIC (correct for random functions)
- Use CHAR(20) declaration matching actual usage
## Why No Data Migration
We decided against creating a separate migration to fix existing malformed UUIDs because:
1. **Limited scope** - Only affects task instances in 'scheduled' state during migration
2. **Self-healing** - System recovers as old tasks complete and new ones are created
3. **Risk mitigation** - Avoid complex primary key modifications in production
4. **Alternative available** - Manual fix script provided below for affected users
5. **Prevention focus** - Fixing root cause prevents future occurrences
## Manual Fix for Affected Users
If you encounter the UUID validation error, you can fix existing malformed UUIDs:
```sql
-- Fix malformed UUIDs by extending them to proper length
UPDATE task_instance
SET id = CONCAT(
SUBSTRING(id, 1, 23), -- Keep first 23 chars (including last dash)
LPAD(HEX(FLOOR(RAND() * POW(2,32))), 8, '0') -- Add 8 random hex chars
)
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) = 8; -- Find 8-char final segments
-- Verify the fix
SELECT id, LENGTH(id) as uuid_length,
LENGTH(SUBSTRING_INDEX(id, '-', -1)) as last_segment_length
FROM task_instance
WHERE LENGTH(SUBSTRING_INDEX(id, '-', -1)) != 12
LIMIT 5;
```
## Testing
Verified the fix generates valid UUIDs:
- Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (36 chars total)
- Final segment: 12 characters (not 8)
- Passes standard UUID validation patterns
Fixesapache#54554
@kaxil
kaxilforce-pushed the fix/mysql-uuid-migration-malformed-ids branch from debe60a to c48b1efCompareAugust 22, 2025 03:57
@kaxil
kaxil merged commit 600716f into apache:mainAug 22, 2025
57 checks passed
@kaxil
kaxil deleted the fix/mysql-uuid-migration-malformed-ids branch August 22, 2025 04:42
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-0-test. View the failure log Run details

StatusBranchResult
v3-0-testCommit Link

You can attempt to backport this manually by running:

cherry_picker 600716f v3-0-test

This should apply the commit to the v3-0-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

kaxil added a commit that referenced this pull request Aug 22, 2025
mangal-vairalkar pushed a commit to mangal-vairalkar/airflow that referenced this pull request Aug 30, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

taskInstance Id format wrong while migrating to 3.0.4 and using MySQL as database

2 participants

@kaxil@vatsrahul1001