We Used List Everywhere — And It Broke Our API Design

And What Happened When We Finally Refactored Ours

medium.com

 

개발을 하다보면 수많은 곳에서 List 형태를 사용합니다.

왜 그런가?

  1. 쓰기 쉽다
  2. 대부분 효과 있음
  3. Collection Framework 의 일환으로 다양한 기능 제공

일종의 기본 소양처럼 사용하곤 합니다.

또 List 라는 자료구조는.. 다음을 내포합니다

  1. 순서가 중요하다.
  2. 변경 가능하다.
  3. 인덱스 접근이 예상된다.
public void sendNotifications(List<User> users) {
    for (User u : users) {
        notificationService.sendEmail(u);
    }
}

하지만 순서가 필요하지 않다면?, 인덱스 접근이 필요 없고 단순 순회만 한다면?
리스트를 사용해야할 이유가 있을까?, 또한 저 코드는 인자로 들어온 것이 가변 리스트인지, 불변 리스트인지 구분할 수 없습니다.

이러한 이유 때문에 글에서는  List<T>를 사용하기 보다는Collection<T>Interface에 의존하거나
Iteratable 을 메서드 시그니처로 사용하자고 주장합니다.

public void sendNotifications(Collection<User> users);
public void sendNotifications(Iterable<User> users);

 

public void sendNotifications(Stream<User> users);

이런식으로 코드 작성이 가능하겠죠

개인적으로는 Iterable<T> 이 마음에 드는데요, 단순하게 순회만 할꺼야! 라는 의도가 굉장히 명시적이라 생각합니다. 
또 들어오는 인자에 대해서 수정을 가하는 행동이 없음으로 가변/불변 리스트등의 사용에서도 자유롭지않을까요? 

글에서는 다음과 같은 체크리스트를 통해서 List<T>의 사용 여부를 다룹니다.

When You Should Actually Use List

There are valid cases! Use List<T> when:

You need to access elements by index (list.get(3))
You care about the exact order of items
You plan to mutate the collection (add/remove/sort)
You explicitly want to signal that caller should pass a List
Otherwise? Don’t.

✅ Our Refactor Checklist
Here’s how we audited all our method signatures:

Does the method use .get(index)? → Keep List<T>
Does the method add/remove elements? → Keep List<T>
Only iterates or filters? → Switch to Collection<T> or Iterable<T>
Works best with lazy evaluation or large data? → Use Stream<T>

Collection & Iterable 

Image

최상위에 Iterable 존재, Collection 은 이를 상속

List,Set,QueueCollection을 상속받고 Iterable

Image

따라서... Map의 경우 에러가 나는 모습을 볼 수 있습니다.

이런식으로 Collection으로 지정한다면, 확실히 Set,Queue 등 다양한 자료구조가 하나의 메서드를 재사용할 수 있음으로 장점이 있고

호출하는 쪽에서도 별도의 자료구조 변환을 해주지 않아도 사용 가능하기 때문에 충분히 매력적인 선택지라고 생각됩니다.

 

 

The Results Shocked Me — I Tried Writing Java Like It’s 2025

Legacy who? I pushed Java to the future — and it pushed back.

medium.com

 

자바를 2025년 답게 쓰자는 글 입니다.

몇몇 공감가고, 도입예정인 부분들에 대해서만 코멘트 남기려 합니다.

✨ Switched to Virtual Threads — Everything Changed

Virtual Thread (이하 VT)에 대한 사용이 가장 먼저 나오는데, 기본적인 개념은 있지만 사용해 본적은 없어
Dawn-Cs-Study 프로젝트를 하면서 사용해볼까 합니다.

VT의 개념자체가 자바가 사용하는 OS Thread를 VT를 핸들링하는 Carrier Thread로 사용하여 
한정된 스레드 갯수로 더 많은 스레드를 태우겠다는 것으로 알고있습니다.

 

Self Study • masiljangajji

Self Study

github.com

아래는 VT 도입 이전, Thread, Executor 관련해 공부하면서 남긴 내용들입니다.

Thead 쓸 때마다 생성해서 쓰면 안되나요?

기존에는 Thread 객체를 직접 생성해서 start() 해야 했음

Thread thread = new Thread(() -> {
    System.out.println("Hello World!");
    });

thread.start();  // 새로운 스레드 생성 후 실행 
thread.run();  // 기존 요청을 물고온 스레드로 실행 (스레드 생성 X)

이 방법은 조금 머리아픈게.. new Thread() 할 때마다 스레드가 생성 됨
start() 실행이 끝나면 OS Thread는 즉시 반납되지만, 자바 Thread 객체는 힙에남아서 공간을 점유함

또 한 번 실행이 끝난 Thread 객체는 다시 start() 못하며, 몇 개의 스레드가 만들어졌는지 예상하기 어려움.
실행자체는 큰 문제가 없을 수 있으나, 관리의 측면에서 생산성이 매우 떨어진다.

Executor Vs Executors

Thread 실행 정책을 추상화 하여 만든 Interface
new Thread() 를 직접 다루지 않도록 하기 위해 도입됨

public interface Executor {
    void execute(Runnable command);
}

스레드 생성, 스케줄링 전략은 구현체에 위임합니다.
ThreadPoolExecutor, ScheduledThreadPoolExecutor 등이 대표적인 구현체

        return new ThreadPoolExecutor(
                core,
                max,
                60, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(queueCapacity),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

그런데.. ThreadPoolExecutor는 생성자 파라미터가 너무 많아서 설정이 귀찮습니다.
그렇기 때문에 자주 쓰는 패턴을 간단히 팩토리 메서드로 제공하기 위해 Executors 유틸 클래스가 만들어집니다.

Executors.newFixedThreadPool(int nThreads);   // 고정 크기 스레드풀
Executors.newCachedThreadPool();              // 필요시 스레드 생성, 유휴 시 종료
Executors.newSingleThreadExecutor();          // 1개 스레드만 사용
Executors.newScheduledThreadPool(int core);   // 주기적 실행 지원

하지만.. 실무에서는 보통 세부적인 커스텀을 하기 때문에 ThreadPoolExecutor 만들어 쓰는 경우가 더 많습니다.

여기서 한발자국 더 나아가면 이제는 OS Thread 하나에 여러 VT를 태워서 쓰는 방식으로 갑니다.
더 적은 Thread로 더 많은 작업을 수행할 수 있겠죠?

🧬 Embraced Records + Sealed Classes

레코드를 사용하자! 너무나 맞는 말입니다.
보통 DTO를 다룰때 Record 사용하거나 VO 같은 녀석들을 다룰때 사용하지 않나 싶습니다.

개인적으로는 불변성을 지켜줘야 한다!, 얘는 불변해!, 이런 의미를 내포하고 싶을때도 사용합니다.

sealed 키워드는 사용은 안해봤는데요, 이번 기회에 알아봤습니다.

이 타입이 어떤 클래스/인터페이스가 상속할 수 있는지를 명시적으로 제한합니다.

public sealed interface Event 
    permits LoginEvent, LogoutEvent {}

public final class LoginEvent implements Event {
    // 더 확장 불가
}

public final class LogoutEvent implements Event {
    // 더 확장 불가
}

이런식으로 구현이 되어지는데, sealed의 구현체는 항상 final, sealed, non-sealed 중 하나여야 합니다.

언제 유용한가?

sealed는 하위 타입의 집합이 닫혀있음을 의미합니다.
따라서...

public sealed interface Event permits LoginEvent, LogoutEvent {}

public final class LoginEvent implements Event {}
public final class LogoutEvent implements Event {}

LoginEvent,LogoutEvent 외에 다른 이벤트가 나오면 안되는 경우, 컴파일 타임때 Event 를 구현하는 상황을 막을 수 있습니다.
DDD에서는 클래스들을 집합화 하고, 이를 Context로 묶는 경향이 있기 때문에 DDD를 도입한다면 명시적으로 나타내기 좋지 않을까? 생각도 듭니다.

또한 switch문과 결합시키는 패턴도 유용합니다.

sealed interface Event permits LoginEvent, LogoutEvent, SignUpEvent {}

record LoginEvent(String email) implements Event {}
record LogoutEvent(Long userId) implements Event {}
record SignUpEvent(String email, String nickname) implements Event {}

public class EventHandler {

    public String handle(Event e) {
        return switch (e) {
            case LoginEvent login   -> "로그인 처리: " + login.email();
            case LogoutEvent logout -> "로그아웃 처리: " + logout.userId();
            case SignUpEvent signUp -> "회원가입 처리: " + signUp.email();
        };
    }

    public static void main(String[] args) {
        EventHandler handler = new EventHandler();

        System.out.println(handler.handle(new LoginEvent("user@test.com")));
        System.out.println(handler.handle(new LogoutEvent(42L)));
        System.out.println(handler.handle(new SignUpEvent("new@test.com", "승재")));
    }
}

또한 Pattern Matching for switch문법은 JDK 17은 preview고 21부터 정식지원입니다.

들어가는 글

안녕하세요 오랜만에 글을 작성합니다.

블로그를 꾸준히 이어가겠다고 다짐했는데,
미디엄 기준 마지막 글 작성일이 5월, 티스토리 기준 2월인 것을 보면 반성하게도 되고
돌아보면 글을 쓸 시간조차 없을 만큼, 정말 가파르게 달려온 상반기였던 것 같습니다.

그만큼 많은 일들이 있었습니다.
우선 2018년에 입학한 학교를 2025년에 졸업하며 약 7년간의 대학 생활을 마무리했습니다.
또 최근에는 가비아 인턴십 전환에 실패하고, 카카오게임즈 최종 면접에서도 고배를 마셨습니다.

연달아 두 번의 실패를 겪고 나니 아쉽고 마음이 꺾이기도 했습니다.
특히 카카오게임즈 탈락 이후에는 며칠 동안 아무것도 하지 못한 채, 집에만 있었던 것 같아요.

그래서 나는 무엇을 배웠나?

실패는 늘 아프고 쓰라리지만 그만큼 크게 배웁니다.

가비아 인턴십 연계 실패에서는 "일을 대하는 자세를" 배웠고
카카오게임즈 최종 탈락에서는 "나를 온전히 이해하는 방법"을 배웠습니다.

가비아 인턴십을 진행하면서는 "어떻게 하면 좋은 평가를 받을 수 있지?"
"어떻게 하면 내가 전환이 가능하지?"라는 생각에 사로잡혀 있었습니다.

개발자로 어떻게 기여할 수 있을까 보다, 불안과 조바심이 앞섰죠.
결과적으로는 정직원 전환에 실패했고, 돌아보며 든 첫 생각은 "뭐가 그리 무서워서 안절부절 했을까?"였습니다.

어차피 결과는 컨트롤할 수 없는 일인데..
그 순간순간에 더 많은 걸 배우고, 얻고, 누려야 했다는 아쉬움이 남았습니다.

이번 카카오게임즈 최종 탈락 후에는 "그렇다고 생각하는 것과 실제로 그런 것은 다르다"를 깨달았습니다.
분명 내 이야기를 한 것 같은데, 그 안에 진짜 ‘나’는 없었던 것 같았어요.

잇단 두 번의 실패는 마치 두 다리가 부러지는 듯한 느낌이 듭니다.
하지만 동시에 이제는 정말 남의 평가가 아닌 스스로의 가치관과 생각을 말할 수 있다는 자신감이 생겼습니다.
다리가 부러지고 나서야 스스로 걷는 방법을 배우는 것 처럼요.

나는 왜 개발자가 되고 싶었나?

임원 면접에서 가장 어려웠던 질문이었습니다.
면접에서는 “해보니 재밌었다, 좋아하는 일은 더 잘할 수 있다고 믿는다”라고 대답했지만,
솔직히 말해 처음부터 개발을 좋아했던 건 아니었습니다.

대학교 전공도 큰 뜻이 있어서 선택한 건 아니었습니다.
남들이 다 가니까 그냥 무난하게 학교를 다녔고, 성적도 그저 그런, 평범한 학생이었죠.

그러다가 4학년 1학기가 됐을 때, 진로에 대한 고민을 하기 시작했습니다.
하지만 큰 뜻이 없이 학교생활을 했던 저에게 명쾌한 답은 없었고, 그 즈음 주목받기 시작한 개발자라는 직업을 선택하게 됐습니다.

하지만 그 순간 한 가지 다짐도 함께 했습니다.
그동안은 내가 큰 뜻이 없고 적당히, 무난하게만 살아왔으니 앞으로의 기간 동안은 정말 미친 듯이 달려보자
남들보다 뒤처진 만큼 더욱 몰입하고 밀도 있게 시간을 보내자고.

그렇게 NHN Academy에 지원해 서울에서 전라도 광주까지 내려가 10개월간 생활하며 Java라는 언어를 처음 접하고 Spring을 통해 웹 개발에 입문하게 됐습니다.

낯선 도시, 낯선 기술, 낯선 환경.
하지만 그 안에서 정말 몰입하며 누구보다도 열심히 배워왔다고 자부할 할 수 있습니다.

그 이후로는 블로그에 기록했듯, IT 커뮤니티 활동, 사이드 프로젝트, 해커톤, 오픈소스 기여까지
제 진심이 향하는 방향으로, 계속 배우고 나아가고 있습니다.

마무리하며

제 블로그에 방문해 주시는 분들은 대부분 개발자를 준비하시는 대학생분들이 많습니다.
여러분들은 왜 개발자가 되고 싶나요? 그 나름의 답을 찾아가실 수 있다면 좋겠습니다.

그 답이 처음부터 선명하지 않아도 괜찮고, 누군가의 말에 흔들릴 수도, 때론 실패에 무너질 수도 있습니다.
저도 그 과정을 지나오고 있고, 여전히 배우고 있는 중입니다.

하지만 분명한 건, 자기 속도와 방향으로 꾸준히 걸어가는 사람은 반드시 성장한다는 것입니다.
저 역시 제가 지나온 길을 공유하며, 조금이라도 그 여정에 도움이 되는 개발자가 되고 싶습니다.

앞으로도 함께 고민하고, 기록하고, 나누는 사람이 되겠습니다.
읽어주셔서 감사합니다.

들어가는 글

안녕하세요 최근 정말 좋은 일이 있었는데요 백엔드 포지션으로 가비아에 합류하게 됐습니다.
이번글은 합류의 과정과 제 개인적인 생각을 작성하려 합니다.

서비스 회사에 가고싶다

블로그에 따로 포스팅하거나, 이력서에 기재하지는 않았지만 SI 회사에서 인턴근무를 한 경험이 있습니다.
SI의 특성상 프로젝트의 생명주기가 짧으며 기한을 맞추는것이 핵심이기 때문에 일을 배우면서 성장하기에는 어려운 환경으로 느껴졌습니다.

생각했던것 보다 성장에 대한 욕심이 크다는걸 알게됐고 "나는 무조건 서비스회사에 가야겠구나" 다짐했던 기억이납니다.
이력서또한 SI/SM 서비스를 주로하는 기업에는 작성하지 않았으며 흥미를 느끼고 성장할 수 있겠다 생각되는 기업의 공고에만 작성했습니다.

작년에는 광탈 올해는 합격

가비아에는 작년 겨울에도 이력서를 접수했었는데요 서류에서 바로 탈락했던 기억이 납니다.
이때는 이력서를 작성하는 방법과 직무적합성을 나타내는 방법에 대한 이해가 많이 부족했습니다.

이력서란 기본적으로 나 자신을 세일즈 하는 것인데 그런 부분들이 들어나지 않았거든요
다만 표현의 방법을 바꾸고 강조하고 싶은 부분만을 보여줌으로써 다른 결과를 만든 것 같습니다.

동일한 프로젝트를 설명하는 이력서

첫 출근 후 느낀점

서로가 서로를 존중한다는 느낌을 받았습니다.
이 느낌이 굉장히 중요하다 생각하는데요, "돈 받으니까 일해야지" 당연히 맞는 말이지만 좋은 방식은 아니라 생각합니다.

"나의 일이 아닌 남의 일을 돈받았으니까 해준다" 라는 인식이 생기는 순간 동기부여가 되지 않습니다.
회사는 내것이 아니고 회사에서 처리하는 일또한 회사의 일이지만 마치 나의 일인것 처럼 생각하게 만드는 것이 좋은 회사의 덕목이라 생각하거든요

저는 자취를하며 월세를 살고있는데요, 이 집은 제 것이 아니기 때문에 방치하고 더럽게 살아도 아무런 문제가 없습니다.
하지만 대부분의 사람들이 마치 "나의 집" 처럼 청소도하고 꾸미기도 하며 내가 속해있는 환경을 나의 마음에 맞게끔 가꾸고 아껴주려고 노력합니다.

회사의 일도 비슷한 것 같습니다.
평생직장은 무의미해졌고 회사는 나의 소유가 아니지만, 그 울타리 안에서 일을 하는 동안에는 서로를 존중하며 열심히 일하는것이 서로에게 좋은 것 아닐까요?

많이 물어보시는 질문 - 개발자도 어학성적, 자격증 필요한가요?

서비스 회사는 없어도 되는 것 같습니다만, 보다 전통적인 산업의 기업 혹은 SI/SM 을 주로하는 경우에는 필요합니다.
개인적으로는 정보처리기사 정도만 필수로 취득하시면 될 것 같습니다.

마무리하며

제 블로그를 방문해주시는 대부분의 분들은 개발자 직무를 희망하거나 취업을 준비중이신 대학생 분들이 많은 것 같습니다.
특히나 NHN Academy , Univ 의 지원 시즌이 되면 조회수가 폭발적으로 나오는데 상당히 기쁩니다.

누군가에게 도움이 된다는 사실이 참 좋은 것 같아요, 감사합니다.

 

조금 늦은 2024년 회고

설 전까지는 2025 BETA 버전이기 때문에 정식 릴리즈된 지금 시점에 회고를 작성합니다.
2024년은 아쉬운 부분도, 만족하는 부분도 공존하지만 나름의 성과를 이뤄냈던 시간인 것 같습니다.

2024, 개발자스러운 것들

2024년은 새로운 도전의 연속이었습니다.

NHN Academy 프로젝트 과정 수료후 개발에 대해 자신감이 생겼고, 이를 토대로 어떤 활동이든 도전해보자! 라는 목표가 생기게 됐습니다.
특히나 개발자스러운 것에 굉장히 많은 도전을 했습니다.

 

2024년 , 성장을 만든 IT 활동 | Notion

SIPE 3기

sprinkle-place-c1a.notion.site


처음으로 컨퍼런스도 참석해 내심 부러웠던 스티커도 받아서 붙여보고… 해커톤을 수상하고, 발표도 하고, 오픈소스에 기여하는 등
좋은 개발자란 무엇이고 어떻게 하는 것이 가파른 성장을 이끌 수 있는진 모르겠으나 그냥 개발자스러운 것을 하면 그게 좋은 개발자 아닌가?라는 생각으로 달려온 것 같습니다.

한마디로 2024년은 개발자스러워지기 위한 과정이었습니다. 개발에 대해 아무것도 몰랐기 때문에, 역설적으로 정말 가파르게 성장할 수 있었습니다.

2025, 새로운 키워드 ‘꾸준함’

2025년은 뛰어난 개발자가 되는 과정이고 싶습니다. 개발자스러운 사람 말고… 그냥 뛰어난 개발자 말이죠.
그러기 위해서 가장 필요한 것은 꾸준함인 것 같습니다.

개인적으로 단기간에 몰입하고 성과를 내는것은 잘하지만 꾸준하게 이어나가는 뒷심은 부족하다 생각합니다.

깃허브 내역만 봐도.. 커밋을 많이 한 기간과 하지 않는 기간이 확연히 차이 나는 것처럼요

 

마무리하며

처음 블로그를 시작했을 때 아무도 봐주지 않아 친구들한테 링크를 보내면서 겨우겨우 조회수를 올렸던 기억이 납니다.

지금은 친구들이 방문해주지 않아도 꽤나 많은 분들이 방문해 주시는 것 같습니다.
어쩌면 첫 번째 꾸준함은 블로그였나 봅니다.

내가 작성하는 글들이 누군가에게 정보가되고 경험을 공유할 수 있다는 것이 정말 큰 힘이 됩니다.
모두 행복하세요

GPT 요약

My-Music-Note 프로젝트에서 AWS 기반 고가용성 아키텍처를 설계하고, Auto Scaling Group, Application Load Balancer, CodeDeploy를 활용해 검증을 진행했습니다.
성능 테스트 도구로 JMeter를 시도했으나 자원 소모와 불필요한 기능으로 Artillery로 전환하여 소규모 부하 테스트를 성공적으로 수행했습니다.
테스트 결과, 단일 EC2 환경에서는 부하 증가로 많은 실패가 발생했지만, My-Music-Note 아키텍처에서는 100% 성공과 낮은 레이턴시를 보여 고가용성을 입증했습니다.

My-Music-Note 프로젝트에서의 경험을 다룬 글입니다.

My-Music-Note 프로젝트에서 유일한 백엔드 개발자로서 AWS 기반 인프라 구축에 집중하였습니다.
이번 글에서는 제가 구현한 AWS 기반 고가용성 아키텍처를 검증하고 테스트한 과정을 공유하려 합니다.

고가용성 아키텍처 설계

이전 포스팅에서 고가용성 아키텍처를 설계할 때 고려했던 3가지 기준을 소개했습니다.

https://masiljangajji-coding.tistory.com/88

이 기준을 바탕으로 Auto Scaling Group(ASG), Application Load Balancer(ALB), 그리고 CodeDeploy를 사용하여 인프라를 구축했습니다. 그러나 설계하고 배포하는 것만으로 고가용성이 보장된다고 할 수는 없습니다.

따라서 실제로 고가용성을 충족하는지 검증이 필요했고 이를 위해 성능 테스트를 진행하기로 했습니다.

성능 테스트 툴 선택 - JMeter에서 Artillery로 전환

JMeter의 도입과 한계

처음에는 널리 알려진 성능 테스트 툴인 JMeter를 선택했습니다.
JMeter는 실무에서도 많이 사용되는 도구로 자바 개발자들 사이에서는 표준이 되는 툴이라 생각했기 때문입니다.

하지만 실제로 사용해보니 우리 서비스에는 적합하지 않다 판단했습니다.

  1. 무거운 자원소모
    • JMeter는 JVM 기반 특성상 상당한 메모리를 소모
  2. 대규모 부하 테스트의 불필요성
    • 테스트 환경의 EC2 인스턴스(t4g.nano)는 고성능 서버가 아니었기에 JMeter의 장점인 대규모 부하 테스트 또한 불필요
  3. 결과 시각화가 제한적
    • 분석이 필요한 경우 별도로 CSV를 내보내서 분석해야 함

켜놓기만해도 RAM을 엄청 잡아먹는다..

Artillery로의 전환

따라서.. 보다 가볍고 소규모 테스트에 적합한 대안을 찾아봤으며 최종적으로는 Artillery로 전환하게 됐습니다.

Artillery는 소규모 부하 테스트에 적합하며 자원 소모가 적은 경량 툴입니다. YAML 파일로 테스트 작성이 가능한데 이는 Spring Boot 프로젝트에서 properties를 관리할 때 사용하던 방식과 유사해 익숙하게 느껴졌습니다.

Locust와 K6 같은 다른 경량 테스트 툴도 검토했지만 각각 Python과 JavaScript로 테스트 스크립트를 작성해야 한다는 점에서 Artillery를 선택했습니다.

테스트 환경과 시나리오 설정

테스트 환경

테스트는 다음과 같은 AWS 리소스를 기반으로 진행되었습니다

  • EC2 : t4g.nano
  • RDS : db.t4g.micro

테스트 시나리오

사용자가 인덱스 페이지 방문 후 로그인하는 시나리오를 작성했으며 트래픽 부하에 따라 Auto Scaling Group과 Application Load Balancer가 요청을 적절히 분산시키는지를 확인하기 위해 5분간 점진적으로 요청을 증가시킨 후 1분간 유지하도록 설정했습니다.

동일한 시나리오에 대해서 target만 EC2 , ALB로 변경해 테스트

테스트 결과

단일 EC2 환경에서 테스트를 수행한 결과는 다음과 같았습니다.

Artillery Metric
요청이 증가함에 따라 time out 발생
AWS Metric , 요청이 증가함에 따라 Network I/O 증가

타임아웃 오류 : 전체 요청의 37.6%(14,766건)
작업 실패율 : 가상 사용자의 54.7%(14,766명)

시간이 갈수록 늘어나는 요청에 따라 부하가 걸리는 것을 볼 수 있습니다.

My-Music-Note 아키텍처에서의 결과

ASG, ALB, CodeDeploy로 구성된 My-Music-Note의 고가용성 아키텍처에서는 다음과 같은 결과를 얻었습니다.

Artillery Metric
99% 요청에 대해 레이턴시 25.8ms
AWS Metric , ASG에 의해 트래픽이 분산돼 Network I/O 하락

타임아웃 오류: 0%
작업 실패율: 0%
99% 요청에 대해 레이턴시 25.8ms

이 결과는 My-Music-Note 아키텍처가 실제로 고가용성 기준을 충족하고 있음을 입증했습니다.

마무리

이번 성능 테스트는 고가용성 아키텍처를 검증하는 데 성공적이었지만 매우 간단한 시나리오에 대해서만 테스트해 API 레벨에서의 병목은 확인할 수 없었습니다.

이 부분에 대해서 개선할 여지가 남아있으며 앞으로도 지속적으로 배움과 개선을 이어나가겠습니다.

같이 보시면 좋은 발표 영상입니다.

 

GPT 요약

My-Music-Note 프로젝트에서 SonarCloud와 GitHub Actions 같은 SaaS 도구를 활용해 서비스 비용을 절감했습니다.
Bastion Host 대신 AWS Systems Manager를 도입해 보안을 강화하고 네트워크 비용을 줄였습니다.
이러한 선택으로 비용 효율성과 운영 효율성을 모두 확보할 수 있었습니다.

My-Music-Note 프로젝트에서의 경험을 다룬 글입니다.


이전 프로젝트들은 교육기관이나 회사에서 제공한 환경에서 진행되었기 때문에 서버 비용 같은 것은 크게 신경 쓰지 않았습니다.

그러나 이번 프로젝트는 직접적으로 돈이 나가는 상황이었기 때문에 자연스럽게 비용을 줄이면서도 효율성을 높이는 방법에 대해 고민하게 되었습니다.

서비스 비용 절감 - SonarCloud와 GitHub Actions 선택

My-Music-Note 이전에 진행했던 My-Books 프로젝트에서는 SonarQube로 코드 품질을 관리하고 CI/CD 도구로는 Jenkins를 사용했습니다.
My-Books는 NHN Academy에서 진행한 것으로 인프라 관리비용을 NHN측에서 전액 부담해주었기 때문에 비용 고민이 없었습니다.

하지만 이번 프로젝트는 제 돈으로 운영해야 했기 때문에 별도의 서버 관리가 필요하지 않은 도구를 찾아야 했으며
결과적으로 SonarCloudGitHub Actions라는 SaaS 기반 도구를 선택하게 되었습니다.

SonarCloud - SaaS형 코드 품질 관리 도구

SonarCloud는 SaaS(Software as a Service)로 제공되며 SonarQube처럼 별도의 서버를 설치하거나 관리할 필요가 없습니다.GitHub 공개 저장소에 한해서 전체 기능을 무료로 제공받을 수 있어 규모가 작은 스타트업이나 팀 프로젝트의 경우에는 매우 적합하다 생각합니다.

물론 SonarQube에 비해 부족한 부분도 존재합니다.

  • 제한된 언어 지원 언어지원과(C,Objective-C,PL/SQL...)
  • 타사 플러그인의 부재
  • 모노레포등 복잡한 프로젝트 구조에 대한 지원 부족

다행히 My-Music-Note 서비스는는 이러한 제약 조건과 관련이 적었기 때문에 SonarCloud를 사용하는 데 만족했습니다.

SonarCloud Review

GitHub Actions - 클라우드 기반 CI/CD

GitHub Actions도 SaaS로 제공되며 SonarCloud와 동일하게 GitHub 공개 저장소에 한해서 무료로 사용할 수 있습니다.

Jenkins와 달리 서버 비용이 필요하지 않으며 GitHub Actions Marketplace 활용시 AWS, Google Cloud , Docker 등 다양한 Actions를 쉽게 추가하여 CI/CD 파이프라인을 확장할 수 있습니다.

개인적으로는 이 부분이 매우 강력하다고 느꼈으며 Jenkins와 비교해도 기능이 제한적일 것 같다는 생각은 들지 않았습니다.

특히나 국내 빅테크에서도 GitHub Actions를 사용하는 사례가 있는만큼 대규모 조직에서도 충분히 활용 가능해보입니다.

네트워크 비용 절감 - Bastion Host에서 Systems Manager로 전환

AWS 기반 인프라에서는 프라이빗 서브넷의 EC2 인스턴스에 접근하는 방법 또한 비용 절감 요소중 하나 입니다.
기존에는 Bastion Host를 사용했지만 이 방식에는 몇 가지 한계가 있었습니다.

Bastion Host - 작동 방식과 한계

Bastion Host는 퍼블릭 서브넷에 배치된 EC2 인스턴스로 외부에서 접근 가능한 경로를 제공합니다.
이를 통해 프라이빗 서브넷의 EC2 인스턴스에 접근할 수 있지만 다음과 같은 문제점이 발생합니다.

  1. 보안 취약점:
    • Bastion Host는 외부 인터넷에 노출되어 보안 위협이 존재
  2. PEM 키 관리의 복잡성:
    • 여러 프라이빗 인스턴스의 PEM 키를 Bastion Host에 복사하고 관리해야 함
  3. 추가 비용 발생:
    • Bastion Host 자체가 EC2 인스턴스이기 때문에 유지비용이 발생

Bastion Host 목적의 추가적인 EC2 자원 필요

Systems Manager - 더 안전하고 효율적인 네트워크 접근 방식

이러한 문제를 해결하기 위해 AWS Systems Manager를 도입했습니다.

Systems Manager는 AWS 내부 네트워크를 활용하기 때문에 인터넷 연결 없이도 EC2 인스턴스에 안전하게 접근할 수 있으며
PEM 키를 관리하지 않아도 되는 편리함 , Bastion Host와 같은 EC2 인스턴스를 유지할 필요가 없어 비용이 절감되는 등 다양한 장점이 존재합니다.

실제로 사용해보니 일반적인 터미널 환경과 유사했으며 기존 Bastion Host 방식보다 간편하고 효율적이였습니다.

SSM을 이용한 Private Subnet EC2 접속

My-Music-Note 프로젝트에서 SaaS 기반 도구(SonarCloud, GitHub Actions)와 Systems Manager를 도입한 결과 서비스 비용과 네트워크 비용을 모두 절감할 수 있었습니다.

같이 보시면 좋은 발표 영상입니다.

GPT 요약

My-Music-Note 프로젝트에서 AWS 기반 인프라를 구축하며 고가용성 아키텍처를 설계하고, ASG와 ALB로 트래픽 분산을 구현했습니다.
CodeDeploy와 Docker를 결합해 배포 프로세스를 자동화했으며, Docker 이미지를 활용한 컨테이너 기반 환경으로 전환하여 개발 생산성과 효율성을 높였습니다.
이 경험을 바탕으로 SAA 자격증을 취득하며 AWS와 컨테이너 기술에 대한 깊은 이해를 확립했습니다.

My-Music-Note 프로젝트에서의 경험을 다룬 글입니다.

My-Music-Note 프로젝트에서 유일한 백엔드 개발자로서 AWS 기반 인프라 구축에 집중하였습니다.
처음 AWS를 사용하며 겪었던 어려움과 SAA(Solutions Architect Associate) 자격증 취득, 실제 운영 환경에서의 개선 과정을 공유하고자 합니다.

AWS 첫 경험과 비용 관리

이전에 진행한 My-Books 프로젝트에서는 주로 API 기능 구현과 인증/인가 프로세스 구축을 담당했기 때문에인프라 설계 및 구현 경험은 부족했습니다. 따라서.. My-Music-Note 프로젝트에서는 이러한 부분을 보완하고자 AWS를 활용하여 인프라를 구축하였습니다.

AWS 프리 티어(Free Tier)를 활용할 수도 있었지만 실제 과금 모델을 경험하는 것이 중요하다고 판단하여 유료 서비스를 사용했습니다.
실제로 주머니에서 돈이 빠져나가는 경험을 하니 어떻게하면 비용을 줄일 수 있을까? 고민 하게 됐으며
NAT 게이트웨이, 퍼블릭 IP, ALB(Application Load Balancer) 등의 예상치 못한 비용에 대해서도 학습할 수 있었습니다. 

새로운 목표 - 고가용성 아키텍처 구축

프로젝트를 시작할때 목표로 잡은것은 고가용성 아키텍처를 구축하는 것이었습니다.
고가용성은 서비스가 지속적이고 안정적으로 운영될 수 있는 능력을 의미하며 이는 모던 아키텍처에서 필수적인 요소라 생각합니다.

따라서 다음의 기준을 세우게 됩니다.

  1. 트래픽을 자동으로 분산시킬 수 있을 것
  2. 장애 감지 시 새로운 리소스를 자동으로 생성하고 교체할 것
  3. 무중단 배포가 가능할 것

트래픽 분산 - ASG와 ALB

트래픽 분산을 위해 AWS의 ELB(Elastic Load Balancer)를 조사하면서 공식 문서를 참고해 ASG(Auto Scaling Group)와 연계하여 사용하는 방법을 학습했습니다.

Elastic Load Balancing 로드 밸런서를 Auto Scaling 그룹에 연결 - Amazon EC2 Auto Scaling
이 문서를 통해 ASG와 ELB의 연계로 트래픽을 효율적으로 처리하는 방법을 이해할 수 있었습니다.

ASG와 ELB를 연계해 사용하는 이유는 간단합니다. ASG를 통해 새로운 인스턴스를 생성하더라도 요청이 해당 인스턴스로 분배되지 않으면 무용지물이기 때문입니다.

ASG는 트래픽 증가나 장애 발생 시 인스턴스 수를 동적으로 조절하여 확장성가용성을 높여줍니다. 또한, 대상 추정 정책(Target Tracking Policy)을 사용하면 CPU 사용률과 같은 지표를 기반으로 자동 조정이 가능합니다.

ASG(Auto Scaling Group)

ELB는 생성된 인스턴스로 트래픽을 효율적으로 분배하며, 이를 통해 트래픽 부하 분산과 확장 작업이 자동화됩니다.

ELB에는 ALB(Application Load Balancer)와 NLB(Network Load Balancer)가 존재하지만 My-Music-Note는 REST API 기반의 웹 애플리케이션이므로 HTTP/HTTPS 트래픽 처리를 지원하는 ALB를 사용했습니다.

무중단 배포와 자동화 - CodeDeploy의 도입

트래픽을 분산시키고 ASG로 새로운 리소스(EC2 Instance)를 생성했지만 한 가지 문제가 있었습니다.
"배포는 어떻게 하지?"라는 고민이 생긴 것이죠.

기존의 ASG와 ALB 스택에 연계할 수 있는 배포 도구를 조사하던 중 AWS의 CodeDeploy를 도입하게 되었습니다.

EC2/온프레미스 컴퓨팅 플랫폼의 배포 - AWS CodeDeploy
CodeDeploy를 통해 무중단 배포와 자동화된 배포 프로세스를 설정할 수 있었습니다.

CodeDeploy를 활용하려면 S3에 업로드된 아티팩트AppSpec 파일이 필요합니다.
이를 위해 초기에는 소스 코드와 Gradle 빌드 산출물(JAR), 설정 파일, 배포 스크립트를 모두 압축하여 S3에 업로드하는 방식을 사용했습니다.

초기 배포 프로세스

하지만.. 배포 과정에서 새로운 EC2 인스턴스가 프로비저닝되었지만 애플리케이션이 설치되지 않는 문제가 발생했습니다.
이는 CodeDeploy 에이전트가 EC2 인스턴스에 설치되어 있지 않아서 발생한 문제였습니다.

이를 해결하기 위해 EC2 템플릿의 사용자 데이터(User Data)를 활용했습니다. 사용자 데이터는 EC2 인스턴스가 처음 시작될 때 실행되므로, CodeDeploy 에이전트를 자동으로 설치하고 배포 프로세스를 완성할 수 있었습니다.

배포 완료


문제점 - 비효율적인 zip파일 & 환경 구성의 어려움

그러나 또 다른 문제가 있었습니다. 프론트엔드 개발자가 백엔드 개발 환경을 구성하는 과정이 복잡했습니다.
AWS에 로그인해서, S3에 접근해서, zip파일 다운받고 jar실행시키고.. JRE도 있어야하고.. 이는 많은 번거로움을 주었습니다.

또한 S3에 필요한 소스코드,빌드결과물 등을 담은 zip파일이 배포마다 생기는 것 또한 비효율적으로 느껴졌습니다.

컨테이너 기반 환경으로의 전환

이 문제를 해결하기 위해 Container 기반 애플리케이션으로 마이그레이션하기로 했습니다.
컨테이너화를 위해 Docker를 사용하고, EC2 인스턴스에는 Docker가 설치된 상태로 프로비저닝되도록 설정했습니다.

CodeDeploy 에이전트의 경우 크기가 크지 않았기 때문에 사용자 데이터를 기반으로 설치했지만 모든 배포마다 Docker 설치를 반복하는 방식은 부담스럽게 느껴졌기 때문에 CodeDeploy와 Docker를 설치한 AMI를 만들어 사용해주었습니다.


배포 프로세스또한 개선했습니다.

  • S3에 zip 파일을 업로드하던 방식 -> Docker 이미지를 Docker Hub에 올리고 EC2 인스턴스에서 이를 실행
  • AppSpec.yml과 스크립트를 포함한 deployment.zip 파일을 미리 준비해 배포 과정에서 재활용

이 과정을 통해 프론트엔드 개발자는 단순히 Docker와 PostMan을 활용해 백엔드 환경을 손쉽게 구성할 수 있게 되었으며
배포마다 생성됐던 zip파일을 최소화 할 수 있었습니다.

프로젝트 당시 Notion 문서

완성된 아키텍처와 SAA 자격증 취득

완성된 아키텍처는 다음과 같습니다.

  • ASG와 ALB를 활용한 트래픽 분산
  • CodeDeploy와 Docker를 결합한 효율적인 배포 프로세스
  • NAT Gateway를 통한 Private Subnet의 네트워크 연결

My-Music-Note 프로젝트는 익숙하지 않았던 AWS와 컨테이너 기술에 몰입할 수 있었던 프로젝트였습니다.
또한 배운 것을 검증하고 더욱 Deep Dive하기위해 AWS Certified Solutions Architect - Associate 자격증 취득했으며
백엔드 개발자로서 역량을 키울 수 있었습니다.

AWS Solutions Architect Associate(SAA)합격 후기


같이 보시면 좋은 발표 영상입니다.

 

+ Recent posts