Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
Today
Total
관리 메뉴

그리미

파일 공유 링크 삭제 로직 리팩토링 본문

카테고리 없음

파일 공유 링크 삭제 로직 리팩토링

시앤시 2024. 6. 2. 23:33

https://dev.mysql.com/doc/refman/8.4/en/delete.html

 

MySQL :: MySQL 8.4 Reference Manual :: 15.2.2 DELETE Statement

MySQL 8.4 Reference Manual  /  ...  /  SQL Statements  /  Data Manipulation Statements  /  DELETE Statement DELETE is a DML statement that removes rows from a table. A DELETE statement can start with a WITH clause to define common table expressions

dev.mysql.com

 


The DELETE statement deletes rows from tbl_name  and returns the number of deleted rows.
.

 

즉, delete 를 사용하면 삭제 행의 개수를 알 수 있다.

 

이를 이용해 파일 공유 링크 삭제 로직 리팩토링 해보자.

 

// 기존

@Component
@RequiredArgsConstructor
public class DeleteLinkScheduler {
	private static final int DELETE_SIZE = 100;

	private final FileShareRepository fileShareRepository;

	@Scheduled(cron = "0 0 3 * * ?")
	public void deleteExpiredLinks() {
		ZonedDateTime expirationTime = ZonedDateTime.now(ZoneOffset.UTC).minusHours(3);
		// 삭제 대상의 id 를 담는 리스트
        List<Long> expirationLinkIds;

		do {
        	// 삭제 대상의 id를 조회한다 (100 개 단위로 조회)
			expirationLinkIds = fileShareRepository.findExpirations(expirationTime, DELETE_SIZE);
			// 삭제 대상을 지운다
       		if (!expirationLinkIds.isEmpty()) {
				fileShareRepository.deleteByIds(expirationLinkIds);
			}
		} while (expirationLinkIds.size() == DELETE_SIZE);
	}
}

 

 

// delete returns the number of deleted rows 를 바탕으로 리팩터링

	@Scheduled(cron = "*/5 * * * * ?")
	public void deleteExpiredLinks() {
		ZonedDateTime expirationTime = ZonedDateTime.now(ZoneOffset.UTC).minusHours(3);
		Integer expirationLinkCnt;

		do {
			expirationLinkCnt = fileShareRepository.deleteByExpirations(expirationTime, DELETE_SIZE);
		} while (expirationLinkCnt == DELETE_SIZE);
	}

 

// 테스트 코드

@ExtendWith(MockitoExtension.class)
@DisplayName("파일공유 링크삭제 스케줄러 테스트")
class DeleteLinkSchedulerTest {
	@Mock
	private FileShareRepository fileShareRepository;

	@InjectMocks
	private DeleteLinkScheduler deleteLinkScheduler;

	@Test
	@DisplayName("파일공유 링크 삭제 성공 테스트")
	void deleteExpiredLinksSuccess() {
		Integer expiredLinkIds = 99;

		when(fileShareRepository.deleteByExpirations(any(ZonedDateTime.class), anyInt()))
			.thenReturn(expiredLinkIds);

		deleteLinkScheduler.deleteExpiredLinks();

		verify(fileShareRepository, times(1)).deleteByExpirations(any(ZonedDateTime.class), anyInt());
	}

	@Test
	@DisplayName("파일공유 링크 삭제 성공 테스트 - 삭제 대상이 딱 100개 일 때 두 번 호출 확인")
	void deleteExpiredLinksSuccessTwoIn100() {
		when(fileShareRepository.deleteByExpirations(any(ZonedDateTime.class), anyInt()))
			.thenReturn(100, 0);

		deleteLinkScheduler.deleteExpiredLinks();

		verify(fileShareRepository, times(2)).deleteByExpirations(any(ZonedDateTime.class), anyInt());
	}

	@Test
	@DisplayName("파일공유 링크 삭제 성공 테스트 - 두 번 호출 확인")
	void deleteExpiredLinksSuccessTwo() {
		when(fileShareRepository.deleteByExpirations(any(ZonedDateTime.class), anyInt()))
			.thenReturn(100, 10);

		deleteLinkScheduler.deleteExpiredLinks();

		verify(fileShareRepository, times(2)).deleteByExpirations(any(ZonedDateTime.class), anyInt());
	}
}

 

 

보다시피 삭제 대상이 딱 100개 남았을 떄는 쿼리가 한 번더 호출된다.

이를 해결하기 위해 101개를 객체를 조회하고 100개를 삭제하는 방식을 고려해 보았으나

삭제 행 뿐만아니라 삭제 대상 객체들도 메모리에 올라와야하는 데서 발생하는 추가적인 메모리와 조회 쿼리가 더 나간 다는 단점이 있어 현재 방식을 고수하기로 했습니다.