코드 품질을 향상시키기 위해선 컨벤션, 단위테스트, 정적 분석 등 여러 가지를 고려해야 한다.

이번 글에서는 팀에서 코딩 스타일을 공통으로 관리하기 위해 고민했던 내용, 적용한 사례, 그리고 느낀 점까지 기록해보려 한다.


배경

프로젝트를 시작할 땐 BE 개발자가 3명이었다. 3명의 코딩스타일을 맞추기 위해 깊게 고민할 필요는 없었고 단순히 intelliJ 플러그인 중 하나인 ktlint를 각자의 IDEA에 설치해서 최소한으로 컨벤션을 맞추고 프로젝트를 진행했다.

 

그 후 동료들이 늘어났지만 변한 건 없었다. 각자의 담당영역이 나뉘어 있었고 코드리뷰를 할 때 코딩스타일 컨벤션까지는 세세하게 보지 않았다. 

 

그러다 최근 서비스에 대규모 리뉴얼 작업을 진행하게 되면서 여러 사람들이 엮인 로직을 작업하게 되었는데 내가 작업할 코드가 빨간 줄 투성이거나 혹은 작업한 코드의 코딩스타일이 바뀌어져 있는 경우가 많았다.

 

원인이 무엇일까 고민했고 내린 결론은 다음과 같았다.

 

첫 번째로 '코딩스타일 컨벤션 체크'를 안 하는 사람도 있었다.

하지 않더라도 (분명 ide에선 빨간 줄로 보일 텐데...) 코드가 동작하는데 문제는 없기 때문에 신경 쓰지 않는 건가 싶었다.

 

두 번째는 명확하진 않지만 ide 설정에서 변경했거나 ktlint 버전 차이 때문에 standard rule이 변경되어서 코딩 스타일이 차이가 있는 게 아닐까 싶었다.

 

그렇기 때문에 코딩스타일을 공통관리하고 컨벤션 체크에 강제성을 부여할 필요성을 느꼈다.


Ktlint

ktlint이 뭔지부터 다시 한번 집어봤다. 

ktlint는 kotlin 코드 스타일을 검사하고 적용하기 위한 정적 분석 도구로서 ktlint는 코드의 일관성과 가독성을 높이기 위해 kotlin 코딩 컨벤션을 적용하고 코드에서 발견된 문제점을 지적하고 수정하는 도움을 줍니다.

ktlint는 Gradle, Maven, Ant 및 Command Line Interface(CLI)와 같은 다양한 빌드 시스템과 통합할 수 있으며, IntelliJ IDEA, Android Studio, Visual Studio Code 등 다양한 통합 개발 환경에서도 지원됩니다.

Kotlin 프로젝트에서 ktlint 사용하면 코드의 일관성과 가독성을 향상시키고, 코드 리뷰 과정에서 발견되는 문제를 줄일 있다.

 

위에서 언급한 것과 같이 ktlint는 빌드 환경에서 사용할 수 있도록 libarary 형태가 있고 에디터에서 쉽게 사용할 수 있도록 만든 플러그인 형태가 있었다. 

 

Gradle을 이용해서 build pipeline에 적용한다면 공통의 코딩스타일 컨벤션을 가지고 코드 체크할 있고, 강제성을 추가 있어서 코드의 품질을 유지하는데 효과적일 이라고 생각이 들었다.


도입기

1.  특정 브랜치 (Develop , Master)에 build 할 때 CI에서 ktlint 컨벤션 체크  

방식: develop or master 브랜치에 push 후 build 될 때 컨벤션을 체크해서 실패 처리

결론: 실패된 작업을 수정하기 위해서 작업 브랜치부터 다시 진행을 해야 하는 불편함이 있고 혹시나 다른 사람들이 작업한 소스의 영향도 받을 수 있기 때문에 포기했다.

 

 

2.  작업 브랜치 Push 시점에 체크

방식: git hook pre-push를 이용해서 브랜치 push 시점에 체크, 컨벤션 오류일 경우 push 실패처리

결론: 코딩 스타일 컨벤션을 맞추기 위한 불필요한 commit 로그가 많아질 것으로 보임, 좀 더 빠른 시점에 체크하는 게 나을 것 같다고 판단함.

 

 

3.  Commit 시점에 체크

방식: git hook pre-commit을 이용해서 coomit 시점에 체크, 컨벤션 오류일 경우 commit 실패처리

결론: 결국 가장 작은 단위인 commit 할 때부터 컨벤션 체크를 하는 게 맞다고 생각, 이 방식으로 작업 진행


작업 

1. IntelliJ Plugin: https://plugins.jetbrains.com/plugin/15057-ktlint-unofficial-  (version "0.10.0")

  • 작성한 코드의 코딩 스타일 컨벤션을 실시간으로 확인해서 알려주는 기능.
  • Preferences > Plugins > Ktlint 설치

 

2. gradle 플러그인 https://github.com/JLLeitschuh/ktlint-gradle (version "11.3.1") + git hook pre-commit 세팅 

  • gradle 플러그인: 전체/부분 로직에 대한 코딩스타일 정적검사를 진행할 수 있음
  • pre-commit 스크립트: commit 이전에 진행되는 작업이 적힌 스크립트. - 해당 스크립트에 코딩스타일 컨벤션 검사 로직을 추가

 

  • gradle 플러그인 설치 & build
plugins {
		id("org.jlleitschuh.gradle.ktlint") version "11.3.1”
}



apply(plugin = "org.jlleitschuh.gradle.ktlint")

 

  • pre-commit 작성방법 
    • 1. ./gradlew addKtlintCheckGitPreCommitHook - 기본적으로 컨벤션 체크해 주는 pre-commit 스크립트를 만들어준다.
    • 2. .git/hooks/pre-commit 스크립트를 직접 생성하기. (feat. 킹갓제너럴 ChatGPT )
           컨벤션 불일치하는 라인을 표시하는 작업을 추가함.
#!/bin/sh
######## KTLINT-GRADLE HOOK START ########

CHANGED_FILES="$(git --no-pager diff --name-status --no-color --cached | awk '$1 != "D" && $NF ~ /\.kts?$/ { print $NF }')"

if [ -z "$CHANGED_FILES" ]; then
    echo "No Kotlin staged files."
    exit 0
fi;

echo "Running ktlint over these files:"
echo "$CHANGED_FILES"

diff=.git/unstaged-ktlint-git-hook.diff
git diff --color=never > $diff
if [ -s $diff ]; then
  git apply -R $diff
fi

echo "KTLINT CHECK START"
ktlint_output=$(./gradlew ktlintCheck -PinternalKtlintGitFilter="$CHANGED_FILES")
if [ $? -ne 0 ]; then
  echo "KTLINT CHECK FAIL"
  echo "check these files:"
  while read -r line; do
    if [[ "$line" =~ ^.*\.kt:[0-9]+:[0-9]+.*$ ]]; then
      echo "$line"
    fi
  done <<< "$ktlint_output"
  exit 1
fi


  echo "KTLINT CHECK SUCCESS"


if [ -s $diff ]; then
  git apply --ignore-whitespace $diff
fi
rm $diff
unset diff

  echo "KTLINT CHECK FINISH"
exit 0
#exit $gradleCommandExitCode
######## KTLINT-GRADLE HOOK END ########

 

3. .editorconfig 파일 설정

    • ktlint 기본 컨벤션 외에 프로젝트에 적용되는 예외 규칙들 작성 리스트 
    • 자세한 내용은 EditorConfig 참조. 
    • .editorconfig 파일이 없는 경우 기본룰 Ktlint 버전에 따라서 Standard rules 기본적으로 따르지만 버전이 변경이 되면 Standard rules 변경이 있기 때문에 root 디렉터리에 .editorconfig파일을 설정하는 것을 권장.


결과

commit 요청 클래스가 코딩스타일 컨벤션에 일치하는 경우: 정상적으로 커밋 성공

commit 요청 클래스가 코딩스타일 컨벤션에 일치하지 않는 경우: console에 에러 표시 & 불일치 라인과 이유 표시


추가적으로 고민해 볼 내용

표시되는 코딩 스타일 컨벤션 오류에 대한 고민 

코드 수정이 아니라 컨벤션 수정을 고려해야 할 수도 있음.

max-line-length가 70으로 설정되어 있어서 작성된 코드들을 줄 바꿈 해야 한다고 알려준다.

하지만 제안에 맞게 코드를 바꾸게 된다면 프로젝트 내 다른 코드들도 전부 변경해줘야 하기 때문에 max-line-length을 늘리는 것을 고려해 볼 수 있다.

 

ktlint 버전 차이에 의한 rule 차이

ktlint 버전은 계속 변하면서 kotlin standard rule도 바뀐다.

한 예로 ktlint 0.42.1 버전에선 rule에 걸리지 않았던 내용이 0.47.1 버전에서는 rule에 잡힘.

그렇기 때문에 꾸준한 버전 관리를 통해서 코딩스타일 컨벤션을 꾸준히 Update 해주는 것이 필요하다고 느꼈다.


마무리

간단할 줄 알았던 코딩 스타일 컨벤션 세팅을 작업하고 나니 생각보다 고려할게 많았고, 작업량에 비해서 시간도 많이 소요되었다. (대부분이 고민, 삽질 등..)

또한, 코딩스타일 규칙을 강제했더니 error를 해결하기 위해 코드 리펙토링을 진행할 수 밖에 없는 환경도 생겼다. (물론 제약이 생겨서 답답한면도 많았다.) 

하지만 명확한 정책을 세우고, 나뿐만 아니라 팀원들이 좀 더 나은 품질의 코드를 볼 수 있겠다는 생각에 뿌듯...

지속적으로 팀원들간에 협의를 통해서 개선해 나갈 예정이다.

 

함께 관리해야 하는 코드는 공통으로 관리할 수 있는 규칙과 그 규칙을 지킬 수 있는 환경을 만들어 주는 게 중요하다는 점을 배운 시간이지 않나 싶다.

레디스를 이용한 데이터 캐싱 도입기 - Part1에 이어서 서비스에 레디스 캐시를 적용 후 진행한 성능 테스트에 대해 이야기해보려고 한다.


서론

개발자라면 누구나 DB만 사용했을 때 보다 캐싱을 적용한 로직이 처리가 빠르다는 것을 안다.

하지만 추상적으로 받아들이는 것 말고 구체적으로 검증을 하고 싶었다.

이전과 비교해서 서비스 로직이 성능적으로 얼마나 좋아졌는지 확인하기 위해서 테스트를 진행했다. 

성능 테스트를 지원하는 도구는 여러 가지가 있지만 가장 익숙하고 간편한 nGrinder를 이용했다.


nGrinder

네이버에서 성능 측정을 목적으로 개발된 오픈소스 프로젝트이다. 이를 이용하면 서버의 부하를 테스트해서 성능을 측정할 수 있다.

nGrinder는 Controller와 Agent, 2가지 주요한 컴포넌트들로 이루어져 있다.

 

Controller는 성능테스트를 위한 웹 인터페이스를 제공한다. 테스트 프로세스들을 구성하고, 테스트 통계를 볼 수 있다.

그리고 테스트를 위한 스크립트를 생성하고 수정할 수 있도록 한다.

 

Agent는 타깃 서버에 부하를 주는 프로세스들과 스레드들을 동작시키는 역할을 한다.

그리고 타깃 시스템(서버)의 성능(CPU/Memory 등)을 모니터링할 수 있다. 

 

nGrinder 시스템의 구조

이번 글에서는 nGrinder에 대한 자세한 내용은 생략하고자 한다. 

좀 더 자세히 알고 싶다면 github의 userGuide 또는 다른 블로그들을 참조하는 것을 추천한다.


테스트

이제 테스트를 진행할 시간이다.

 

우선 시나리오는 두 개다.

1. 기존 로직에 대한 테스트 (DB)
2. 새로운 로직에 대한 테스트(Redis + DB) 

 

테스트 설정은 다음과 같이 하였다.

Target 서버: Local 환경, 개발서버
Vuser(virtural user로 동시에 접속하는 유저수를 의미) : 500명
테스트 시간: 10분

테스트 진행 시 겪은 시행착오

테스트를 진행했는데 생각보다 결과에서 차이가 없었다. 문제의 원인을 찾다가 redis를 잘못 적용했나 생각하며 꽤 오랜 시간 삽질을 했었다. 약간 창피하지만 혹시나 나와 같은 실수를 하는 사람을 위해 확인한 내용을 공유해 본다.

 

첫 번째는 테스트 데이터의 양이였다. API가 수행되는데 조회할 데이터 자체가 작다 보니 캐시 적용효과를 테스트에서 확인하기 어려웠다. 그래서 운영환경과 비슷하게 많은 데이터를 모집단으로 설정하는 게 필요하다 판단했고 테스트 관련 데이터는 운영환경에서 가져와서 처리했다. 

 

두 번째는 API 로직 이해 부족이었다. 첫 번째 원인을 수정하였는데도 두 가지 테스트에서 차이가 눈에 띌정도로 보이지 않았다. 확인해 보니 해당 API는 페이징 처리를 해두었는데 request에 paging 관련 값은 default 던지고 있었다. 그래서 계속 같은 데이터만 return 하는 구조였다. 이 원인은 테스트 스크립트의 api 호출 부분을 수정해서 처리하였다.

 

테스트환경, 데이터, 로직에 대한 정확한 이해를 기반으로
테스트를 진행해야겠다는 점을 다시 한번 느꼈던 상황들이었다. 😭😭

 

테스트 스크립트는 기본 template을 약간 수정해서 작성했다. 전체 코드는 아래 더보기로 확인하길 바란다.

  • Redis Test Script
더보기
import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
// import static net.grinder.util.GrinderUtils.* // You can use this if you're using nGrinder after 3.2.3
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPRequestControl
import org.ngrinder.http.HTTPResponse
import org.ngrinder.http.cookie.Cookie
import org.ngrinder.http.cookie.CookieManager

/**
* A simple example using the HTTP plugin that shows the retrieval of a single page via HTTP.
*
* This script is automatically generated by ngrinder.
*
* @author admin
*/
@RunWith(GrinderRunner)
class TestRunner {

	public static GTest test
	public static HTTPRequest request
	public static Map<String, String> headers = ["x-balcony-id":"BOMTOON_COM"]
	public static Map<String, Object> params = ["groupMenu":"SCHEDULE_ALL","isIncludeAdult":"true","contentsThumbnailType":"MAIN","isIncludeTen":"false","isIncludeTenComplete":"false","sort":"POPULAR"]
	public static List<Cookie> cookies = []
	@BeforeProcess
	public static void beforeProcess() {
		HTTPRequestControl.setConnectionTimeout(300000)
		test = new GTest(1, "http://localhost:8080")
		request = new HTTPRequest()
		grinder.logger.info("before process.")
	}

	@BeforeThread
	public void beforeThread() {
		test.record(this, "test")
		grinder.statistics.delayReports = true
		grinder.logger.info("before thread.")
	}

	@Before
	public void before() {
		headers.put("Authorization", "Bearer eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im1DbnNLTWhGNFZiMGJZK0g0amlwNWVSNHF4QTJwaHhQaDA3SlNwTm1Oc1pCZXJGS2Vkcmpidm05aVY2RzVkZkgiLCJzbnNQcm92aWRlciI6IkgxK3RUdU9EQ1lBQ3ZFKzdwNGJNR1E9PSIsImlzQWR1bHQiOiJjYUZmRU00Z0VIUmZXd215VDNCMXlnPT0iLCJ1c2VySWQiOiJYR3k1c1ByQmxFWmxpZXVNenp2cDZBPT0iLCJ1dWlkIjoiUERnelE3NVR5S3UzQWhpb2gzQTZtRHBheDhmQk10cUdnT3ZOL3hBa0VRVGN3V1QxQ1MyQVk3bmx6WWNtQ3dMQSIsImlhdCI6MTY3OTk2ODEzMywiZXhwIjoxNjgwMDU0NTMzfQ.ezMtHPpfpXEeQlF0aYxfyC8dZFgPGMfYMaeQkT6pl6Q")
		request.setHeaders(headers)
		CookieManager.addCookies(cookies)
		grinder.logger.info("before. init headers and cookies")
	}

	@Test
	public void test() {
		int page = (int)(Math.random()*500)
		params.put("page", Integer.toString(page))
		HTTPResponse response = request.GET("http://localhost:8080/v2/contents/group/new", params)

		if (response.statusCode == 301 || response.statusCode == 302) {
			grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", response.statusCode)
		} else {
			assertThat(response.statusCode, is(200))
		}
	}
}
  • DB Test Script
더보기
import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
// import static net.grinder.util.GrinderUtils.* // You can use this if you're using nGrinder after 3.2.3
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPRequestControl
import org.ngrinder.http.HTTPResponse
import org.ngrinder.http.cookie.Cookie
import org.ngrinder.http.cookie.CookieManager

/**
* A simple example using the HTTP plugin that shows the retrieval of a single page via HTTP.
*
* This script is automatically generated by ngrinder.
*
* @author admin
*/
@RunWith(GrinderRunner)
class TestRunner {

	public static GTest test
	public static HTTPRequest request
	public static Map<String, String> headers = ["x-balcony-id":"BOMTOON_COM"]
	public static Map<String, Object> params = ["groupMenu":"SCHEDULE_ALL","isIncludeAdult":"true","contentsThumbnailType":"MAIN","isIncludeTen":"false","sort":"POPULAR"]
	public static List<Cookie> cookies = []
	@BeforeProcess
	public static void beforeProcess() {
		HTTPRequestControl.setConnectionTimeout(300000)
		test = new GTest(1, "http://localhost:8080")
		request = new HTTPRequest()
		grinder.logger.info("before process.")
	}

	@BeforeThread
	public void beforeThread() {
		test.record(this, "test")
		grinder.statistics.delayReports = true
		grinder.logger.info("before thread.")
	}

	@Before
	public void before() {
		headers.put("Authorization", "Bearer eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im1DbnNLTWhGNFZiMGJZK0g0amlwNWVSNHF4QTJwaHhQaDA3SlNwTm1Oc1pCZXJGS2Vkcmpidm05aVY2RzVkZkgiLCJzbnNQcm92aWRlciI6IkgxK3RUdU9EQ1lBQ3ZFKzdwNGJNR1E9PSIsImlzQWR1bHQiOiJjYUZmRU00Z0VIUmZXd215VDNCMXlnPT0iLCJ1c2VySWQiOiJYR3k1c1ByQmxFWmxpZXVNenp2cDZBPT0iLCJ1dWlkIjoiUERnelE3NVR5S3UzQWhpb2gzQTZtRHBheDhmQk10cUdnT3ZOL3hBa0VRVGN3V1QxQ1MyQVk3bmx6WWNtQ3dMQSIsImlhdCI6MTY3OTk2ODEzMywiZXhwIjoxNjgwMDU0NTMzfQ.ezMtHPpfpXEeQlF0aYxfyC8dZFgPGMfYMaeQkT6pl6Q")
		request.setHeaders(headers)
		CookieManager.addCookies(cookies)
		grinder.logger.info("before. init headers and cookies")
	}

	@Test
	public void test() {
		int page = (int)(Math.random()*500)
		params.put("page", Integer.toString(page))
		HTTPResponse response = request.GET("http://localhost:8080/v2/contents/group", params)

		if (response.statusCode == 301 || response.statusCode == 302) {
			grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", response.statusCode)
		} else {
			assertThat(response.statusCode, is(200))
		}
	}
}

테스트 결과

테스트 결과는 다음과 같았다.

  • DB Test 결과

  • Redis Test 결과

 

TPS 테스트시간만 체크했다.

 

TPS는 초당 트랜잭션 완료수이다. 높을수록 성능이 높다는 것을 의미한다.

테스트 시간은 user가 request 한 시점에서 시스템이 response 한 시점, 즉 API 수행시간이다.

 

두 개의 결과에 대한 차이는 10 이상 것을 확인할 수 있었다.


결론

DB만 사용할 때보다 캐싱을 도입했을 때 성능적인 차이를 구체적인 수치로 확인할 수 있어서 좋았다.

나와 같은 멍청한 실수를 하지 않으려면 몇 가지만 신경 쓰고 테스트를 진행하길 바란다.

 

구현한 로직 프로세스, 모집단의 데이터의 , 서버 성능에 따라서 테스트의 결과는 확연하게 달라질 있다는을 인지하는 게 중요할것 같다. 

이번 글에서는 레디스를 도입할 때 고민했던 내용에 대해 공유해보고자 한다.


일반적으로 캐시는 메모리를 사용하기 때문에 DB 보다 훨씬 빠른 성능을 기대한다. 

하지만 올바르지 않은 방법으로 사용하게 된다면 데이터에 문제가 생길 수도 있고, DB만 사용하는 것보다 더 비효율적이 될 수도 있다. 

효율적으로 사용하기 위해서 캐시에 저장할 데이터의 기준확립 캐싱 전략 중요하다고 생각했다.


캐시 전략 패턴

캐시를 사용하게 되면 데이터의 정합성 문제에 직면하게 된다. 

서로 다른 두 저장소(메모리와 DB)에 데이터를 저장하게 되니 불일치가 발생할 가능성이 높다.

이 문제를 방지, 극복하기 위해선 적절한 캐싱 전략을 사용해야 하기 때문에 먼저 어떤 방법들이 있는지 찾아보았다.

 

크게 캐시는 읽기 전략과 쓰기 전략으로 나뉜다.

 

캐시 읽기 전략(cache read strategy)

1. Look Aside Pattern

  • 캐시 우선 확인 전략, 캐시에 없으면 DB조회.
  • 레디스 장애 시 DB에서 데이터를 가져올 수 있기 때문에 서비스에 문제없이 운영 가능.
  • 캐시 connection이 많이 있던 경우 순간적으로 DB에 부하가 몰릴 수 있음.
  • 반복적인 읽기가 많은 호출에 적합.

 

2. Read Through Pattern

  • 캐시에서만 데이터를 읽는 전략
  • cache miss가 많을 경우 look aside 전략보다 전체적인 성능이 느릴 수 있음.
  • 레디스 다운 시 서비스 이용에 문제가 생김. 이중화 구조 필요.

 

캐시 쓰기 전략(cache write strategy)

1. Write Back Pattern

  • 데이터를 DB에 바로 저장하지 않음.
  • 캐시에 모아놨다가 DB에 저장. (DB 쓰기 비용, 부하 줄일 수 있음)
  • Write가 빈번한 서비스에 적합.
  • 레디스 장애 시 데이터 영구 소실가능.

2. Write Through Pattern

  • 데이터를 캐시와 DB에 함께 저장하는 방식
  • 캐시와 DB의 데이터 일관성 유지 가능.
  • 데이터 유실 발생 가능성 적음.
  • 매 요청마다 두 번의 write 발생.
  • 빈번한 생성, 수정이 있는 서비스에서는 성능 이슈가 발생할 수 있음.

3. Write Around Pattern

  • 모든 데이터를 DB에 저장.
  • Write Through보다 훨씬 빠름.
  • 캐시와 DB 데이터 불일치 가능성 있음.

전략패턴 조합

캐시 읽기 전략과 쓰기 전략을 조합해서 사용한다.

 

ex)

  • Look Aside + Write Around
  • Read through + Write Around
  • Read through + Write Through

나의 도메인에 가장 적합한 방식은 Look Aside + Write Around라고 생각했다.

 

웹툰 서비스의 특성상

읽기가 빈번히 발생하고 레디스 장애 시에도 정상적으로 서비스 제공이 가능하기 때문에 Look Aside를 생각했고

상대적으로 뜸한 쓰기(작품 등록, 수정) 때문에 Write Around 적합하다고 생각했다.

 

 


캐싱 데이터의 기준

두 번째로 아래의 원칙을 가지고 캐싱 데이터를 정리하였다.


  • 자주 사용 되면서 자주 변경되지 않는 데이터를 추출.
  • 중요한 정보, 민감 정보는 저장하지 않음.
  • 휘발성 염두하여 데이터가 유실, 또는 정합성이 맞지 않아도 괜찮은 데이터 저장.

 

(간단하게 데이터를 표현하면 다음과 같다.)

AS-IS 콘텐츠 데이터:

성인작품여부
완결여부
제목
회차수
이벤트여부
오픈기간(시작, 끝)
뷰카운트
작가정보
썸네일정보
태그정보

우선 다른 도메인의 성격을 띠는 썸네일 정보, 태그 정보를 분리하였다.

그다음으로 상대적으로 자주 변경되는 데이터에 속하는 회차수, 이벤트 여부, 뷰카운트 정보를 분리하였다.

 

TO-BE 콘텐츠 데이터:

성인작품여부
완결여부
제목
오픈기간(시작, 끝)
작가정보

크게 변한 것은 없어 보이지만 원칙을 가지고 기준에 맞는 데이터만 남긴 부분이 추후 유지보수 할 때 용이할 것이라는 생각이 들었다.

또한, 캐시 관련 로직을 심플하게 작성할 수 있었다.


마무리

이번 경험을 통해서 수많은 문제 해결 방법 중에 하나의 적절한 방법을 결정하는 게 더 어렵다고 느꼈다.

나의 기준이 다른 도메인에서는 올바르지 않을 수 있다.

그렇기 때문에 문제를 해결하는 데 있어 중요한 것은 도메인에 대한 이해라고 생각했다.

 

Part2 글에서는 도입한 캐시를 가지고 진행한 성능 테스트에 대해 이야기해 보도록 하겠다.

 

참고 블로그

- 레디스를 이용한 캐싱 설계 전략

+ Recent posts