비동기 로직 처리 속도 개선

최봉수·2023년 12월 13일
0

배포 전 로컬에서 혼자 사용하고 있는데 비동기 로직 처리가 너무 느리다...
스크랩 속도가 느린 것도 있지만 로직이 불필요하게 중첩되어 있다고 생각했다.
그래서 로직을 분리했고 결과적으론 렌더링까지 4~5초 정도 걸리던 로직이 약 3초 정도로 줄었다.

어떻게?


리팩토링 전에 슥슥 적었던 부분인데 요약하면
접속 후 스크래핑 ~ json 저장 까지 비동기 로직이 함수 하나에서 돌아가고 있었고,
한 곳의 업체(데이터)당 스크래핑 > 필터링 > json 저장을 3번 반복 했던 것이다.

고민하던 중 솔루션#1이 떠올라서 진행하였다.

로직의 분리

클라이언트

아래는 기존의 클라이언트 코드이다

기존

async function initializeMenuAndRender() {
	RESTAURANT_DATA.forEach(({ name, ig_name }) => createRestaurantElement(name, ig_name));

	startLoading();

	if (isNotLatest() && isUpdateTime() && isWeekday()) {
		const data = RESTAURANT_DATA.filter(({ ig_name }) => lunchData[ig_name].lastUpdate !== getCurrentDate());
		for (const { ig_name, ig_owner_id, filter } of data) {
			await updateMenuData(ig_name, ig_owner_id, filter);
		}

		const updatedData = await getUpdatedData();
		renderMenuImages(updatedData);
	} else {
		renderMenuImages(lunchData);
	}

	endLoading();
}

async function updateMenuData(ig_name, ig_owner_id, filterFunc) {
	try {
		const feedData = await useFetch(GET_FEED_DATA_URL, 'GET', { ig_name, ig_owner_id });
		const { url, id } = filterFunc(feedData);
		await useFetch(SET_MENU_DATA_URL, 'POST', { ig_name, url, id, lastUpdate: getCurrentDate() });
	} catch (error) {
		handleClientError(error, 'updateMenuData Function');
	}
}

문제는 저 updateMenuData() 함수였는데, 피드 데이터를 가져온 후에 가져온 데이터를 필터링해서 필요한 정보를 뽑아서 local static json 내용을 업데이트 할 수 있도록 서버로 보내 fs-module을 사용해 json을 수정한다.

왜 이렇게 했나 싶다..
아래는 개선한 코드이다.

개선

async function initializeMenuAndRender() {
	try {
		const notUpdatedData = RESTAURANT_DATA.filter(({ ig_name }) => lunchData[ig_name].lastUpdate !== getCurrentDate());
		const isNotLatest = notUpdatedData.length > 0;

		RESTAURANT_DATA.forEach(({ name, ig_name }) => createRestaurantElement(name, ig_name));
		startLoading();

		if (isNotLatest && isUpdateTime() && isWeekday()) {
			const data = notUpdatedData.map(({ ig_name, ig_owner_id, filter }) => updateMenuData(ig_name, ig_owner_id, filter));
			const menuData = await Promise.all(data);

			await useFetch(SET_MENU_DATA_URL, 'POST', menuData);

			const updatedData = await getUpdatedData();
			renderMenuImages(updatedData);
		} else {
			renderMenuImages(lunchData);
		}

		endLoading();
	} catch (error) {
		handleClientError(error, 'initializeMenuAndRender Function');
	}
}

async function updateMenuData(ig_name, ig_owner_id, filterFunc) {
	try {
		const feedData = await useFetch(GET_FEED_DATA_URL, 'GET', { ig_name, ig_owner_id });
		const { url, id } = filterFunc(feedData);
		return { ig_name, url, id, lastUpdate: getCurrentDate() };
	} catch (error) {
		handleClientError(error, `updateMenuData Function for ${ig_name}`);
		throw error;
	}
}

로직을 분리하면서 다른 것도 조금씩 수정했다.

  • 중복되는 조건식 개선(isNotLatest)
  • 기존에는 3개의 데이터 중 2개가 최신이고 1개만 업데이트가 필요한 경우에도 3개 모두 새로 업데이트를 진행하였다. 이 부분을 notUpdatedData를 통해서 최신이 아닌 데이터만 1차적으로 필터링하여 불필요한 요청을 하지않도록 개선하였다.
  • 기존에 순차적으로 처리되던 비동기 함수들을 map을 사용해서 비동기 함수가 담긴 배열을 생성하고, Promise.all() 을 이용해서 비동기 처리 속도를 소폭 개선하였다.
  • 기존에 updateMenuData() 안에 있던 json을 수정하는 함수를 분리해서 모든 스크랩이 완료된 후 실행되게 수정하였다.

서버

아래는 기존의 서버 코드이다

기존

app.get('/getFeedData', async (req, res) => {
	try {
		const { ig_owner_id } = req.headers;
		const scrapeData = await getScrapeData(`https://www.instagram.com/graphql/query/?query_hash=e769aa130647d2354c40ea6a439bfc08&variables={"id":"${ig_owner_id}", "first":12 }`);

		if (scrapeData.status !== 'ok') {
			throw new Error(scrapeData.message);
		}

		res.json(scrapeData);
	} catch (error) {
		handleServerError(res, error, '피드 데이터를 가져오는 중 오류가 발생했습니다.');
	}
});

app.post('/setMenuData', async (req, res) => {
	try {
		const { ig_name, url, id, lastUpdate } = req.body;
		const data = await readFile(LUNCH_MENU_DATA, 'utf8');
		const jsonData = JSON.parse(data);

		const isNotSameId = jsonData[ig_name].id !== String(id);
		const isNotSameLastUpdate = jsonData[ig_name].lastUpdate !== lastUpdate;

		// id 혹은 lastUpdate가 다를 경우에만 json 파일 내용 수정
		if (isNotSameId || isNotSameLastUpdate) {
			// id가 다를 경우에만 lastUpdate 수정
			jsonData[ig_name].lastUpdate = isNotSameId ? lastUpdate : jsonData[ig_name].lastUpdate;
			jsonData[ig_name].id = id;
			jsonData[ig_name].url = url;
			jsonData[ig_name].base64 = await getImageBase64(url);

			await writeFile(LUNCH_MENU_DATA, JSON.stringify(jsonData, null, 4), 'utf8');
			console.log('JSON 파일 내용이 성공적으로 수정되었습니다.');
		} else {
			console.log('JSON 파일은 이미 최신 상태입니다.');
		}
		res.sendStatus(200);
	} catch (error) {
		handleServerError(res, error, 'JSON 데이터를 수정하는 중 오류가 발생했습니다.');
	}
});

위 두 로직이 함수 1회당 한번씩 실행이 됐었다..
이전 포스팅 중에서 설명했던 적이 있으니 설명은 생략하겠다.

개선

app.get('/getFeedData', async (req, res) => {
	try {
		const { ig_owner_id } = req.headers;
		const scrapeData = await getScrapeData(`https://www.instagram.com/graphql/query/?query_hash=e769aa130647d2354c40ea6a439bfc08&variables={"id":"${ig_owner_id}", "first":12 }`);

		if (scrapeData.status !== 'ok') {
			throw new Error(scrapeData.message);
		}

		res.json(scrapeData);
	} catch (error) {
		handleServerError(res, error, '피드 데이터를 가져오는 중 오류가 발생했습니다.');
	}
});

app.post('/setMenuData', async (req, res) => {
	try {
		const data = await readFile(LUNCH_MENU_DATA, 'utf8');
		const jsonData = JSON.parse(data);

		for (const { ig_name, url, id, lastUpdate } of req.body) {
			const isNotSameId = jsonData[ig_name].id !== String(id);
			if (isNotSameId) {
				jsonData[ig_name].id = id;
				jsonData[ig_name].url = url;
				jsonData[ig_name].lastUpdate = lastUpdate;
				jsonData[ig_name].base64 = await getImageBase64(url);
			}
		}

		await writeFile(LUNCH_MENU_DATA, JSON.stringify(jsonData, null, 4), 'utf8');
		res.sendStatus(200);
		console.log('JSON 업데이트 완료.');
	} catch (error) {
		handleServerError(res, error, 'JSON 데이터를 수정하는 중 오류가 발생했습니다.');
	}
});

/getFeedData는 그대로이고 /setMenuData 해당 로직이 수정되었다.

  • 불필요한 조건식 제거(isNotSameLastUpdate 클라이언트에서 이미 검사했기에 굳이 필요 없음)
  • 기존에는 Object로 받던 데이터를 Array 형태로 받아서 for...of로 순회를 돌며 json과 일치하는 내용의 값을 수정하도록 했다. 이로써 3개의 데이터를 받던지 1개를 받던지 100개를 받던지 /setMenuData는 한번만 쏴주면 된다.
  • isNotSameId 조건은 이미 수정이 완료되어 json에 lastUpdate의 날짜가 오늘과 같으면 클라이언트에서 걸려서 애초에 request가 오진 않겠지만 혹시 모르니 넣어놨다. 결과적으로 클라이언트에서 날짜로 최신 상태를 체크해서 한번 걸러주고, 서버에서는 가져온 데이터의 매일 변하는 id 값으로 최신 상태를 체크해서 최대한 데이터의 최신 상태를 보장하도록 하였다.

정리

별거 아닌 수정이지만 실제로 데이터를 가져와서 렌더링하는 속도가 소폭 상승하였고,
데이터를 가져오고 렌더링 전까지 클라이언트에 스켈레톤UI를 추가하여 체감상 더욱 더 빨라진 느낌이 든다.

더 수정할 부분도 있고, 에러 처리도 개선해야 하지만 우선 이 정도면 배포하여 회사 사람들한테 보여줄 수 있을 거 같다.

다음 포스팅은 nas에 서버 세팅하는 포스팅이 될지, 후기로 완을 할지는 모르겠다.

profile
돈이 좋아

0개의 댓글