Structure
Post-commit side effects — IEventPublisher and IWorkQueue enqueue after Postgres commit with no transactional guarantee.
Problem
If the process crashes or Redis fails between DB commit and enqueue/publish, the system is inconsistent:
| Flow |
DB state |
Side effect lost |
ChatMessageService.CreateMessageAsync |
Message persisted |
Stream job, pub/sub event, SignalR |
MediaService.CompleteUploadAsync |
Status Processing |
media.uploaded worker job |
UserService.DeleteUserAsync / nickname update |
User updated/deleted |
UserNicknameChanged event, cache invalidate |
RedisStreamWorkQueue.EnqueueAsync and RedisEventPublisher.PublishAsync are separate from EF transactions (services/Tangle.AspNetCore/Queue/, services/Users/Infrastructure/RedisEventPublisher.cs).
Workers use at-least-once delivery (ack after handler success) — correct for async work, but does not fix lost enqueues.
Suggested fix
Introduce a transactional outbox pattern (per service or shared in Tangle.AspNetCore):
- Write outbox row in same DB transaction as domain change.
- Background dispatcher reads outbox → Redis Streams / pub/sub.
- Idempotent consumers keyed by
messageId / mediaAssetId.
Phased rollout: start with media.uploaded and chat.message.created (highest business impact).
Acceptance criteria
Structure
Post-commit side effects —
IEventPublisherandIWorkQueueenqueue after Postgres commit with no transactional guarantee.Problem
If the process crashes or Redis fails between DB commit and enqueue/publish, the system is inconsistent:
ChatMessageService.CreateMessageAsyncMediaService.CompleteUploadAsyncProcessingmedia.uploadedworker jobUserService.DeleteUserAsync/ nickname updateUserNicknameChangedevent, cache invalidateRedisStreamWorkQueue.EnqueueAsyncandRedisEventPublisher.PublishAsyncare separate from EF transactions (services/Tangle.AspNetCore/Queue/,services/Users/Infrastructure/RedisEventPublisher.cs).Workers use at-least-once delivery (ack after handler success) — correct for async work, but does not fix lost enqueues.
Suggested fix
Introduce a transactional outbox pattern (per service or shared in
Tangle.AspNetCore):messageId/mediaAssetId.Phased rollout: start with
media.uploadedandchat.message.created(highest business impact).Acceptance criteria
docs/QUEUE.mdanddocs/EVENTS.mdwith durability guarantees