๋ณ„์  ์ค‘๋ณต ์ƒ์„ฑ ๋™์‹œ์„ฑ ์ด์Šˆ ํ•ด๊ฒฐ

2024. 10. 27. 00:04ยทProject

๋“ค์–ด๊ฐ€๊ธฐ ์•ž์„œ

ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ ์ค‘, ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋ถ„ ๊ป˜์„œ ๋ณ„์  ๊ธฐ๋Šฅ์—์„œ ๋ณ„์  ๊ฐ’์ด ์ œ๋Œ€๋กœ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ•˜์…จ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๋ฅผ ์ •ํ™•ํžˆ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ํ™•์ธํ•ด๋ณธ ๊ฒฐ๊ณผ, ๋™์ผํ•œ ๋ณ„์  ๋ฐ์ดํ„ฐ๊ฐ€ 2๊ฐœ ์ €์žฅ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. 

public RatingCreateResponse createRating(Long userId, RatingCreateServiceRequest request) {
	User user = userRepository.findById(userId)
		.orElseThrow(() -> new BusinessException(USER_NOT_FOUND));
	
	Book book = bookRepository.findById(request.getBookIsbn())
		.orElseThrow(() -> new BusinessException(BOOK_NOT_FOUND));
	
	// ์กฐํšŒํ•œ user์™€ book์„ ํ†ตํ•ด์„œ rating์„ ์ค‘๋ณต ์ƒ์„ฑํ•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
	if (ratingRepository.existByUserAndBook(user, book)) {
		throw new BusinessException(RATING_ALREADY_EXIST);
	}
	
	Rating rating = ratingRepository.save(request.toEntity());
	
	return RatingCreateResponse.of(rating, book);
}

์‚ฌ์šฉ์ž๊ฐ€ ๋„์„œ์— ๋Œ€ํ•ด์„œ 2๊ฐœ์˜ ๋ณ„์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Œ

ํฅ๋ฏธ๋กœ์šด ์ ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์— ์ค‘๋ณต ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š” ๋กœ์ง์ด ์ด๋ฏธ ๊ตฌํ˜„๋˜์–ด ์žˆ์—ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ์ค‘๋ณต ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด, ์˜ˆ์ƒ๋Œ€๋กœ ์ฝ”๋“œ๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š์€ ์›์ธ์„ ๋ถ„์„ํ•ด๋ด„์œผ๋กœ์จ ๋ฌธ์ œ์˜ ๊ทผ๋ณธ์ ์ธ ์›์ธ์„ ์ฐพ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๋™์‹œ์„ฑ ๋ฌธ์ œ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•œ ์ค‘๋ณต ๋ฐ์ดํ„ฐ ์ €์žฅ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋ก์„ ์‚ดํŽด๋ณด๋‹ˆ, ๋ณ„์  ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฑฐ์˜ ๋™์‹œ์— ์ƒ์„ฑ๋œ ๊ฒƒ์ด ํ™•์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‘ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฑฐ์˜ ๋™์ผํ•œ ์‹œ๊ฐ„์— ์ €์žฅ๋œ ๊ฒƒ์œผ๋กœ ๋ณด์•„, ์ด๋Š” ๋™์‹œ์„ฑ ๋ฌธ์ œ, ํŠนํžˆ ๊ฒฝ์Ÿ ์ƒํƒœ(Race Condition)๋กœ ์ธํ•ด ๋ฐœ์ƒํ•œ ๊ฒƒ์œผ๋กœ ์ถ”์ธก๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ๊ฒฝ์Ÿ ์ƒํƒœ๋ž€ ๋ฌด์—‡์ด๋ฉฐ, ์™œ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ผ๊นŒ์š”?

 

๊ฒฝ์Ÿ ์ƒํƒœ (Race Condition)

๊ฒฝ์Ÿ ์ƒํƒœ๋Š” ๋‹ค์ˆ˜์˜ ํ”„๋กœ์„ธ์Šค๋‚˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ๋™์ผํ•œ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜์—ฌ ์ˆ˜์ •ํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋กœ, ์ด๋กœ ์ธํ•ด ์˜ˆ๊ธฐ์น˜ ์•Š์€ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

 

์œ„ ์‚ฌ์ง„์ฒ˜๋Ÿผ ํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณ„์ ์„ ์ œ์ถœํ•  ๋•Œ ๋น ๋ฅด๊ฒŒ ์š”์ฒญํ•˜์—ฌ 2๋ฒˆ ์ƒ์„ฑ๋˜๋„๋ก ์‹œ๋„ํ•  ๊ฒฝ์šฐ, ๋‘ ์š”์ฒญ์ด ๊ฑฐ์˜ ๋™์‹œ์— ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์— ๋„๋‹ฌํ•˜๊ฒŒ ๋˜๋ฉด์„œ ๋ณ„์ ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง์—์„œ ๊ฒ€์ฆ๋˜์ง€ ์•Š๊ณ  ์ค‘๋ณต ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์ƒ๊น๋‹ˆ๋‹ค. 

 

์ด ๊ฐ™์€ ์ƒํ™ฉ์€ ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

public RatingCreateResponse createRating(Long userId, RatingCreateServiceRequest request) {
	User user = userRepository.findById(userId)
		.orElseThrow(() -> new BusinessException(USER_NOT_FOUND));
	
	Book book = bookRepository.findById(request.getBookIsbn())
		.orElseThrow(() -> new BusinessException(BOOK_NOT_FOUND));
	
	// ์กฐํšŒํ•œ user์™€ book์„ ํ†ตํ•ด์„œ rating์„ ์ค‘๋ณต ์ƒ์„ฑํ•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
	if (ratingRepository.existByUserAndBook(user, book)) {
		throw new BusinessException(RATING_ALREADY_EXIST);
	}
	
	Rating rating = ratingRepository.save(request.toEntity());
	
	return RatingCreateResponse.of(rating, book);
}

 

@Test
@DisplayName("๋™์ผํ•œ ๋„์„œ์— ๋Œ€ํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ๋ณ„์ ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋ณ„์ ์ด ์ƒ์„ฑ๋œ๋‹ค.")
public void createRatingWithThreads() throws Exception {
	// given
	User user = userRepository.save(createUser());
	Book book = bookRepository.save(createBook());
	
	RatingCreateServiceRequest request = RatingCreateServiceRequest.builder()
		.rating(4.5)
		.bookIsbn(book.getIsbn())
		.build();
	
	int threadCount = 2;
	ExecutorService executorService = Executores.newFixedThreadPool(threadCount);
	CountDownLatch latch = new CountDownLatch(threadCount);
	
	// when - ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ๋ณ„์  ์ƒ์„ฑ์„ ์š”์ฒญํ•œ ์ƒํ™ฉ
	for (int i = 0; i < threadCount; i++) {
		executorService.submit(() -> {
			try {
				ratingService.createRating(user.getId(), request);
			} finally {
				latch.countDown();
			}
		});
	}
	
	latch.await();
	
	// then
	List<Rating> ratings = ratingRepository.findAll();
	assertThat(ratings).hasSize(2);
}

๋ณ„์ ์ด 2๊ฐœ๊ฐ€ ์ƒ์„ฑ๋จ

์„œ๋น„์Šค ๋กœ์ง์ƒ์—์„œ๋Š” ์ด๋ฏธ ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ๋ณ„์ ์„ ์ถ”๊ฐ€ํ–ˆ์„ ๊ฒฝ์šฐ ์ค‘๋ณต ์ƒ์„ฑ์ด ๋˜์ง€ ์•Š๋„๋ก ๋˜์–ด ์žˆ์œผ๋‚˜, ๋™์‹œ์— ๋ณ„์  ์ƒ์„ฑ์„ ์š”์ฒญํ•œ ๊ฒฝ์šฐ์—๋Š” ๋ณ„์ ์ด 2๊ฐœ๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์ค‘๋ณต ๋ฐ์ดํ„ฐ ์‚ฝ์ž…์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ ์œ ๋‹ˆํฌ ์ธ๋ฑ์Šค ์„ค์ •๊ณผ ๋„ค์ž„๋“œ ๋ฝ์„ ํ†ตํ•œ Redis ๋ถ„์‚ฐ ๋ฝ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด ์žˆ์Šต๋‹ˆ๋‹ค. 

 

์ €ํฌ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ด ๋‘ ๊ฐ€์ง€ ํ•ด๊ฒฐ ๋ฐฉ์‹ ์ค‘ ์œ ๋‹ˆํฌ ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ค‘๋ณต ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์˜€์Šต๋‹ˆ๋‹ค. Redis ๋ถ„์‚ฐ ๋ฝ์€ ๋Œ€๊ทœ๋ชจ ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์ด๋‚˜ ๋ฉ€ํ‹ฐ ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์—์„œ ์—ฌ๋Ÿฌ ์„œ๋น„์Šค ์ธ์Šคํ„ด์Šค๊ฐ€ ๋™์‹œ์— ์ ‘๊ทผํ•˜๋Š” ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์ค‘๋ณต ์ œ์–ด์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ €ํฌ ์„œ๋น„์Šค๋Š” ๋‹จ์ผ ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์œผ๋กœ ์šด์˜๋˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Redis ๋ถ„์‚ฐ ๋ฝ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์€ ๊ณผ๋„ํ•œ ํŒ๋‹จ์ด๋ผ ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. 

โ“ ๋‚™๊ด€์ /๋น„๊ด€์  ๋ฝ์€ ์ค‘๋ณต ์ƒ์„ฑ์„ ๋ง‰์„ ์ˆ˜ ์—†๋‚˜์š”?
๋‚™๊ด€์ /๋น„๊ด€์  ๋ฝ์€ ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ์˜ ์ •ํ•ฉ์„ฑ์„ ๋ง‰๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— INSERT์˜ ๊ฒฝ์šฐ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ, ์ด๋ฏธ ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ์— ๋ฝ์˜ ๊ฐœ๋…์ด ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

โ“ ์œ ๋‹ˆํฌ ์ธ๋ฑ์Šค๋Š” ๋ฝ ์—†์ด ๋™์‹œ์„ฑ์„ ์ œ์–ดํ•˜๋Š” ๊ฑด๊ฐ€์š”?
์œ ๋‹ˆํฌ ์ธ๋ฑ์Šค๋Š” INSERT ์‹œ ์ค‘๋ณต ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๊ณต์œ  ๋ฝ(S-Lock)์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ INSERT ์ž‘์—…์„ ํ•ด๋„ ๊ณต์œ  ๋ฝ์„ ๊ฑธ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ํŠธ๋žœ์žญ์…˜ ์ž‘์—…์€ ๋Œ€๊ธฐํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ค‘๋ณต๋œ ๊ฐ’์ด ์—†๋‹ค๊ณ  ํ™•์ธ๋˜๋ฉด, InnoDB๋Š” ๊ณต์œ  ๋ฝ์„ ํ•ด์ œํ•˜๊ณ , ์‹ค์ œ ์‚ฝ์ž…ํ•˜๋Š” ๊ณผ์ •์—์„œ ์“ฐ๊ธฐ ๋ฝ(X-Lock)์œผ๋กœ ์ „ํ™˜ํ•˜์—ฌ ์‚ฝ์ž… ์ž‘์—…์„ ์™„๋ฃŒํ•ฉ๋‹ˆ๋‹ค. ์‚ฝ์ž… ์™„๋ฃŒ ํ›„, X-Lock๋„ ํ•ด์ œ๋˜๋ฉด์„œ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ์ด ๋ ˆ์ฝ”๋“œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ๋ฉ๋‹ˆ๋‹ค.

์—”ํ‹ฐํ‹ฐ์— ์œ ๋‹ˆํฌ ์ธ๋ฑ์Šค ์ ์šฉ

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(uniqueConstraint = {
	@UniqueConstraint(columnNames = {"user_id", "book_isbn"})
})
public class Rating extends BaseEntity {
	...
}

ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์—๋Š” ์‚ฌ์šฉ์ž์™€ ๋„์„œ์— ๊ด€ํ•œ ๋ณ„์ ์ด ์ค‘๋ณต๋˜์ง€ ์•Š๊ฒŒ ์ƒ์„ฑ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ID๋“ค์„ ์œ ๋‹ˆํฌ ๋ณตํ•ฉ ์ธ๋ฑ์Šค๋กœ ์ƒ์„ฑํ•˜์—ฌ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

@Test
@DisplayName("์‚ฌ์šฉ์ž๊ฐ€ ๋™์ผํ•œ ๋„์„œ์— ๋Œ€ํ•ด ๋™์‹œ์— ๋ณ„์ ์„ ์ถ”๊ฐ€ํ•ด๋„ ํ•˜๋‚˜์˜ ๋ณ„์ ๋งŒ ์ƒ์„ฑ๋œ๋‹ค.")
public void createRatingWithThreads() throws Exception {
	// given
	User user = userRepository.save(createUser());
	Book book = bookRepository.save(createBook());
	
	RatingCreateServiceRequest reqeust = RatingCreateServiceRequest.builder()
		.rating(4.5)
		.bookIsbn(book.getIsbn())
		.build();
		
	int threadCount = 2;
	ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
	CountDownLatch latch = new CountDownLatch();
	
	// when
	for (int i = 0; i < threadCount; i++) {
		executorService.submit(() -> {
			try {
				ratingService.createRating(user.getId(), request);
			} finally {
				latch.countDown();
			}
		});
	}
	
	latch.await();
	
	// then
	List<Rating> ratings = ratingRepository.findAll();
	assertThat(ratings).hasSize(1);
}

ํ•ด๋‹น ์„ค์ •์„ ๋งˆ์น˜๊ณ  ์œ„์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰์‹œ์ผœ๋ณด๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— 1๊ฐœ์˜ ๋ณ„์  ๋ฐ์ดํ„ฐ๋งŒ ์กฐํšŒ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

DataIntergrityViolationException ์ฒ˜๋ฆฌ

์œ ๋‹ˆํฌ ์ธ๋ฑ์Šค๋ฅผ ์„ค์ •ํ•œ ํ›„ ๋ฐ์ดํ„ฐ ์ค‘๋ณต ์ €์žฅ ์‹œ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋‚ด๋ถ€์—์„œ ์ค‘๋ณต์— ๋Œ€ํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ์œ„์™€ ๊ฐ™์€ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. 

// Service์—์„œ try/catch ๊ตฌ๋ฌธ์„ ์ด์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌ
@Transactional
public RatingCreateResponse createRating(Long userId, RatingCreateServiceRequest request) {
	User user = userRepository.findById(userId)
		.orElseThrow(() -> new BusinessExeption(USER_NOT_FOUND));
		
	Book book = bookRepository.findById(request.getBookIsbn())
		.orElseThrow(() -> new BusinessException(BOOK_NOT_FOUND));
	
	if (ratingRepository.existByUserAndBook(user, book)) {
		throw new BusinessException(RATING_ALREADY_EXIST);
	}
	
	// ์ค‘๋ณต ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
	try {
		Rating rating = ratingRepository.save(request.toEntity(user, book));
	} catch (DataIntergrityViolationException e) {
		throw new BusinessException(DATA_ALREADY_EXIST);
	}
	
	return RatingCreateResponse.of(rating, book);
}
@RestControllerAdvice
public class GlobalExceptionHandler {
	
	@ExceptionHandler(DataIntergrityViolationException.class)
	public ResponseEntity<ErrorResponse> handleDataIntergrityViolationException(
		DataIntergrityViolationException e
	) {
		// ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๋ฆฌํ„ดํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋กœ์ง ์ž‘์„ฑ
		...
	}
}

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋น„์Šค ๋กœ์ง์—์„œ try/catch ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•ด ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜, @ControllerAdvice์™€ ๊ฐ™์€ ๊ณตํ†ต ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ €ํฌ ์„œ๋น„์Šค์—์„œ๋Š” @ControllerAdvice๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ๋ฅผ ๊ณตํ†ต ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์„ ์„ ํƒํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

'Project' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ๋กœ ์ธํ•œ API ์„ฑ๋Šฅ ๋ฌธ์ œ ํ•ด๊ฒฐ: ์ปค๋ฒ„๋ง ์ธ๋ฑ์Šค ๋ฐ ์ธ๋ฑ์Šค ํŠœ๋‹์„ ํ†ตํ•œ ์ตœ์ ํ™”  (1) 2025.05.29
GitHub Actions + Docker + NGINX๋ฅผ ํ™œ์šฉํ•œ Blue/Green ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ ํ™˜๊ฒฝ ๊ตฌ์ถ•  (1) 2025.05.04
์ฑ„ํŒ… ๋ฉ”์„ธ์ง€ ์ฝ์Œ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ ๊ตฌ์กฐ ๊ฐœ์„ ๊ธฐ  (0) 2024.12.18
Embedded Mongo/Redis ์ ์šฉํ•˜๊ธฐ  (0) 2024.11.26
CompletableFuture๋ฅผ ํ™œ์šฉํ•œ ์„ฑ๋Šฅ ๊ฐœ์„ ๊ธฐ  (0) 2024.08.01
'Project' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • GitHub Actions + Docker + NGINX๋ฅผ ํ™œ์šฉํ•œ Blue/Green ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ ํ™˜๊ฒฝ ๊ตฌ์ถ•
  • ์ฑ„ํŒ… ๋ฉ”์„ธ์ง€ ์ฝ์Œ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ ๊ตฌ์กฐ ๊ฐœ์„ ๊ธฐ
  • Embedded Mongo/Redis ์ ์šฉํ•˜๊ธฐ
  • CompletableFuture๋ฅผ ํ™œ์šฉํ•œ ์„ฑ๋Šฅ ๊ฐœ์„ ๊ธฐ
jwooo๐ŸŒฑ
jwooo๐ŸŒฑ
jwooo's log ์ž…๋‹ˆ๋‹ค.
  • jwooo๐ŸŒฑ
    jwooo's log
    jwooo๐ŸŒฑ
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (12)
      • Java (4)
      • Project (6)
      • Computer Science (2)
        • Network (1)
        • Security (1)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ๋ฐฉ๋ช…๋ก
  • ๋งํฌ

  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
jwooo๐ŸŒฑ
๋ณ„์  ์ค‘๋ณต ์ƒ์„ฑ ๋™์‹œ์„ฑ ์ด์Šˆ ํ•ด๊ฒฐ
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”