단위 테스트를 위한 mini-memcached 개발기

Roy Ra
당근 테크 블로그
5 min readMar 14, 2022

안녕하세요, 당근마켓 채팅팀에서 서버 개발자로 근무하는 Roy(로이)에요.

제가 소속된 채팅 서비스를 포함한 당근마켓의 많은 서비스는 Go 언어로 개발되어 있는데요. API Server를 개발하다 보니, 자연스럽게 여러 가지 Caching 솔루션들을 도입해 사용하고 있어요. 그중 채팅팀에서는 대표적으로 Redis와 Memcached를 사용하고 있어요.

기존 방식

채팅팀에는 현재 시점을 기준으로 1,249개의 테스트 케이스가 존재해요. 그리고 이 테스트 케이스들에는 상황에 따라 로직만을 검증하는 단위 테스트도 있고, 외부 서비스와 연동해서 기대한 대로 동작하는지를 검증하는 통합 테스트도 있어요.

외부 서비스와 연동해야 하는 경우, 현재는 docker compose를 활용해 테스트 코드가 실행되기 전 관련된 서비스들을 docker container로 띄우고, 해당 container들에 연결해 올바른 동작을 검증하고 있어요.

기존 방식의 문제점

테스트 코드는 특정 로직 또는 기대하는 행위를 코드로 기술하고, 예상하는 대로 동작함을 보장해준다는 점에서 굉장히 유용해요. 따라서 저는 개인적으로 F.I.R.S.T principles of testing을 지키려고 노력하는데요. 기존 방식에는 이 방침을 지키지 못하는 대표적인 두 개의 문제점이 있었어요.

(1) Fast

이 규칙은 “개발자는 개발 주기(Development Cycle) 중 언제든지 부담없이 테스트 코드를 수행할 수 있어야 하며, 초 단위의 시간에 실행을 마치고 원하는 결과를 출력해낼 수 있어야 한다.” 는 규칙이에요. 하지만 docker container를 띄우는 순간, container 마다 port 한 개씩을 사용해야 하니, 조금의 불편함이 있었어요. 예를 들어 개발 환경을 위한 container들이 띄워져 있을 때, 테스트 코드를 수행해보기 위해 테스트 용 container들을 실행시키면 port 충돌이 나는 경우가 있었어요.

또한 적지만 container를 띄우는 리소스를 사용하다보니, 테스트 코드의 수행 시간과 거의 맞먹게 테스트 환경을 구축하는 데에도 시간이 꽤나 든다는 문제점도 있었어요.

(2) Repeatable

이 규칙에서는 “각 테스트는 해당 테스트 외의 어떠한 요인으로부터 영향을 받으면 안된다.” 는 말을 강조해요. 현재 약 1,200개가 넘는 테스트 케이스들을 병렬로 실행할 때 가끔씩 동일한 memcache에 동일한 key를 갖는 item을 참조하는 경우가 발생해 Race Condition으로 이어지고, 테스트가 실패하는 경우가 발생해요. 즉, 코드나 로직 상의 문제가 아니라 테스트 환경의 문제로 인해 간혹 테스트가 실패해 테스트 코드에 대한 신뢰도가 떨어지고 있어요. 또한 테스트가 실패하면 원인이 무엇인지 찾는 공수도 적지 않아요.

minimemcached 소개

채팅팀에서는 위의 문제점들을 어떻게 해결할지 생각하던 도중, miniredis라는 프로젝트를 접하게 되었어요. 이 프로젝트의 핵심은 “Go 테스트 코드를 위해 Go로 만들어진 redis” 라는 점이에요. 즉, redis를 테스트하기 위해 실제 redis를 실행 시키지 않아도 돼요.

miniredis 도입 후, CI 과정 중 테스트에 소모되는 시간이 1~2분 가량 줄게 되었어요. 이후, redis보다 더 간단한 인터페이스를 제공하는 memcached를 miniredis처럼 순수 Go로 만들어 테스트 코드에 사용해보자는 생각을 하게 되었고, 그렇게 해서 minimemcached가 만들어지게 되었어요.

개발 요구사항

minimemcached는 처음부터 오픈소스로 공개될 예정이었어요. 분명 miniredis처럼 memcached도 container를 띄우기에는 부담을 느끼는 수요가 있을 것이라고 생각했기 때문이에요. 따라서 처음부터 너무 고도화하기 보단, 최소한의 기능 구현만을 목표로 선택했어요.

따라서 minimemcached는 아래처럼 TCP 요청을 처리하는 Server struct와 memcached의 Item 을 가지는 MiniMemcached 구조체로 간단하게 이루어져 있어요.

이후 자주 사용되는 순으로 memcached의 command를 구현했어요.

Benchmark 결과

set, get, delete를 수행하는 동일한 테스트 케이스로 docker container로 띄운 memcached에 대해서, 그리고 minimemcached에 대해서 go test를 사용한 벤치마크를 수행해봤어요.

벤치마크 관련 코드는 여기에서 확인할 수 있어요!

benchmark results

벤치마크 결과, docker container로 띄운 memcached는 평균적으로 1733716 ns, minimemcached는 48003.2 ns로 minimemcached가 대략 36배 더 빠른 속도를 띄었어요.

테스트 코드 도입 후의 변화

테스트 코드에 minimemcached를 도입한 후, miniredis를 도입했을 때와 마찬가지로 소요되는 총 시간이 대략 1–2분 가량 감소했어요. 또한 기대한 대로 간혹가다 발생하는 race condition 등 외부 요인에 의해 테스트가 실패하는 상황이 없어졌어요. 이로써 테스트 코드에 대한 신뢰도를 높일 수 있게 되는 등 위에서 기존에 있던 두 가지 문제(Fast, Repeatable)을 모두 극복할 수 있게 되었어요.

마치며

minimemcached는 이제 갓 만들어진 신생 오픈소스 프로젝트에요. 당근마켓은 이 외에도 개발을 편리하게 도와주는 많은 오픈소스를 공개하고 있어요. 여러분의 기여는 언제나 환영이랍니다 🎉

--

--