Scoping a proper review of MagicMapper's single-object write path. #2215 fixes the concurrency symptom; this is about what the investigation exposed underneath.
What is already established
saveObjectToRegisterSchemaTable() (~line 3400) does its own lookup and its own insert-or-update decision — independently of the lookup SaveObject::saveObject() already performed. Two layers each decide "create or update" from separate reads.
Consequences observed while debugging #2212:
- The audit action can disagree with the write. The service decides "create" from lookup (1) and records
create in the audit trail; the mapper can then take the UPDATE branch from lookup (2). Measured: 3 audit create entries against a single _id that was inserted once. The audit trail said three objects were created. One was.
- A re-fetch failure is treated as success.
insertObjectEntity() re-reads the row it just wrote and, on DoesNotExistException, logs a warning and returns the in-memory entity as though persisted. A caller cannot distinguish that from a real write.
- Two lookups per write. Both hit the database for the same question. On the fleet's hottest path that is worth measuring — see the OR CRUD budget work, where repeated resolution rather than the write itself dominated.
Questions the deep dive should answer
- Can the two insert-or-update decisions be collapsed into one, with the mapper as the single decider?
- Should the audit action be derived from what the mapper actually did rather than what the service predicted?
- Is the re-fetch fallback ever correct, or should it fail loudly?
- What does the second lookup cost per write at p50/p95?
Why not fold this into #2215
#2215 is a targeted fix with a measured before/after (12 racers: up to 8x201 before, 1x201 6/6 runs after) and 15,516 tests green. This is a structural review of the same code with a different risk profile, and mixing them would make both harder to review and to revert.
Related: #2212, #2215, hydra#425.
Scoping a proper review of
MagicMapper's single-object write path. #2215 fixes the concurrency symptom; this is about what the investigation exposed underneath.What is already established
saveObjectToRegisterSchemaTable()(~line 3400) does its own lookup and its own insert-or-update decision — independently of the lookupSaveObject::saveObject()already performed. Two layers each decide "create or update" from separate reads.Consequences observed while debugging #2212:
createin the audit trail; the mapper can then take the UPDATE branch from lookup (2). Measured: 3 auditcreateentries against a single_idthat was inserted once. The audit trail said three objects were created. One was.insertObjectEntity()re-reads the row it just wrote and, onDoesNotExistException, logs a warning and returns the in-memory entity as though persisted. A caller cannot distinguish that from a real write.Questions the deep dive should answer
Why not fold this into #2215
#2215 is a targeted fix with a measured before/after (12 racers: up to 8x201 before, 1x201 6/6 runs after) and 15,516 tests green. This is a structural review of the same code with a different risk profile, and mixing them would make both harder to review and to revert.
Related: #2212, #2215, hydra#425.