👋 안녕하세요!

꾸준히 성장하는 개발자 이우진입니다.

새로운 기술을 배우고, 문제를 해결하며, 더 나은 코드를 작성하기 위해 노력합니다.
이 블로그에서는 개발 경험, 기술 학습, 프로젝트를 공유합니다.

📚 학습 & 프로젝트

✨ 최근 글

첫 글 - Hugo 블로그 시작하기

블로그를 시작합니다! 안녕하세요! 이우진입니다. Hugo와 PaperMod 테마를 사용하여 기술 블로그를 시작했습니다. 이 블로그에서 다룰 내용 개발 경험 공유 기술 학습 기록 프로젝트 소개 앞으로 꾸준히 글을 작성하겠습니다. 감사합니다! 코드 예시 인라인 코드는 console.log("Hello World!") 이렇게 표시됩니다. // JavaScript 코드 예시 function greet(name) { const message = `안녕하세요, ${name}님!`; console.log(message); return message; } greet("이우진"); # Python 코드 예시 def fibonacci(n): if n <= 1: return n return fibonacci(n-1) + fibonacci(n-2) for i in range(10): print(fibonacci(i))

February 2, 2026 · Lee WooJin

[SpringBoot] ThreadLocal을 활용한 인증관리

개요 Spring Boot는 스레드 풀을 통해 요청에 맞는 스레드를 할당합니다. 인증된 사용자의 ID를 ThreadLocal에 저장하여 전역적으로 접근 가능하게 하려고 했습니다. 문제점 ThreadLocal에 저장되고 초기화 하지 않았을 때 다음 사용자가 인증에 실패해도 전에 사용자의 정보가 남아있어 비즈니스 로직이 실행되는 문제가 발생했습니다. 시나리오 사용자 1이 ID “1"로 인증 성공 후 ThreadLocal에 저장 사용자 2가 토큰 없이 요청 (인증 실패) 사용자 2가 사용자 1의 정보로 비즈니스 로직 실행 해결방안 ...

June 20, 2024 · Lee WooJin

[SpringBoot] In-Memory 환경 Memory Leak

InMemory 인증번호 관리 회원가입 시 휴대폰 번호로 인증번호를 전송하고 인증번호를 통해 회원을 검증하는 방식을 구현했습니다. 인증번호 발행 랜덤 번호를 생성하여 휴대폰으로 전송하고 Map에 번호를 저장합니다. 인증번호 검증 현재 인증시간의 유효성을 확인하고 인증번호를 검증합니다. 일치하면 Map에서 key를 제거하지만, 불일치 시 메모리에 계속 남아있게 됩니다. 메모리 누수 문제 key를 요청하고 검증을 하지 않았을 때 계속해서 데이터가 Map에 참조하고 있어 GC가 메모리를 수거하지 않습니다. 문제 시나리오 사용자가 인증번호 요청 Map에 {phoneNumber: authCode} 저장 사용자가 인증을 완료하지 않음 Map에 데이터가 계속 남아있음 반복 시 메모리 누수 발생 해결방법 ...

June 20, 2024 · Lee WooJin

BeanPay Detail 삭제 이유

변경 전 테이블 구조 기존에는 BeanPayDetail과 PaymentDetail이 분리되어 있었습니다. BeanPayDetail 역할 BeanPayDetail은 기존의 빈페이의 변경 내역에 관한 정보를 담고 있었습니다. 4가지 상태값으로 구분되었습니다: 충전 지출 입금 출금 PaymentDetail 역할 PaymentDetail은 세부 결제 내역 정보를 저장하며 PaymentStatus로 상태를 관리합니다: 결제 환불 중복 문제 발견 PaymentDetail과 BeanPayDetail의 ProcessStatus 값이 중복 사용되고 있었습니다. 변경 이유 PaymentDetail과 BeanPayDetail의 ProcessStatus 값이 중복 사용됨 BeanPayDetail의 추가 행 생성 제거 가능 로직이 하나의 Depth가 줄어들고 유지보수성이 높아졌습니다 복잡한 연관관계 감소 BeanPayDetail의 증감 역할을 PaymentDetail로 통합 가능하다고 판단했습니다. ...

June 3, 2024 · Lee WooJin

락 선택 이유와 성능 테스트

개요 이 포스트는 세 가지 동시성 제어 방식의 성능을 비교합니다: 락 없는 트랜잭션, Beta Lock, Redisson 분산락 테스트 환경 DB: H2 (MariaDB 모드) 환경: 로컬 Redis: Embedded 스레드 수: 32 요청 횟수: 각 1회 1. 트랜잭션만 사용한 결과 @Transactional public void notUseLockTest(String lockName, Integer userId) { BeanPay beanPay = getBeanPay(1, Role.USER); final BeanPayDetail beanPayDetail = BeanPayDetail.ofCreate( beanPay, 1, 5000 ); final BeanPayDetail createBeanPayDetail = beanPayDetailRepository.save(beanPayDetail); beanPay.chargeBeanPayDetail(createBeanPayDetail.getAmount()); } 결과 트랜잭션만 사용하면 MySQL의 기본 격리 수준(Repeatable Read)에서 Lost Update 문제가 발생하여 데이터 정합성이 낮아집니다. ...

April 30, 2024 · Lee WooJin

Reflection 메소드 매개변수 필드명이 arg0, arg1로 나오는 문제 해결

메소드 필드명이 필요했던 이유 AOP를 이용해 다양한 곳에서 유연하고 범용성 있게 사용하기 위해 메소드의 매개변수 필드명이 필요했습니다. 문제 상황 원하는 출력 값은 lockName과 userId의 값이었지만, 예상했던 것과는 다르게 arg0, arg1과 같은 변수명을 가져왔습니다. Parameter.getName() 분석 클래스 구조도 Method 클래스는 Executable 클래스를 상속하고 있고 getParameters는 Executable 클래스에서 구현되어 있었습니다. Executable 클래스 분석 getParameters 메소드 (시작) getParameters() 메소드에서 시작해서 내부 구현을 추적했습니다. ...

April 28, 2024 · Lee WooJin

Github Actions와 AWS CodeDeploy를 활용한 CI/CD 구축

해야할 일 S3 버킷 생성 S3 접근 키 생성 EC2 생성 EC2 규칙 생성 (보안그룹) EC2가 S3, CodeDeploy에 접근하기 위한 역할 적용 EC2 내에 서버를 실행할 수 있게 프로그램 설치 CodeDeploy IAM 권한 설정 S3 버킷 생성 버킷을 생성할 지역을 선정해 주고 생성합니다. S3 접근 키 생성 비밀 엑세스키는 생성 시에 보관해 두어야 합니다. EC2 생성 EC2 규칙 생성 (보안그룹) ...

April 19, 2024 · Lee WooJin

코리아노 BeanPay 고민

빈페이로 결제하게 된 이유 내부 결제 로직과 외부 API 충전 기능을 분리함으로써 외부 API 장애 시 영향을 최소화합니다. 결제를 진행하는 중에는 외부API를 연동하지 않기 때문에 사용자의 경험을 향상시키고 결제 프로세스를 간소화할 수 있습니다. 서비스 자체 포인트인 빈페이 관리 방법 비교 방안 1: 유저 MSA에 beanpay 컬럼 추가 결제 MSA에서 유저 MSA로 API 요청하여 관리. 문제점: 단일 트랜잭션 구현의 어려움 수동 롤백 처리 필요 비관적 락을 사용하거나 분산락을 사용하게 되면 회원의 읽기 성능도 떨어지게 됩니다. 방안 2: 결제 MSA에 관리 (선택) 결제 MSA 내 BeanPay 테이블 및 상세 테이블로 관리. ...

April 17, 2024 · Lee WooJin

TreeSet의 구조와 Comparator 주의사항

TreeSet 구조 TreeSet은 필드로 NavigableMap 인터페이스를 가지고 있으며, TreeMap 클래스가 해당 인터페이스를 구현하는 구조를 가지고 있습니다. TreeSet 생성자가 호출되면 m 필드에 TreeMap 클래스가 할당됩니다. 문제점 발견 TreeSet을 사용하면서 예상한 결과값과 다르게 결과가 나와서 원인을 찾아보기로 했습니다. 예시: x만 비교할 때 Comparator<Point> comparator = Comparator.comparingInt(p -> p.x); TreeSet<Point> treeSet = new TreeSet<>(comparator); treeSet.add(new Point(1, 1)); treeSet.add(new Point(1, 2)); System.out.println(treeSet.size()); // 결과: 1 x만 비교하여 TreeSet 자료구조를 사용했을 때는 1개의 요소밖에 존재하지 않았습니다. HashSet이라면 객체의 hashCode()와 equals()를 비교하여 2라는 결과가 나왔을 텐데… ...

February 29, 2024 · Lee WooJin

해시(Hash) 자료구조와 충돌 회피 기법

해시 자료구조는 16자리의 번호의 일부분을 index로 사용하는 자료구조입니다. 해시 함수란? 임의 길이의 데이터를 고정된 길이의 데이터로 대응시키는 함수 왜 일부분만 사용하는가? 모든 해시를 배열로 가지고 있으면 10^16의 배열이 필요합니다. 이는 40페타 바이트를 차지하게 됩니다. 그렇기에 모든 hash 값을 사용하는 게 아닌 일부분만의 값을 사용하여 해시 테이블을 생성합니다. 충돌 회피 방법 1. Chaining Key가 중복된 노드를 LinkedList로 연결하여 관리합니다. Java의 STL 자료구조는 Chaining 방식을 사용합니다. 주의사항: 충돌이 빈번할수록 성능이 안 좋아지고, 해시 충돌이 한 곳으로 몰렸다면 O(N)의 시간 복잡도를 가질 수 있습니다. ...

February 5, 2024 · Lee WooJin