얼쑤는 녹음 파일이 없어도 연습 기록을 저장할 수 있습니다. 또 저장 뒤 악보 버전이 바뀌면 기록은 남지만 현재 악보에서 어느 칸에 해당하는지 표시하지 않습니다. 연습 기록 API를 만들면서 이렇게 값이 비는 경우를 두 곳에서 따로 처리했습니다. SQL을 읽는 매퍼와 한 요청 안에서 조회를 줄이는 캐시였습니다.
| 구분할 곳 | 항목이 없음 | 항목이 있고 값이 비어 있음 |
|---|---|---|
| DB 조회 | 지울 세션이 없습니다. | 세션은 지울 수 있고 녹음 파일은 없습니다. |
| 요청 안의 캐시 | 악보 위치를 아직 조회하지 않았습니다. | 이미 조회했고 현재 악보에서 표시할 위치가 없습니다. |
녹음 없이도 남는 삭제 대상
삭제 API는 먼저 사용자가 지울 수 있는 기록인지 찾고, 녹음 경로가 있으면 파일 삭제도 처리합니다. 이때 recording_url만 조회하면 MyBatis의 기본 설정에서는 조회한 열이 전부 NULL인 행이 null로 반환됩니다. 결과만 보면 행이 없었는지, 행은 있는데 녹음 경로만 비었는지 구분할 수 없습니다.
조회 결과에 항상 값이 있는 세션 ID를 함께 넣었습니다. 반환 객체가 있으면 지울 기록이 있다는 뜻입니다. 그 안의 녹음 경로는 nullable로 두어 실제 파일에 대한 동작만 분기했습니다. 전역 매퍼 설정을 바꾸면 다른 쿼리에도 영향을 주므로, 두 사실이 모두 필요한 조회에서만 열을 추가했습니다. 실제 HTTP와 MySQL을 연결한 검사에서 녹음 없는 기록을 삭제하자 HTTP 200과 recordingDeleted: false가 돌아왔습니다.
두 프로필에서 MyBatis가 열 이름으로 Kotlin 결과 객체의 생성자 인자를 찾도록 설정했습니다. 삭제 조회는 id AS session_id와 recording_url을 각각 sessionId: Long과 recordingUrl: String?에 연결합니다. 세션 ID는 조회 결과에 행이 있음을 보존하고, 녹음 경로는 값이 있는지 판단하는 데 사용합니다.
조회한 null도 캐시
기록 목록에서 같은 곡의 같은 악보 버전에 대해 칸 위치를 반복해서 계산하지 않도록 요청 안에 맵을 뒀습니다. 그런데 Kotlin의 getOrPut은 저장된 값이 null이면 함수를 다시 호출합니다. 악보 버전이 맞지 않아 위치를 구할 수 없는 결과를 저장해도, 같은 버전의 다음 기록을 처리할 때 조회가 반복됐습니다.
맵의 containsKey로 키가 있는지를 먼저 확인하도록 바꿨습니다. 키가 있으면 이전 값이 null이어도 돌려줍니다. 검사는 같은 곡의 두 버전에서 기록을 두 개씩 준비했습니다. 위치가 나오는 버전의 두 항목에는 위치가, 버전이 다른 두 항목에는 빈 결과가 담겼고, 위치 조회 호출은 두 번이었습니다. 이 수치는 단위 테스트 대역의 호출 수입니다. 운영 DB에서는 조회량을 따로 측정하지 않았습니다.

댓글
댓글과 반응은 GitHub 계정으로 로그인해 남길 수 있어요.