어제는 발표자료를 만드느라 바빴다. 그래서 적을 내용도 없었고, 평소보다 늦은 시간까지 참여하였기 때문에 TIL은 없다. 오늘 아침도 어제에 이어서 발표자료를 만드느라 바빴기에 오늘의 CodeKata는 없다. 대신 어제는 CodeKata 2문제를 풀었기 때문에 이것을 살짝 언급만 하고 넘어가겠다.
어제 푼 문제는 각각 BFS, Hash 문제였다 앞의 하나는 그래프를 직접 만들어서 BFS를 돌리는 문제였기에 크게 어려운 것은 없었다. 다만 Hash 문제는 약간 까다로웠는데, 말이 Hash 문제지 속을 들여다보면, Sort 문제였기 때문이다. 이 문제를 풀면서 내가 아직도 Kotlin의 Sort를 잘 사용하지 못한다는 것을 깨닫게 되었다. Kotlin에서 SortBy는 Sort 기중을 정하는 것이고, SortWith는 Comparator 객체를 활용해서 Sort 하는 법을 적는다. 이것을 꼭꼭 명심해 두자.
이제, 오늘로써 발표도 다 마쳤다. 이제 1주일 반에 거친 프로젝트가 드디어 끝났다. 지난 프로젝트에서 아쉬웠던 점은 새로운 것에 도전을 하지 못했다는 점이였다. 그래서 이번에는 새로운 기술을 접하고 사용을 하였다. 다른 부트캠프 일지에서도 적었던 내용이지만, 프로젝트를 마친 기념 겸 기술을 되씹어보는 시간을 가져보자.
이번에 사용한 Redis는 처음 접해서 사용해는 보는 것이였다. Redis는 in-memory database 로써, 디스크가 아닌 메모리에 저장을 하는 휘발성 데이터베이스이다. 내 이 Redis를 사용하게 된 것은 멀티스레드 환경에서의 동시성 이슈를 해결하기 위해서이다. Redis는 싱글 스레드 기반이기 떄문에 동시성 이슈에서 자유롭다. 이 이슈가 발생하는 이유는 발표자료에 있는 ppt 페이지로 대채해도 무방할 것 같다. 내가 만든 페이지라서 다른 사람의 양해를 구하지 않아서 참 편리하다.
Redis에는 setnx란 명령어가 존재한다. 이 명령어는 key 값이 존재하지만 않으면 Parameter로 준 key와 value를 set 하고 true를 반환하고, 아니라면 단순히 false를 반환하는 명령어이다. 이 명령어를 사용해서 Spin Lock을 구현할 수 있다.
Spin Lock은 스레드가 Lock에 걸리면 반복적으로 Lock이 있는지 확인하는 시스템이다. 즉, 화장실 앞에서 대기하는 사람과 비슷하다.
while (!redisLockRepository.lock("Event", eventId))
{
Thread.sleep(100)
} // Lock 대기
try {
val event = getExistEvent(eventId)
if(participantRepository.existsParticipantByEventIdAndMemberId(eventId, loginMember.id))
throw AlreadyParticipatedEvent(ErrorCode.ALREADY_PARTICIPATED_EVENT)
if(!event.addParticipant()) throw EventIsClosedException(ErrorCode.EVENT_IS_CLOSED)
eventRepository.save(event)
return participantRepository.save(Participant(member = memberRepository.findByIdOrNull(loginMember.id), event = event))
.let { ParticipantsResponse.from(it) }
} finally {
redisLockRepository.unlock("Event", eventId)
} // Lock 해제
이렇게 해서 Thread Pool을 만들고 테스트 코드로 검증을 하였다.
describe("여러 개의 Thread가")
{
context("하나의 Event에 거의 동시에 참여하면")
{
it("정확한 인원이 참여한다.")
{
val threadCount = 100
addRandomMember(memberRepository, threadCount)
val eventService = EventServiceImpl(eventRepository = eventRepository, memberRepository = memberRepository,
redisLockRepository = redisLockRepository, participantRepository = participantRepository)
val eventId = eventRepository.save(EventEntity("This is tutor", 100)).id!!
val executorService = Executors.newFixedThreadPool(20)
val countDownLatch = CountDownLatch(threadCount)
repeat(threadCount)
{
executorService.submit{
try{
eventService.participateEvent(eventId, loginMember = LoginMember(it.toLong() + 1, "aaa"))
} finally {
countDownLatch.countDown()
}
}
}
countDownLatch.await()
eventRepository.findByIdOrNull(eventId)!!.participantCount shouldBe threadCount
}
}
}
이것까지 일단 내가 맡은 부분이다. 이렇게 보면 양이 많지는 않지만, 처음 접한 것이여서, 삽질을 하고 발표자료까지 만들다보니까 시간이 빠듯해졌다. 이 Spin Lock의 장점은 기본적인 Lettuce를 활용해서 만들기 쉽다는 것이다. 단점은 Redis 서버에 부담이 간다. 그래서, Redisson을 사용해서 만드는 것이 더 좋을 것이라 생각한다. 후에 Lock을 만들 일이 있다면 이 Redisson을 활용해 Lock을 구현을 해보고 싶다.
이번 프로젝트를 진행하면서 가장 좋았던 점은 다른 팀원 분들에게 큰 신경을 안 쓰고 내 코드에 열중할 수 있었다는 점이다. 거기다 새로운 기술을 도입하는 도전적이였던 프로젝트였던 것도 플러스 요인이다. 다만, "팀" 프로젝트로써는 미묘하다. 이번 조의 팀원 분들은 다들 잘하셔서 각자 코드를 짜느라 다른 팀원이 사용한 코드를 나는 아직도 제대로 이해하지 못하고 있다. 예로, 어떤 팀원은 Cache기능을 사용하셨는데, 나는 이 기능을 완전히 이해하지 못하고, 그냥 잘 짰겠거니 하고 넘어갔었다. 그리고 개발 상황 공유가 제대로 이루어지지 않아서 테스트를 제대로 하지 못한 것이 아쉽다. 최후반에 nGrinder에서 이상한 버그가 걸려서 수정을 해야함에도 시간이 없어서 그냥 넘어갔고, 시연도 못했다. 이 점이 정말로 정말로 아쉽다.
다음엔 좀 더 개발상황을 정기적으로 보고하는 분위기를 만들고, 다른 팀원 분들의 코드를 신경쓰자.
이제 다음주부터 최종 프로젝트다, 최종 프로젝트엔 더욱 힘을 줘서 만들자. 지금까지 겪은 팀 프로젝트를 통해 배운 것을 활용해서 좋은 프로젝트로 만들자. 화이팅.
'부트캠프 일지' 카테고리의 다른 글
부트캠프 63일차 후기 (1) | 2024.02.27 |
---|---|
부트캠프 62일차 일지 (0) | 2024.02.26 |
부트캠프 59일차 후기 (0) | 2024.02.21 |
부트캠프 58일차 후기 (0) | 2024.02.20 |
부트캠프 57일차 후기 (0) | 2024.02.19 |