# javadoc 은 넣기를 upsert 로 정의한다 19: *

An enqueue is an upsert: the same file reported twice updates the open item rather than adding 20: * a second one. Reconciliation runs on a schedule and re-raises whatever it still cannot settle, so 21: * an append-only queue would grow one row per sweep per unresolved file and bury the distinct 22: * problems under repetitions of the same one. # 저장소 javadoc 은 최신 이유가 이긴다고 적는다 24: /** 25: * Re-raises an open item instead of adding a second one. 26: * 27: *

Reconciliation is retried on a schedule, so the same file reaches the queue repeatedly. One 28: * open item per file keeps the queue a worklist rather than a failure log; the newest reason wins 29: * because it describes the most recent evidence. 30: */ # 구현은 갱신하고, 0 이면 저장한다 42: @Override 43: public void enqueue(FileId fileId, String reasonCode) { 44: Instant now = clock.instant(); 45: if (items.refreshPending(fileId.value(), reasonCode, now) > 0) { 46: return; 47: } 48: items.save( 49: new RecoveryItemEntity(UUID.randomUUID(), fileId.value(), reasonCode, STATUS_PENDING, now)); 50: } # 그 저장이 persist 로 갈지 merge 로 갈지 정하는 것 # 엔티티의 @Version 필드: 0 # Persistable 구현: 0 # 데이터베이스가 파일당 열린 항목을 하나로 강제한다 52--- At most one open item per file: the queue is a worklist, not a log of every sweep. 53:CREATE UNIQUE INDEX uq_fs_recovery_open 54- ON fs_recovery_item (file_id) 55- WHERE status = 'PENDING'; 56- # 호출자가 실제로 받는 예외 195: private T executeLegacy(TransactionTemplate template, Supplier action) { 196- try { 197- return template.execute(status -> action.get()); 198- } catch (RuntimeException failure) { 199- throw exceptionTranslator 200- .translate(failure) 201- .map(RuntimeException.class::cast) 202- .orElse(failure); 203- } 204- } 20: "23505", OperationalError.DB_UNIQUE_VIOLATION, # 이 넣기를 부르는 프로덕션 코드와 그 경계 application-core/src/main/java/dev/caskeleton/application/fileserver/upload/DefaultFinalizeUploadService.java:323: recoveryQueue.enqueue(verifying.fileId(), "READY_COMMIT_UNCONFIRMED"); application-core/src/main/java/dev/caskeleton/application/fileserver/recovery/DefaultFileReconciliationService.java:220: recoveryQueue.enqueue(record.fileId(), reasonCode); } catch (RuntimeException exception) { // The object is on the volume and the record does not say so — the definition of an ambiguous // completion. Enqueued in its own boundary, because the one that failed took nothing with it. transactions.inWrite( () -> { recoveryQueue.enqueue(verifying.fileId(), "READY_COMMIT_UNCONFIRMED"); }); throw new AmbiguousCompletionException( "content was published but the READY commit could not be confirmed", # 조정 쪽 진입점을 부르는 프로덕션 코드: 0 # 조정이 집어 올리는 대상의 조건 27: private static final Duration STALE_AFTER = Duration.ofMinutes(5); 71: clock.instant().minus(STALE_AFTER), # 같은 저장소가 승패를 데이터베이스에 물어보는 자리 application-core/src/main/java/dev/caskeleton/application/notification/platform/dispatch/NotificationRequestInsertOutcome.java:16: * anything: {@code ON CONFLICT DO NOTHING RETURNING} returns a row when the caller won and no rows messaging/messaging-inbox-jdbc-postgresql/src/main/java/dev/caskeleton/messaging/inbox/JdbcInboxRepository.java:20: *

Reservation is an {@code INSERT ... ON CONFLICT DO NOTHING} whose affected-row count is the messaging/messaging-inbox-jdbc-postgresql/src/main/java/dev/caskeleton/messaging/inbox/JdbcInboxRepository.java:36: ON CONFLICT (message_id, consumer_id) DO NOTHING