The Fix
pip install pydantic==2.12.0
Based on closed pydantic/pydantic issue #11605 · PR/commit linked
@@ -10,7 +10,7 @@
from functools import cache, partial, wraps
from types import FunctionType
-from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, NoReturn, cast
+from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, NoReturn, TypeVar, cast
# https://github.com/peterchenadded/pydantic-abc-performance
from pydantic import BaseModel
import logging
import time
class MyEntity1Pattern(BaseModel):
pass
class MyEntity2Pattern(BaseModel):
pass
def _get_my_entity1_pattern():
class MyTest1(MyEntity1Pattern):
pass
return MyTest1
def _get_my_entity2_pattern():
class MyTest2(MyEntity2Pattern):
pass
return MyTest2
class Measurement(BaseModel):
my_entity_1_pattern_check: float = 0
my_entity_2_pattern_check: float = 0
success: int = 0
failed: int = 0
def count(self, input: bool) -> None:
if input:
self.success += 1
else:
self.failed += 1
def test_performance():
my_objects = []
logging.info("Started getting objects to check")
for i in range(7000):
if i % 2 == 0:
my_objects.append(_get_my_entity1_pattern()())
else:
my_objects.append(_get_my_entity2_pattern()())
logging.info("Completed len(my_objects) = %d", len(my_objects))
logging.info("Started checking objects")
measurement = Measurement()
for m in my_objects:
start = time.time()
measurement.count(isinstance(m, MyEntity1Pattern))
measurement.my_entity_1_pattern_check += time.time() - start
start = time.time()
measurement.count(isinstance(m, MyEntity2Pattern))
measurement.my_entity_2_pattern_check += time.time() - start
logging.info("Completed checking classes")
logging.info("measures:\n%s", measurement.model_dump_json(indent=4))
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Option A — Upgrade to fixed release\npip install pydantic==2.12.0\nWhen NOT to use: This fix is not suitable if virtual subclasses are essential for your application.\n\n
Why This Fix Works in Production
- Trigger: It took 1 second to isinstance 7000 distinct class objects on my custom changes in test_performance_fix.py with --profile.
- Mechanism: Pydantic's use of abc leads to excessive memory usage during isinstance checks with many distinct classes
- Why the fix works: Warns if registering virtual subclasses on Pydantic models to avoid memory and performance issues. (first fixed release: 2.12.0).
- If left unfixed, this can cause silent data inconsistencies that propagate (bad cache entries, incorrect downstream decisions).
Why This Breaks in Prod
- Pydantic's use of abc leads to excessive memory usage during isinstance checks with many distinct classes
- Production symptom (often without a traceback): It took 1 second to isinstance 7000 distinct class objects on my custom changes in test_performance_fix.py with --profile.
Proof / Evidence
- GitHub issue: #11605
- Fix PR: https://github.com/pydantic/pydantic/pull/11669
- First fixed release: 2.12.0
- Reproduced locally: No (not executed)
- Last verified: 2026-02-09
- Confidence: 0.85
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.39
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“> If someone relied on .register() behavior, what should used to use it on 2.12? If someone does, it isn't working anyway, for at least…”
“> That said the 2.11 patch #11116 to fix the issue should then be reverted if we follow the logic”
“PydanticAI Github Bot Found 1 issues similar to this one: 1. "https://github.com/pydantic/pydantic/issues/9458" (95% similar)”
“@Viicos thanks for looking into it further and finding another bug”
Failure Signature (Search String)
- It took 1 second to isinstance 7000 distinct class objects on my custom changes in test_performance_fix.py with --profile.
- If I changed it to 10_000 distinct class objects it gets killed on my machine.
Copy-friendly signature
Failure Signature
-----------------
It took 1 second to isinstance 7000 distinct class objects on my custom changes in test_performance_fix.py with --profile.
If I changed it to 10_000 distinct class objects it gets killed on my machine.
Error Message
Signature-only (no traceback captured)
Error Message
-------------
It took 1 second to isinstance 7000 distinct class objects on my custom changes in test_performance_fix.py with --profile.
If I changed it to 10_000 distinct class objects it gets killed on my machine.
Minimal Reproduction
# https://github.com/peterchenadded/pydantic-abc-performance
from pydantic import BaseModel
import logging
import time
class MyEntity1Pattern(BaseModel):
pass
class MyEntity2Pattern(BaseModel):
pass
def _get_my_entity1_pattern():
class MyTest1(MyEntity1Pattern):
pass
return MyTest1
def _get_my_entity2_pattern():
class MyTest2(MyEntity2Pattern):
pass
return MyTest2
class Measurement(BaseModel):
my_entity_1_pattern_check: float = 0
my_entity_2_pattern_check: float = 0
success: int = 0
failed: int = 0
def count(self, input: bool) -> None:
if input:
self.success += 1
else:
self.failed += 1
def test_performance():
my_objects = []
logging.info("Started getting objects to check")
for i in range(7000):
if i % 2 == 0:
my_objects.append(_get_my_entity1_pattern()())
else:
my_objects.append(_get_my_entity2_pattern()())
logging.info("Completed len(my_objects) = %d", len(my_objects))
logging.info("Started checking objects")
measurement = Measurement()
for m in my_objects:
start = time.time()
measurement.count(isinstance(m, MyEntity1Pattern))
measurement.my_entity_1_pattern_check += time.time() - start
start = time.time()
measurement.count(isinstance(m, MyEntity2Pattern))
measurement.my_entity_2_pattern_check += time.time() - start
logging.info("Completed checking classes")
logging.info("measures:\n%s", measurement.model_dump_json(indent=4))
Environment
- Pydantic: 2
What Broke
Severe memory and performance issues occur when checking types of many distinct Pydantic models.
Why It Broke
Pydantic's use of abc leads to excessive memory usage during isinstance checks with many distinct classes
Fix Options (Details)
Option A — Upgrade to fixed release Safe default (recommended)
pip install pydantic==2.12.0
Use when you can deploy the upstream fix. It is usually lower-risk than long-lived workarounds.
Fix reference: https://github.com/pydantic/pydantic/pull/11669
First fixed release: 2.12.0
Last verified: 2026-02-09. Validate in your environment.
When NOT to Use This Fix
- This fix is not suitable if virtual subclasses are essential for your application.
Verify Fix
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Did This Fix Work in Your Case?
Quick signal helps us prioritize which fixes to verify and improve.
Prevention
- Add a CI check that diffs key outputs after upgrades (OpenAPI schema snapshots, JSON payload shapes, CLI output).
- Upgrade behind a canary and run integration tests against the canary before 100% rollout.
Version Compatibility Table
| Version | Status |
|---|---|
| 2.12.0 | Fixed |
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.