페이지네이션을 구현하려고 했다. 오프셋 페이지네이션의 경우 조회할 때 처음부터 데이터를 다 읽어와야하는 점과 중복된 게시글을 볼 수 있다는 점에서 아쉬운 점이 남는 방법이다. 그래서 커서 기반 페이지네이션을 구현하기로 했다. 처음에 cursor라는 개념을 알고 접근했으나 page, pageSize, cursor(오프셋과 혼동이 있었나봄)어딘가 복잡하고 어렵다는 생각이 들어 api를 실행하면서도 이게 맞나 확신이 들지 않았다.
그래서 처음에 skip과 take를 이용해서도 구현이 가능할것같아 그렇게 진행했다. 그러다 며칠이 지난 후 다시 커서 기반 페이지네이션을 확인하니 그제서야 확신이 들어 코드를 수정할 수 있었다.
아래는 수정 전 코드이고, 이 아래는 수정 후 코드이다.
수정 전
/* 게시물 목록 조회 page / lastSeenPage */
// 자치구 카테고리 선택 시 -> 조회 (없으면 전 자치구 조회)
router.get("/posts", async (req, res, next) => {
try {
const { page, lastSeenPage, categoryName, districtName } = req.query;
const findCategory = categoryName
? await prisma.categories.findFirst({ where: { categoryName } })
: null;
const findDistrict = districtName
? await prisma.districts.findFirst({ where: { districtName } })
: null;
const parsedPage = parseInt(page, 10) || 24; // 조회할 게시글 수
let posts;
if (!lastSeenPage) {
posts = await prisma.posts.findMany({
select: {
User: {
select: {
nickname: true,
},
},
Location: {
select: {
storeName: true,
address: true,
starAvg: true,
},
},
postId: true,
imgUrl: true,
content: true,
likeCount: true,
commentCount: true,
createdAt: true,
},
orderBy: { postId: "desc" },
take: parsedPage,
where: {
...(findCategory?.categoryId && {
CategoryId: findCategory.categoryId,
}),
...(findDistrict?.districtId && {
Location: { DistrictId: findDistrict.districtId },
}),
updatedAt: {
lt: new Date(),
},
},
});
} else {
posts = await prisma.posts.findMany({
select: {
User: {
select: {
nickname: true,
},
},
Location: {
select: {
storeName: true,
address: true,
starAvg: true,
},
},
postId: true,
imgUrl: true,
content: true,
likeCount: true,
commentCount: true,
createdAt: true,
},
orderBy: { postId: "desc" },
take: parsedPage,
where: {
...(findCategory?.categoryId && {
CategoryId: findCategory.categoryId,
}),
...(findDistrict?.districtId && {
Location: { DistrictId: findDistrict.districtId },
}),
updatedAt: {
lt: new Date(),
},
postId: {
lt: parseInt(lastSeenPage),
},
},
});
}
await getManyImagesS3(posts);
return res.status(200).json({ posts });
} catch (error) {
next(error);
}
});
수정 후
/* 게시물 목록 조회 */
// 커서 기반
router.get("/posts", async (req, res, next) => {
try {
const { page, lastSeenPage, categoryName, districtName } = req.query;
const findCategory = categoryName
? await prisma.categories.findFirst({ where: { categoryName } })
: null;
const findDistrict = districtName
? await prisma.districts.findFirst({ where: { districtName } })
: null;
const parsedPage = parseInt(page, 10) || 1;
const parsedPageSize = parseInt(page, 10) || 10;
const startIndex = (parsedPage - 1) * parsedPageSize;
const endIndex = startIndex + parsedPageSize;
const posts = await prisma.posts.findMany({
select: {
User: {
select: {
nickname: true,
},
},
Location: {
select: {
storeName: true,
address: true,
starAvg: true,
},
},
postId: true,
imgUrl: true,
content: true,
likeCount: true,
commentCount: true,
createdAt: true,
},
orderBy: { postId: "desc" },
take: parsedPage,
skip: lastSeenPage ? 1 : 0,
...(+lastSeenPage && { cursor: { postId: +lastSeenPage } }),
where: {
...(findCategory?.categoryId && {
CategoryId: findCategory.categoryId,
}),
...(findDistrict?.districtId && {
Location: { DistrictId: findDistrict.districtId },
}),
updatedAt: {
lt: new Date(),
}
},
});
await getManyImagesS3(posts);
return res.status(200).json({ posts });
} catch (error) {
next(error);
}
});
if문으로 분기를 나눴던것에 비해 훨씬 간결해졌다. 둘 다 같은 값을 받고 같은 결과값을 보여주므로 속도도 비슷한가라는 의문이 들었다. cursor를 이용하면 처음부터 데이터를 읽지않아 빨라진다고 어디선가 봤기때문에 생긴 의문이었다.
100장만 불러오기

100장 불러오기 , 42번 게시글 이후부터

100장만 불러오기

100장 불러오기 , 42번 게시글 이후부터

100장만 불러오기

100장 불러오기 , 42번 게시글 이후부터

근소한 차이지만 확실히 커서 기반 페이지네이션 방법이 빠르긴 했다.