Skip to content

[BUG] 문서 삭제 오류 #47

@Yoo-SH

Description

@Yoo-SH

🐛 문서 삭제 기능 오류: DELETING 상태에서 멈춤

📋 문제 설명

문서 삭제 시 상태가 DELETING으로 변경되지만, 실제로 삭제가 완료되지 않고 무한히 DELETING 상태로 남아있습니다.

🔴 현재 동작 (버그)

  1. Admin UI에서 문서 삭제 버튼 클릭
  2. 문서 상태가 DELETING으로 변경됨
  3. 계속 DELETING 상태로 유지됨
  4. 문서가 실제로 삭제되지 않음
  5. 새로고침해도 여전히 DELETING 상태

✅ 예상 동작

  1. Admin UI에서 문서 삭제 버튼 클릭
  2. 문서 상태가 DELETING으로 변경됨
  3. 백엔드에서 삭제 처리 완료
  4. 문서가 DB 및 스토리지에서 완전히 제거됨
  5. UI에서 문서가 사라짐

🔍 재현 방법

단계:

  1. Admin UI 접속: http://localhost:3001
  2. 업로드된 문서 목록 확인
  3. 임의의 문서에서 삭제 버튼 클릭
  4. 문서 상태 확인

실제 결과:

상태: DELETING
→ 몇 초 대기...
→ 여전히 DELETING
→ 새로고침 후에도 DELETING
→ 문서가 삭제되지 않음

예상 결과:

상태: DELETING
→ 삭제 완료
→ 문서가 목록에서 사라짐

🖼️ 스크린샷

Before (정상):

[문서 목록]
├─ document1.pdf - COMPLETED
├─ document2.pdf - COMPLETED
└─ document3.pdf - COMPLETED

After 삭제 시도 (버그):

[문서 목록]
├─ document1.pdf - COMPLETED
├─ document2.pdf - DELETING  ← 여기서 멈춤!
└─ document3.pdf - COMPLETED

Expected After:

[문서 목록]
├─ document1.pdf - COMPLETED
└─ document3.pdf - COMPLETED
                   ↑ document2.pdf 삭제됨

🔧 영향받는 컴포넌트

Backend (Spring Boot)

  • Controller: SourceDocumentController.deleteDocument()
  • Service: 삭제 로직 처리
  • Repository: SourceDocumentRepository, DocumentChunkRepository

Database

  • PostgreSQL:
    • source_documents 테이블
    • document_chunks 테이블 (외래키 제약)

Storage

  • MinIO: 원본 파일 삭제
  • Elasticsearch: 인덱싱된 청크 삭제

🐞 가능한 원인

1. 외래키 제약 위반

-- document_chunks가 source_documents를 참조
-- CASCADE DELETE가 설정되지 않았을 가능성

확인 방법:

SELECT * FROM source_documents WHERE ingestion_status = 'DELETING';
SELECT * FROM document_chunks WHERE source_document_id = 'xxx';

2. Elasticsearch 삭제 실패

Elasticsearch에서 청크 삭제 중 에러 발생
→ 트랜잭션 롤백
→ 상태만 DELETING으로 남음

3. MinIO 파일 삭제 실패

MinIO에서 파일 삭제 권한 문제
→ 삭제 실패
→ 전체 삭제 프로세스 중단

4. 비동기 처리 문제

// 삭제가 비동기로 처리되지만
// 완료 콜백이 호출되지 않음
@Async
public void deleteDocument(UUID documentId) {
    // 삭제 로직...
    // 상태 업데이트 누락?
}

5. 예외 처리 누락

try {
    // 삭제 로직
    deleteFromElasticsearch();
    deleteFromMinio();
    deleteFromPostgres();
} catch (Exception e) {
    // 예외 발생 시 상태 복구 로직 없음
    log.error("Delete failed", e);
    // DELETING 상태로 남음!
}

🔬 디버깅 체크리스트

Backend 로그 확인

docker compose logs -f open-context-core | grep -i delete
docker compose logs -f open-context-core | grep -i error

Database 상태 확인

-- DELETING 상태 문서 확인
SELECT id, original_filename, ingestion_status, error_message
FROM source_documents
WHERE ingestion_status = 'DELETING';

-- 관련 청크 확인
SELECT COUNT(*)
FROM document_chunks
WHERE source_document_id = 'xxx';

Elasticsearch 확인

# 해당 문서의 청크가 여전히 존재하는지 확인
curl "http://localhost:9200/document_chunks_index/_search?q=sourceDocumentId:xxx"

MinIO 확인

# MinIO에 파일이 여전히 존재하는지 확인
curl http://localhost:9001
# 또는 MinIO Console에서 확인

💡 제안된 해결 방법

해결책 1: 트랜잭션 관리 개선

@Transactional
public void deleteDocument(UUID documentId) {
    try {
        // 1. 상태 변경
        updateStatus(documentId, IngestionStatus.DELETING);

        // 2. Elasticsearch에서 청크 삭제
        deleteChunksFromElasticsearch(documentId);

        // 3. MinIO에서 파일 삭제
        deleteFileFromMinio(documentId);

        // 4. PostgreSQL에서 청크 삭제 (CASCADE)
        documentChunkRepository.deleteBySourceDocumentId(documentId);

        // 5. PostgreSQL에서 문서 삭제
        sourceDocumentRepository.deleteById(documentId);

        log.info("Document deleted successfully: {}", documentId);

    } catch (Exception e) {
        // 에러 발생 시 상태를 ERROR로 변경
        updateStatus(documentId, IngestionStatus.ERROR);
        updateErrorMessage(documentId, "Delete failed: " + e.getMessage());
        throw new BusinessException(ErrorCode.DELETE_FAILED, e.getMessage());
    }
}

해결책 2: CASCADE DELETE 설정

DocumentChunk 엔티티 수정:

@Entity
@Table(name = "document_chunks")
public class DocumentChunk {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "source_document_id",
                nullable = false,
                foreignKey = @ForeignKey(
                    name = "fk_chunk_source_document",
                    foreignKeyDefinition = "FOREIGN KEY (source_document_id) " +
                        "REFERENCES source_documents(id) " +
                        "ON DELETE CASCADE"  // ← 추가!
                ))
    private SourceDocument sourceDocument;
}

또는 Flyway Migration:

-- V{version}__add_cascade_delete_to_chunks.sql
ALTER TABLE document_chunks
DROP CONSTRAINT IF EXISTS fk_chunk_source_document;

ALTER TABLE document_chunks
ADD CONSTRAINT fk_chunk_source_document
FOREIGN KEY (source_document_id)
REFERENCES source_documents(id)
ON DELETE CASCADE;

해결책 3: 재시도 로직 추가

@Retryable(
    value = {ElasticsearchException.class, MinioException.class},
    maxAttempts = 3,
    backoff = @Backoff(delay = 2000)
)
public void deleteDocument(UUID documentId) {
    // 삭제 로직...
}

해결책 4: 삭제 작업 순서 최적화

올바른 삭제 순서:
1. 상태를 DELETING으로 변경
2. Elasticsearch 청크 삭제 (검색 불가능하게)
3. PostgreSQL 청크 삭제 (메타데이터 제거)
4. MinIO 파일 삭제 (스토리지 정리)
5. PostgreSQL 문서 삭제 (완전 제거)
6. 커밋

실패 시:
- 롤백
- 상태를 ERROR로 변경
- 에러 메시지 기록

해결책 5: 수동 복구 스크립트

임시 해결책 (긴급):

-- DELETING 상태 문서 강제 삭제 (주의!)
BEGIN;

-- 1. 해당 문서 ID 확인
SELECT id, original_filename FROM source_documents WHERE ingestion_status = 'DELETING';

-- 2. 청크 삭제
DELETE FROM document_chunks WHERE source_document_id = '{documentId}';

-- 3. 문서 삭제
DELETE FROM source_documents WHERE id = '{documentId}';

COMMIT;

Elasticsearch 정리:

curl -X POST "http://localhost:9200/document_chunks_index/_delete_by_query" \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "match": {
        "sourceDocumentId": "{documentId}"
      }
    }
  }'

🧪 테스트 계획

단위 테스트

@Test
void deleteDocument_success() {
    // Given
    UUID documentId = createTestDocument();

    // When
    documentService.deleteDocument(documentId);

    // Then
    assertFalse(sourceDocumentRepository.existsById(documentId));
    assertEquals(0, documentChunkRepository.countBySourceDocumentId(documentId));
    // Elasticsearch 확인
    // MinIO 확인
}

@Test
void deleteDocument_elasticsearchFailure_shouldRollback() {
    // Elasticsearch 삭제 실패 시 전체 롤백 확인
}

통합 테스트

@Test
void deleteDocument_endToEnd() {
    // 1. 문서 업로드
    // 2. 인덱싱 완료 대기
    // 3. 삭제 요청
    // 4. 모든 스토리지에서 제거 확인
}

📊 우선순위

  • 심각도: 🔴 High (핵심 기능 오류)
  • 영향도: 사용자가 문서를 삭제할 수 없음
  • 긴급도: High (데이터 관리에 영향)

🎯 수용 기준

  • 문서 삭제 시 모든 관련 데이터가 완전히 제거됨
    • PostgreSQL: source_documents 삭제
    • PostgreSQL: document_chunks 삭제 (CASCADE)
    • Elasticsearch: 청크 인덱스 삭제
    • MinIO: 원본 파일 삭제
  • 삭제 실패 시 에러 메시지 표시
  • 삭제 성공 시 문서가 UI에서 즉시 사라짐
  • 삭제 중 에러 발생 시 상태가 ERROR로 변경되고 롤백됨
  • 로그에 삭제 과정이 명확히 기록됨

🔗 관련 코드

확인이 필요한 파일들:

  • core/src/main/java/com/opencontext/controller/SourceController.java
  • core/src/main/java/com/opencontext/service/DocumentDeletionService.java (존재한다면)
  • core/src/main/java/com/opencontext/repository/SourceDocumentRepository.java
  • core/src/main/java/com/opencontext/repository/DocumentChunkRepository.java
  • core/src/main/java/com/opencontext/entity/DocumentChunk.java (외래키 설정)

🏷️ Labels

  • bug 🐛
  • priority: high 🔴
  • component: backend
  • component: database
  • needs-investigation 🔍

👥 Assignees

  • Backend 담당자

추가 정보가 필요하시면 댓글로 남겨주세요.

Metadata

Metadata

Labels

bugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions