|
| 1 | +# BatchOperations 에러 처리 정책 - 클라이언트/서버 연동 |
| 2 | + |
| 3 | +**작성일**: 2025-12-10 |
| 4 | +**최종 업데이트**: 2025-12-11 |
| 5 | +**대상**: cosmic-sync-server 팀, cosmic-sync 클라이언트 팀 |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## 1. 개요 |
| 10 | + |
| 11 | +서버에서 제공한 권장사항을 반영하여 클라이언트 재시도 정책을 개선했습니다. |
| 12 | +이 문서는 **서버-클라이언트 간 에러 처리 연동 규약**을 정의합니다. |
| 13 | + |
| 14 | +### 1.1 주요 변경사항 (2025-12-11) |
| 15 | + |
| 16 | +| 구분 | 이전 | 이후 | |
| 17 | +|------|------|------| |
| 18 | +| **영구적 에러 재시도** | 1회 | **0회** (즉시 실패) | |
| 19 | +| **일시적 에러 재시도** | 무제한 | **10회** | |
| 20 | +| **충돌 에러 재시도** | 무제한 | **3회** (싱크 후) | |
| 21 | +| **에러 분류** | 메시지 파싱 | **error_code 우선** | |
| 22 | +| **Delete 멱등성** | 미구현 | **구현 완료** | |
| 23 | + |
| 24 | +### 1.2 서버 구현 완료 항목 |
| 25 | + |
| 26 | +| 작업 | 상태 | 파일 | |
| 27 | +|------|------|------| |
| 28 | +| Delete 멱등성 구현 | ✅ 완료 | `src/handlers/file/delete.rs:275-287` | |
| 29 | +| Batch 에러 코드 개선 | ✅ 완료 | `src/handlers/file/batch.rs:270-302` | |
| 30 | +| Proto ErrorCode 정의 | ✅ 완료 | `proto/sync.proto:598-633` | |
| 31 | +| conflict_resolution 필드 | ✅ 완료 | `proto/sync.proto:581-586` | |
| 32 | + |
| 33 | +--- |
| 34 | + |
| 35 | +## 2. ErrorCode 기반 에러 분류 |
| 36 | + |
| 37 | +### 2.1 클라이언트 구현: `classify_error()` |
| 38 | + |
| 39 | +```rust |
| 40 | +pub enum ErrorCategory { |
| 41 | + Success, |
| 42 | + AlreadyDone, |
| 43 | + Transient { max_retries: u32 }, |
| 44 | + Conflict { max_retries: u32 }, |
| 45 | + Permanent { notify_user: bool, reason: &'static str }, |
| 46 | +} |
| 47 | + |
| 48 | +pub fn classify_error(error_code: i32) -> ErrorCategory { |
| 49 | + match error_code { |
| 50 | + 0 => ErrorCategory::Success, // SUCCESS |
| 51 | + 31 => ErrorCategory::AlreadyDone, // FILE_ALREADY_DELETED |
| 52 | + |
| 53 | + 70 | 90 | 1 => ErrorCategory::Transient { max_retries: 10 }, // STORAGE, DB, UNKNOWN |
| 54 | + 50 | 53 | 54 => ErrorCategory::Conflict { max_retries: 3 }, // REVISION, CONCURRENT, STALE |
| 55 | + |
| 56 | + 10 | 11 | 12 => ErrorCategory::Permanent { notify_user: true, reason: "Authentication" }, |
| 57 | + 51 => ErrorCategory::Permanent { notify_user: true, reason: "Path conflict" }, |
| 58 | + 71 | 72 => ErrorCategory::Permanent { notify_user: true, reason: "Quota exceeded" }, |
| 59 | + 21 | 20 | 22 | 23 => ErrorCategory::Permanent { notify_user: false, reason: "Invalid request" }, |
| 60 | + 91 => ErrorCategory::Permanent { notify_user: false, reason: "Schema error" }, |
| 61 | + |
| 62 | + 30 => ErrorCategory::Transient { max_retries: 5 }, // FILE_NOT_FOUND |
| 63 | + _ => ErrorCategory::Transient { max_retries: 5 }, // Unknown codes |
| 64 | + } |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +### 2.2 ErrorCode 참조 테이블 |
| 69 | + |
| 70 | +| ErrorCode | 값 | 용도 | 클라이언트 동작 | |
| 71 | +|-----------|-----|------|----------------| |
| 72 | +| **성공** | | | | |
| 73 | +| `SUCCESS` | 0 | 정상 완료 | 큐에서 제거 | |
| 74 | +| `FILE_ALREADY_DELETED` | 31 | 이미 삭제됨 | 성공으로 처리, 큐에서 제거 | |
| 75 | +| **일시적 에러** | | | | |
| 76 | +| `STORAGE_FAILED` | 70 | 저장소 오류 | 최대 10회 재시도 | |
| 77 | +| `DB_ERROR` | 90 | DB 오류 | 최대 10회 재시도 | |
| 78 | +| `UNKNOWN_ERROR` | 1 | 알 수 없는 오류 | 최대 10회 재시도 | |
| 79 | +| **충돌 에러** | | | | |
| 80 | +| `REVISION_CONFLICT` | 50 | 리비전 불일치 | 싱크 후 최대 3회 재시도 | |
| 81 | +| `CONCURRENT_MODIFICATION` | 53 | 동시 수정 | 싱크 후 최대 3회 재시도 | |
| 82 | +| `STALE_OPERATION` | 54 | 오래된 작업 | 싱크 후 최대 3회 재시도 | |
| 83 | +| **영구적 에러 (사용자 알림)** | | | | |
| 84 | +| `AUTH_FAILED` | 10 | 인증 실패 | 즉시 실패, 사용자 알림 | |
| 85 | +| `AUTH_TOKEN_INVALID` | 11 | 토큰 만료 | 즉시 실패, 사용자 알림 | |
| 86 | +| `PATH_CONFLICT` | 51 | 경로 충돌 | 즉시 실패, 사용자 알림 | |
| 87 | +| `QUOTA_EXCEEDED` | 71 | 용량 초과 | 즉시 실패, 사용자 알림 | |
| 88 | +| **영구적 에러 (로그만)** | | | | |
| 89 | +| `INVALID_REQUEST` | 21 | 잘못된 요청 | 즉시 실패 | |
| 90 | +| `PATH_INVALID` | 23 | 잘못된 경로 | 즉시 실패 | |
| 91 | +| `DB_SCHEMA_ERROR` | 91 | DB 스키마 오류 | 즉시 실패 | |
| 92 | + |
| 93 | +--- |
| 94 | + |
| 95 | +## 3. Delete 작업 멱등성 |
| 96 | + |
| 97 | +### 3.1 클라이언트 구현 |
| 98 | + |
| 99 | +```rust |
| 100 | +pub fn is_delete_success_error_code(error_code: i32) -> bool { |
| 101 | + matches!(error_code, 0 | 30 | 31) // SUCCESS, FILE_NOT_FOUND, FILE_ALREADY_DELETED |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +### 3.2 서버 응답 |
| 106 | + |
| 107 | +**Delete 요청 시 파일이 이미 없는 경우:** |
| 108 | +- `success: true`, `error_code: 0` (SUCCESS) 반환 |
| 109 | +- 클라이언트는 `error_code`가 0, 30, 31이면 성공으로 처리 |
| 110 | + |
| 111 | +--- |
| 112 | + |
| 113 | +## 4. 재시도 정책 |
| 114 | + |
| 115 | +### 4.1 재시도 상수 |
| 116 | + |
| 117 | +```rust |
| 118 | +const MAX_PERMANENT_ERROR_RETRIES: u32 = 0; // 영구적 에러: 즉시 실패 |
| 119 | +const MAX_TRANSIENT_ERROR_RETRIES: u32 = 10; // 일시적 에러: 최대 10회 |
| 120 | +const MAX_CONFLICT_ERROR_RETRIES: u32 = 3; // 충돌 에러: 최대 3회 |
| 121 | +``` |
| 122 | + |
| 123 | +### 4.2 백오프 전략 |
| 124 | + |
| 125 | +```rust |
| 126 | +const INITIAL_DELAY_MS: u64 = 1000; // 1초 |
| 127 | +const MAX_DELAY_MS: u64 = 300_000; // 5분 |
| 128 | +const BACKOFF_MULTIPLIER: f64 = 2.0; |
| 129 | +const JITTER_FACTOR: f64 = 0.1; // ±10% 랜덤 지터 |
| 130 | +``` |
| 131 | + |
| 132 | +| 재시도 | 대기 시간 | |
| 133 | +|--------|----------| |
| 134 | +| 1 | 1초 | |
| 135 | +| 2 | 2초 | |
| 136 | +| 3 | 4초 | |
| 137 | +| ... | ... | |
| 138 | +| 10 | 300초 (5분, 최대) | |
| 139 | + |
| 140 | +--- |
| 141 | + |
| 142 | +## 5. 사용자 결정 기반 재시도 시스템 |
| 143 | + |
| 144 | +`notify_user: true` 에러 발생 시 **AwaitingUserDecision** 상태로 전환됩니다. |
| 145 | + |
| 146 | +### 5.1 사용자 액션 |
| 147 | + |
| 148 | +| 에러 코드 | 가능한 액션 | |
| 149 | +|-----------|------------| |
| 150 | +| `PATH_CONFLICT` (51) | Overwrite, Rename, Skip | |
| 151 | +| `QUOTA_EXCEEDED` (71) | Retry, Skip | |
| 152 | +| `AUTH_*` (10-12) | Retry, Skip | |
| 153 | + |
| 154 | +### 5.2 Overwrite 지원 |
| 155 | + |
| 156 | +Proto에 `ConflictInfo.ResolutionStrategy`가 정의되어 있음: |
| 157 | +```protobuf |
| 158 | +enum ResolutionStrategy { |
| 159 | + MANUAL = 0; // 기본값 |
| 160 | + LAST_WRITE_WINS = 1; // 타임스탬프 기반 |
| 161 | + SERVER_WINS = 2; // 서버 우선 |
| 162 | + CLIENT_WINS = 3; // 클라이언트 우선 (= Overwrite) |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +**클라이언트 Overwrite 액션 = `conflict_resolution: CLIENT_WINS`로 요청 전송** |
| 167 | + |
| 168 | +### 5.3 Stale 작업 자동 정리 |
| 169 | + |
| 170 | +동기화 후 더 이상 필요 없는 `AwaitingUserDecision` 작업은 자동으로 정리됩니다: |
| 171 | + |
| 172 | +| 작업 유형 | 정리 조건 | |
| 173 | +|----------|----------| |
| 174 | +| Upload/Update | 서버에 동일/더 높은 리비전 존재 | |
| 175 | +| Delete | 서버에 해당 파일 없음 | |
| 176 | +| Download | 로컬에 동일/더 높은 리비전 존재 | |
| 177 | + |
| 178 | +--- |
| 179 | + |
| 180 | +## 6. Proto 정의 참조 |
| 181 | + |
| 182 | +```protobuf |
| 183 | +enum ErrorCode { |
| 184 | + SUCCESS = 0; |
| 185 | + UNKNOWN_ERROR = 1; |
| 186 | +
|
| 187 | + // Authentication (10-19) |
| 188 | + AUTH_FAILED = 10; |
| 189 | + AUTH_TOKEN_INVALID = 11; |
| 190 | + AUTH_ACCOUNT_MISMATCH = 12; |
| 191 | +
|
| 192 | + // Validation (20-29) |
| 193 | + VALIDATION_FAILED = 20; |
| 194 | + INVALID_REQUEST = 21; |
| 195 | + FILE_SIZE_MISMATCH = 22; |
| 196 | + PATH_INVALID = 23; |
| 197 | +
|
| 198 | + // File Operations (30-49) |
| 199 | + FILE_NOT_FOUND = 30; |
| 200 | + FILE_ALREADY_DELETED = 31; |
| 201 | + FILE_ALREADY_EXISTS = 32; |
| 202 | +
|
| 203 | + // Conflicts (50-69) |
| 204 | + REVISION_CONFLICT = 50; |
| 205 | + PATH_CONFLICT = 51; |
| 206 | + PATH_MISMATCH = 52; |
| 207 | + CONCURRENT_MODIFICATION = 53; |
| 208 | + STALE_OPERATION = 54; |
| 209 | +
|
| 210 | + // Storage (70-89) |
| 211 | + STORAGE_FAILED = 70; |
| 212 | + QUOTA_EXCEEDED = 71; |
| 213 | + FILE_SIZE_LIMIT = 72; |
| 214 | +
|
| 215 | + // Database (90-99) |
| 216 | + DB_ERROR = 90; |
| 217 | + DB_SCHEMA_ERROR = 91; |
| 218 | +} |
| 219 | +``` |
| 220 | + |
| 221 | +--- |
| 222 | + |
| 223 | +## 7. 구현 체크리스트 |
| 224 | + |
| 225 | +### 서버측 ✅ 완료 |
| 226 | +- [x] Delete 멱등성: 이미 삭제된 파일 → `success: true` 반환 |
| 227 | +- [x] Batch 에러 코드 개선: 에러 메시지 기반 ErrorCode 매핑 |
| 228 | +- [x] conflict_resolution 필드 지원 |
| 229 | + |
| 230 | +### 클라이언트측 |
| 231 | +- [x] `classify_error()` 함수 구현 |
| 232 | +- [x] 재시도 횟수 제한 (영구:0, 일시:10, 충돌:3) |
| 233 | +- [x] Delete 멱등성 처리 (`is_delete_success_error_code`) |
| 234 | +- [x] `error_code` 우선 분류 |
| 235 | +- [x] 사용자 결정 시스템 (`AwaitingUserDecision` 상태) |
| 236 | +- [x] Stale 작업 자동 정리 |
| 237 | +- [ ] 프론트엔드 UI 통합 |
| 238 | + |
| 239 | +--- |
| 240 | + |
| 241 | +**문의**: 연동 관련 질문이 있으시면 연락주세요. |
0 commit comments