Skip to content

fix(server): handle duplicate asset checksums atomically with ON CONFLICT#25899

Closed
Oddly wants to merge 1 commit intoimmich-app:mainfrom
Oddly:fix/asset-duplicate-checksum-race-condition
Closed

fix(server): handle duplicate asset checksums atomically with ON CONFLICT#25899
Oddly wants to merge 1 commit intoimmich-app:mainfrom
Oddly:fix/asset-duplicate-checksum-race-condition

Conversation

@Oddly
Copy link

@Oddly Oddly commented Feb 4, 2026

Summary

Replaces the check-then-insert race condition in asset creation with PostgreSQL ON CONFLICT DO NOTHING on the UQ_assets_owner_checksum constraint. This eliminates the noisy constraint violation errors that spam server logs when the mobile app uploads duplicate assets.

Fixes #22742

Changes

AssetRepository (asset.repository.ts)

  • create() now uses ON CONFLICT (constraint UQ_assets_owner_checksum) DO NOTHING with executeTakeFirst(), returning undefined when the asset already exists instead of throwing
  • createStrict() (new) preserves the original throwing behavior via executeTakeFirstOrThrow(), used by createCopy() during asset replacement where a conflict is a real error
  • createAll() also uses ON CONFLICT DO NOTHING, so batch inserts silently skip duplicates

AssetMediaService (asset-media.service.ts)

  • uploadAsset() handles the undefined return from create() as a duplicate: cleans up uploaded files, returns DUPLICATE status, and skips the usage update — all without logging a constraint violation
  • createCopy() switched to createStrict() since backup copies during asset replacement should never conflict
  • Private create() method now returns { id, duplicate } for clean control flow

MetadataService (metadata.service.ts)

  • Motion photo extraction simplified from try/catch with isAssetChecksumConstraint to a straightforward if (created) / else pattern
  • Removed unused isAssetChecksumConstraint import

Tests

  • Updated duplicate handling tests from mockRejectedValue(error) to mockResolvedValue()
  • Updated replaceAsset tests from mocks.asset.create to mocks.asset.createStrict
  • Added createStrict to the asset repository mock

Verification

  • tsc --noEmit — clean
  • All 2185 tests pass (102 test files)
  • pnpm lint — clean (0 warnings, 0 errors)

@Oddly Oddly requested a review from danieldietzler as a code owner February 4, 2026 20:50
@immich-push-o-matic
Copy link

immich-push-o-matic bot commented Feb 4, 2026

Label error. Requires exactly 1 of: changelog:.*. Found: 🗄️server. A maintainer will add the required label.

…LICT

Replace the check-then-insert race condition with PostgreSQL ON CONFLICT
DO NOTHING on the UQ_assets_owner_checksum constraint.

- Split AssetRepository.create() into create() (ON CONFLICT DO NOTHING,
  returns undefined on conflict) and createStrict() (throws, used by
  createCopy for asset replacement)
- Update AssetRepository.createAll() with the same ON CONFLICT clause
- Handle the undefined return in uploadAsset() by cleaning up files and
  returning DUPLICATE status without logging noisy constraint violations
- Simplify metadata.service motion photo extraction to use if/else
  instead of try/catch with isAssetChecksumConstraint
- Update tests to match the new behavior

Fixes immich-app#22742
@Oddly Oddly force-pushed the fix/asset-duplicate-checksum-race-condition branch from f0fbb67 to c7b2a2c Compare February 4, 2026 21:08
@Oddly
Copy link
Author

Oddly commented Feb 4, 2026

I've tested the issue before and after this patch, and this patch does seem to fix it. This was definitely LLM assisted, so I took care to write tests an also manually test again.
Could replicate the issue by doing the following:

  • Setup Immich
  • Fire off 20 concurrent image uploads with the same image
  • HTTP responses are fine, but the immich logs will show "multiple constraint violations" with a stack trace.

In the newer version, this is not issue any more.

@Oddly
Copy link
Author

Oddly commented Feb 4, 2026

I've asked for feedback with a test image in the issue thread.

@jrasm91
Copy link
Member

jrasm91 commented Feb 10, 2026

Closing in favor of #26113

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PostgresError: duplicate key value violates unique constraint "UQ_assets_owner_checksum"

2 participants