Principle:OpenHands OpenHands Post Migration Verification
| Knowledge Sources | |
|---|---|
| Domains | Organization_Management, Multi_Tenancy |
| Last Updated | 2026-02-11 21:00 GMT |
Overview
Verifying data integrity after migration with distributed locking prevents concurrent migration attempts and confirms that the migrated user record and its related entities are fully consistent.
Description
The Post-Migration Verification principle implements a verification pattern that uses two complementary techniques to ensure migration correctness:
- Distributed locking: Before attempting to read (and potentially trigger migration of) a user record, the system acquires a distributed lock keyed to the user ID. This prevents race conditions where two concurrent requests for the same un-migrated user could both attempt migration simultaneously, leading to duplicate organizations or conflicting state.
- Eager loading with verification: After migration completes (or when loading an already-migrated user), the system uses SQL eager loading (joinedload) to fetch the user record along with all related entities (personal organization, organization memberships, roles) in a single query. This serves two purposes: it verifies that all expected relationships exist, and it avoids the N+1 query problem when the caller needs to access related data.
This pattern is especially important during the migration transition period when some users have been migrated and others have not. Every user lookup must either confirm the user is fully migrated or trigger and verify the migration in a thread-safe manner.
Usage
Apply this principle when reading data that may or may not have undergone a migration, and where concurrent access could trigger duplicate migrations. Common scenarios include:
- Loading user records that may need just-in-time migration to the organizational model
- Accessing any entity with lazy-initialized dependent resources in a concurrent environment
- Post-migration health checks that verify referential integrity across related tables
Theoretical Basis
The verification pattern follows a lock-load-verify sequence:
# Pseudocode for post-migration verification with distributed locking
async def get_entity_verified(entity_id):
# Step 1: Acquire a distributed lock to prevent concurrent migration
async with distributed_lock(key=f"entity:{entity_id}"):
# Step 2: Attempt to load the entity with eager-loaded relationships
entity = await database.query(Entity).options(
joinedload(Entity.organization),
joinedload(Entity.memberships).joinedload(Membership.role),
).filter_by(id=entity_id).first()
# Step 3: If entity does not exist, trigger migration
if entity is None:
entity = await migrate_entity(entity_id)
# Step 4: Verify all expected relationships are present
assert entity.organization is not None
assert len(entity.memberships) > 0
return entity
Key considerations:
- Lock scope: The distributed lock should be scoped to the individual entity (not a global lock) to minimize contention. Only concurrent requests for the same user are serialized.
- Lock timeout: The lock must have a reasonable timeout to prevent deadlocks if the migration process crashes while holding the lock.
- Eager loading strategy: Use
joinedloadfor one-to-one and small one-to-many relationships. For large collections, considersubqueryloadto avoid Cartesian product issues. - Graceful degradation: If the lock cannot be acquired within the timeout, the system should return an appropriate error rather than proceeding without the lock.