냉장고 기본 CRUD 이후 남아 있던 유통기한 기반 조회 API, 기존 냉장고 화면 API 연동, 사전 재료 검색 성능 개선을 진행했다.
작업 범위는 다음과 같다.
- #23 유통기한 임박 재료 조회 API 구현
- #24 만료 재료 조회 API 구현
- #25 기존 냉장고 화면 API 연동
- 사전 재료 검색 pg_trgm 인덱스 적용
1. 유통기한 임박 재료 조회 API
냉장고 화면에서는 사용자가 보관 중인 재료 중 유통기한이 얼마 남지 않은 항목을 빠르게 확인할 수 있어야 한다.
이를 위해 유통기한 임박 재료 조회 API를 구현했다.
GET /api/v1/fridge-items/expiring-soon
이 API는 로그인한 사용자의 냉장고 재료 중 삭제되지 않은 항목을 기준으로 조회한다.
냉장고 재료는 사용자별 데이터이기 때문에 단순히 전체 재료를 조회하면 안 되고, 반드시 현재 인증된 회원의 데이터만 조회해야 한다.
서비스에서는 인증 정보에서 가져온 이메일로 회원을 조회하고, 해당 회원의 memberId를 기준으로 냉장고 재료를 필터링했다.
private Member findMemberByEmail(String email) {
return memberRepository.findByEmail(email)
.orElseThrow(() -> new BadCredentialsException("회원을 찾을 수 없습니다."));
}
이 구조를 사용하면 컨트롤러에서는 인증된 사용자 정보만 서비스에 넘기고, 실제 회원 검증과 조회 기준은 서비스에서 일관되게 처리할 수 있다.
유통기한 임박 기준은 현재 날짜를 기준으로 가까운 만료일을 가진 재료를 조회하는 방식으로 처리했다.
조회 결과는 화면에서 바로 사용할 수 있도록 재료명, 카테고리, 수량, 유통기한, 메모를 포함한 응답 DTO로 변환했다.
단순히 FridgeItem 엔티티만 반환하지 않고 응답 DTO를 따로 둔 이유는 화면에서 필요한 데이터와 DB 엔티티 구조가 완전히 같지 않기 때문이다. 예를 들어 냉장고 재료 테이블에는 productId만 있고, 실제 재료명은 products 테이블에서 가져와야 한다.
2. 만료 재료 조회 API
만료 재료 조회 API는 이미 유통기한이 지난 냉장고 재료를 조회하는 기능이다.
GET /api/v1/fridge-items/expired
유통기한 임박 조회와 마찬가지로 현재 로그인한 회원의 데이터만 조회하도록 구현했다.
만료 재료는 다음과 같은 기준으로 판단한다.
- 삭제되지 않은 냉장고 재료
- 현재 로그인한 사용자의 재료
- expiryDate가 오늘보다 이전인 재료
이 API는 냉장고 관리 화면뿐 아니라 이후 냉파 점수, 통계, 만료 이력 관리와도 연결될 수 있는 기능이다.
따라서 단순 조회 기능이지만 사용자별 데이터 검증을 반드시 포함해야 했다.
3. 기존 냉장고 화면 API 연동
백엔드에서 냉장고 관련 API를 구현한 뒤, 기존 프론트 화면의 mock state 기반 흐름을 실제 API 호출로 교체했다.
연동한 주요 API는 다음과 같다.
GET /api/v1/fridge-items
POST /api/v1/fridge-items
PATCH /api/v1/fridge-items/{fridgeItemId}
DELETE /api/v1/fridge-items/{fridgeItemId}
PATCH /api/v1/fridge-items/{fridgeItemId}/use-all
PATCH /api/v1/fridge-items/{fridgeItemId}/use-partial
GET /api/v1/products/search?keyword={keyword}
GET /api/v1/categories
GET /api/v1/fridge-items/expiring-soon
GET /api/v1/fridge-items/expired
프론트에서는 fridgeApi 파일을 만들어 냉장고 관련 API 호출을 한 곳에서 관리하도록 했다.
export const fridgeApi = {
getItems: () =>
axiosClient.get('/api/v1/fridge-items').then(unwrap),
createItem: (data) =>
axiosClient.post('/api/v1/fridge-items', data).then(unwrap),
updateItem: (fridgeItemId, data) =>
axiosClient.patch(`/api/v1/fridge-items/${fridgeItemId}`, data).then(unwrap),
deleteItem: (fridgeItemId) =>
axiosClient.delete(`/api/v1/fridge-items/${fridgeItemId}`),
searchProducts: (keyword) =>
axiosClient.get('/api/v1/products/search', { params: { keyword } }).then(unwrap),
};
axiosClient를 사용한 이유는 인증 토큰 처리와 공통 응답 처리를 한 곳에서 관리하기 위해서다.
로그인 후 저장된 access token을 요청 헤더에 자동으로 붙여주기 때문에, 각 API 호출마다 직접 Authorization 헤더를 작성하지 않아도 된다.
냉장고 재료 등록/수정 모달에서는 사전 재료 검색 API를 연결했다.
사용자가 재료명을 입력하면 백엔드의 products/search API를 호출하고, 검색 결과 중 하나를 선택해야 냉장고 재료를 등록할 수 있도록 했다.
이 과정에서 중요한 문제가 하나 있었다.
등록/수정/사용 처리 API의 단건 응답에는 productName, productCategoryId가 포함되지 않았다.
하지만 화면 목록에서는 재료명과 카테고리명이 필요했다.
그래서 등록, 수정, 삭제, 사용 처리 이후에는 단건 응답만으로 화면을 갱신하지 않고 냉장고 목록을 다시 조회하도록 처리했다.
await fridgeApi.createItem(payload);
await get().fetchIngredients();
이 방식은 추가 API 호출이 한 번 더 발생하지만, 현재 구조에서는 가장 단순하고 안전한 방법이다.
화면에 필요한 데이터는 목록 조회 응답에 이미 맞춰져 있기 때문에, 별도의 복잡한 프론트 변환 로직을 만들지 않아도 된다.
4. 사전 재료 검색 성능 개선
사전 재료 검색 API는 다음과 같은 형태로 동작한다.
WHERE is_active = true
AND name LIKE '%keyword%'
이 검색 방식은 사용자가 재료명의 일부만 입력해도 검색할 수 있다는 장점이 있다. 하지만 LIKE '%keyword%'는 일반적인 B-Tree 인덱스를 효율적으로 사용하기 어렵다.
예를 들어 아래와 같은 일반 인덱스는 정확히 일치하거나 앞부분이 고정된 검색에는 유리하다.
CREATE INDEX idx_products_name ON products (name);
하지만 %두부%처럼 앞뒤가 모두 열려 있는 부분 검색에서는 일반 B-Tree 인덱스가 잘 활용되지 않는다.
이를 개선하기 위해 PostgreSQL의 pg_trgm extension과 GIN 인덱스를 적용했다.
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX IF NOT EXISTS idx_products_name_trgm
ON products
USING gin (name gin_trgm_ops);
pg_trgm은 문자열을 trigram 단위로 나누어 유사도 검색이나 부분 검색을 빠르게 처리할 수 있도록 도와준다.
GIN 인덱스는 이런 다중 토큰 기반 검색에 적합한 인덱스 방식이다.
이번에는 db/init.sql에도 해당 SQL을 추가해서, 새 DB를 초기화할 때도 같은 인덱스가 생성되도록 반영했다.
5. 실행 계획 비교
RDS에서 직접 EXPLAIN (ANALYZE, BUFFERS)를 사용해 실행 계획을 확인했다.
측정 결과는 다음과 같다.
| 조건실행 | 계획 | Execution Time |
| 인덱스 생성 전 실제 API 조건 | Seq Scan | 0.284 ms |
| 인덱스 생성 후 실제 API 조건 | Seq Scan | 0.223 ms |
| pg_trgm 강제 확인 | Bitmap Index Scan on idx_products_name_trgm | 0.653 ms |
현재 products 데이터는 약 1.4K건 수준이다.
이 정도 규모에서는 PostgreSQL planner가 GIN 인덱스를 사용하는 것보다 전체 테이블을 순차적으로 스캔하는 것이 더 저렴하다고 판단했다.
그래서 실제 API 조건에서는 여전히 Seq Scan이 선택되었다.
Seq Scan on products
Filter: (is_active AND name ~~ '%두부%')
하지만 enable_seqscan = off 조건에서 확인했을 때는 idx_products_name_trgm 인덱스가 정상적으로 사용되는 것을 확인했다.
Bitmap Index Scan on idx_products_name_trgm
Index Cond: name ~~ '%두부%'
따라서 이번 작업은 “현재 데이터 기준으로 검색 속도가 크게 개선되었다”라고 보기보다는, “부분 검색에 적합한 인덱스 구조를 적용하고, 데이터 증가에 대비한 검색 성능 개선 기반을 마련했다”라고 보는 것이 정확하다.
6. 정리
냉장고 도메인의 주요 흐름이 백엔드와 프론트에서 연결되었다.
냉장고 재료 등록, 조회, 수정, 삭제뿐 아니라 전부 사용, 일부 사용, 유통기한 임박 조회, 만료 조회까지 연결되면서 사용자가 냉장고 화면에서 실제 데이터를 기반으로 재료를 관리할 수 있게 되었다.
또한 사전 재료 검색은 LIKE '%keyword%' 구조의 한계를 고려해 pg_trgm 기반 GIN 인덱스를 적용했다. 현재 데이터 규모에서는 PostgreSQL이 Seq Scan을 선택했지만, 인덱스 사용 가능성을 실행 계획으로 확인했고 향후 데이터 증가에 대비할 수 있는 구조를 마련했다.
가장 중요했던 점은 단순히 API를 하나씩 구현하는 것이 아니라, 사용자별 데이터 검증, 화면 응답 구조, 프론트 상태 동기화, 검색 성능까지 하나의 흐름으로 연결하는 것이었다.
'개발 프로젝트 > [팀 프로젝트] 냉파마스터' 카테고리의 다른 글
| 냉장고 유통기한 자동 반영 보완 (0) | 2026.07.02 |
|---|---|
| 장보기 연동과 사전 재료 관리자 기능 정리 (0) | 2026.06.30 |
| 장보기 도메인 API 구현 정리 (0) | 2026.06.29 |
| 냉장고 재료 CRUD 및 사용 처리 API 구현 (0) | 2026.06.28 |
| 냉파마스터 BE 작업 정리: 사전 재료 검색 API / 카테고리 목록 조회 API (0) | 2026.06.27 |