-
ํผ์ ๋ฐฐ๋ฌ ์์์ ์์ผ ๋จน๋ค ๋ณด๋ฉด ๋ฉ๋ด ์ ํ๋ ์ด๋ ต๊ณ ๋ฐฐ๋ฌ๋น๋ ๋ถ๋ด๋๊ธฐ ์ฝ์ต๋๋ค. ๊ฐ์ด๋จน์๋ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด, ๋ฐฐ๋ฌ์ โํจ๊ปโ๋ก ๋ฐ๊พธ๋ ํ๋ซํผ์ ๋๋ค.
- ๋ฉ๋ด๋ฅผ ๊ณต์ ์ฅ๋ฐ๊ตฌ๋์ ๋ด์ ํจ๊ป ์ฃผ๋ฌธ
- ์ค์๊ฐ ์ฑํ ์ผ๋ก ์ฃผ๋ณ ์ฌ๋๊ณผ ๋ฉ๋ด์ ๋ฐฐ๋ฌ ์กฐ์จ
- ํผ์์ ๋ฐฐ๋ฌ ์ ๋ฐ์ํ๋ ๋ถ๋ด ์ต์ํ
- ์ค์๊ฐ ์ปค๋ฎค๋์ผ์ด์ ์ผ๋ก ํจ๊ป ์ฃผ๋ฌธ ํจ์จ ๊ทน๋ํ
- ๋ฐฐ๋ฌ์ ํผ์๊ฐ ์๋ ์ฌ๋ฟ์ด์ ๋ ํธ๋ฆฌํ๊ณ ์ฆ๊ฒ๊ฒ ์ฆ๊ธธ ์ ์๋๋ก ์ค๊ณ
ํด๋ฆญํ์ฌ ํ๋ก์ ํธ ํ์ผ ๊ตฌ์กฐ๋ฅผ ํ์ธํ์ธ์.
๐ฆ project-root/
โฃ ๐ data/
โฃ ๐ gradle/
โฃ ๐ grafana/
โฃ ๐ grafana_data/
โฃ ๐ logs/
โฃ ๐ mysql_exporter/
โฃ ๐ prometheus/
โฃ ๐ src/
โ โฃ ๐ main/
โ โ โฃ ๐ java/
โ โ โ โ ๐ com/
โ โ โ โ โ ๐ example/
โ โ โ โ โ โ ๐ eat_together/
โ โ โ โ โ โ โฃ ๐ domain/
โ โ โ โ โ โ โ โฃ ๐ cart/
โ โ โ โ โ โ โ โ โฃ ๐ controller/
โ โ โ โ โ โ โ โ โฃ ๐ dto/
โ โ โ โ โ โ โ โ โฃ ๐ entity/
โ โ โ โ โ โ โ โ โฃ ๐ repository/
โ โ โ โ โ โ โ โ โ ๐ service/
โ โ โ โ โ โ โ โฃ ๐ chat/
โ โ โ โ โ โ โ โฃ ๐ menu/
โ โ โ โ โ โ โ โฃ ๐ notification/
โ โ โ โ โ โ โ โฃ ๐ order/
โ โ โ โ โ โ โ โฃ ๐ payment/
โ โ โ โ โ โ โ โฃ ๐ rider/
โ โ โ โ โ โ โ โฃ ๐ social/
โ โ โ โ โ โ โ โ ๐ store/
โ โ โ โ โ โ โ โ ๐ users/
โ โ โ โ โ โ โฃ ๐ global/
โ โ โ โ โ โ โ โฃ ๐ common/
โ โ โ โ โ โ โ โฃ ๐ config/
โ โ โ โ โ โ โ โฃ ๐ dto/
โ โ โ โ โ โ โ โฃ ๐ entity/
โ โ โ โ โ โ โ โฃ ๐ exception/
โ โ โ โ โ โ โ โฃ ๐ redis/
โ โ โ โ โ โ โ โฃ ๐ util/
โ โ โ โ โ โ โ โ ๐ websocket/
โ โ โ โ โ โ โ ๐ EatTogetherApplication.java
โ โ โ ๐ resources/
โ โ โ โฃ ๐ static/
โ โ โ โ ๐ application.yml
โ โฃ ๐ test/
โ โ ๐ .env
โฃ ๐ .gitattributes
โฃ ๐ .gitignore
โฃ ๐ build.gradle
โฃ ๐ docker-compose.yml
โฃ ๐ Dockerfile
โฃ ๐ Dockerfile.elasticsearch
โฃ ๐ gradlew
โฃ ๐ gradlew.bat
โฃ ๐ README.md
โฃ ๐ settings.gradle
โ ๐ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
-
๋ฌธ์ ์ํฉ: ์บ์ฑ๋
MenuResponseDto/PagingMenuResponseDto๊ฐ์ฒด ์ง๋ ฌํ ์SerializationException: Java 8 date/time type LocalDateTime not supported by default๋ฐ์ -
์์ธ ๋ถ์: Jackson ๊ธฐ๋ณธ ์ง๋ ฌํ์์๋
LocalDateTimeํ์ ์ ์ฒ๋ฆฌํ์ง ๋ชปํด ๋ฐ์-
์๋ํ ๋ฐฉ๋ฒ
ObjectMapper objectMapper = new ObjectMapper() .registerModule(new JavaTimeModule()) // LocalDateTime ์ง์ .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
โ ์ง๋ ฌํ๋ ํด๊ฒฐ๋์ง๋ง, ์บ์ฑ๋ DTO ์ญ์ง๋ ฌํ ๊ณผ์ ์์ ๋งคํ ์ค๋ฅ ๋ฐ์
-
-
ํด๊ฒฐ: DTO ์ ์ฉ RedisTemplate์ ์์ฑํ์ฌ Jackson2JsonRedisSerializer + JavaTimeModule ์ค์ ์ ์ฉ
@Bean public RedisTemplate<String, PagingMenuResponseDto> pagingMenuRedisTemplate( RedisConnectionFactory connectionFactory) { RedisTemplate<String, PagingMenuResponseDto> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); template.setKeySerializer(new StringRedisSerializer()); Jackson2JsonRedisSerializer<PagingMenuResponseDto> serializer = new Jackson2JsonRedisSerializer<>(PagingMenuResponseDto.class); serializer.setObjectMapper(objectMapper()); template.setValueSerializer(serializer); return template; }
โ ์ด ๋ฐฉ์์ผ๋ก LocalDateTime ํ๋๋ฅผ ํฌํจํ DTO๋ ๋ฌธ์ ์์ด ์บ์ฑ/์กฐํ ๊ฐ๋ฅ
-
๋ฌธ์ ์ํฉ: ๋ฉ๋ด ๋จ๊ฑด ์กฐํ ์ ์กด์ฌํ์ง ์๋ ๋งค์ฅ์์ Redis์ ์ ์ฅ๋ ๋ฉ๋ด๊ฐ ์กฐํ ์ฑ๊ณต์ผ๋ก ์๋ต๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์
-
์์ธ ๋ถ์:
- ์บ์ ํค๊ฐ
menuId๋จ์ผ ๊ฐ๋ง ํฌํจํ๋๋ก ์ค๊ณ๋จ - ์๋ก ๋ค๋ฅธ ๋งค์ฅ์ด๋ผ๋
menuId๊ฐ ๊ฐ์ผ๋ฉด ๊ฐ์ ์บ์ ์ํธ๋ฆฌ๋ฅผ ์ฐธ์กฐ โ ๋งค์ฅ ๊ฒ์ฆ ์คํจ
- ์บ์ ํค๊ฐ
-
ํด๊ฒฐ:
-
๊ฒฐ๊ณผ:
- ์บ์ ํค์
storeId๋ฅผ ํฌํจ์์ผ ๋งค์ฅ ๋จ์ ๊ฒ์ฆ์ด ๊ฐ๋ฅํด์ง - ์๋ชป๋ ๋งค์ฅ์์ ๊ฐ์
menuId์์ฒญ ์ ์บ์ ์ฌ์ฌ์ฉ์ด ์ฐจ๋จ๋์ด, ๋ฐ์ดํฐ ์ ํฉ์ฑ ํ๋ณด
- ์บ์ ํค์
-
๋ฌธ์ ์ํฉ:
- ์ฃผ๋ฌธ ํ์ด์ง ์กฐํ ์ ์ฃผ๋ฌธ ์ ๋งํผ ์ถ๊ฐ ์ฟผ๋ฆฌ ๋ฐ์ (N+1 ๋ฌธ์ )
- ์ฃผ๋ฌธ ๋จ์ผ ์กฐํ ์ ์ฃผ๋ฌธ ์์ดํ ์ ๋งํผ ์ถ๊ฐ ์ฟผ๋ฆฌ ๋ฐ์ (N+1 ๋ฌธ์ )
-
์์ธ ๋ถ์:
- ํ์ด์ง ์กฐํ: store ์ ๋ณด๋ฅผ ์ฃผ๋ฌธ๋ง๋ค ๋ณ๋ ์กฐํ
- ๋จ์ผ ์กฐํ: orderItems์ menu๋ฅผ fetch joinํ์ง ์์ ๋ณ๋ ์ฟผ๋ฆฌ ๋ฐ์
-
ํด๊ฒฐ:
- ํ์ด์ง ์กฐํ
- QueryDSL์ .join(order.store).fetchJoin() ์ถ๊ฐ โ ์ฃผ๋ฌธ๊ณผ ๊ฐ๊ฒ ์ ๋ณด๋ฅผ ํ ๋ฒ์ ์กฐํ
- ๋จ์ผ ์กฐํ
- QueryDSL์ ์๋ ๋ด์ฉ์ ์ถ๊ฐ:
.join(order.store).fetchJoin() // ์ฃผ๋ฌธ๊ณผ ๊ฐ๊ฒ ์ ๋ณด๋ฅผ ํ ๋ฒ์ ์กฐํ .leftJoin(order.orderItems, orderItem).fetchJoin() // ์ฃผ๋ฌธ๊ณผ ์ฃผ๋ฌธ์์ดํ ์ ํ ๋ฒ์ ์กฐํ .leftJoin(orderItem.menu).fetchJoin() // ์ฃผ๋ฌธ์์ดํ ๊ณผ ๋ฉ๋ด๋ฅผ ํ ๋ฒ์ ์กฐํ
- QueryDSL์ ์๋ ๋ด์ฉ์ ์ถ๊ฐ:
- ํ์ด์ง ์กฐํ
-
๋ฌธ์ ์ํฉ:
- Docker Compose์์
prom/mysqld-exporter๋ฅผ ํ๊ฒฝ๋ณ์DATA_SOURCE_NAME๋ฐฉ์์ผ๋ก ์ค์ ํ์ผ๋, Prometheus์์ ํ๊ฒ์ด ์ธ์๋์ง ์์(์์ง ์คํจ)
mysqld_exporter: image: prom/mysqld-exporter # ... environment: # mysql ์๋น์ค ์ด๋ฆ๊ณผ MySQL ๊ธฐ๋ณธ ํฌํธ 3306 ์ฌ์ฉ - DATA_SOURCE_NAME=exporter:YOUR_PASSWORD@(mysql:3306)/ # โ ๋ฌธ์ ๋ฐ์ depends_on: - mysql - Docker Compose์์
-
์์ธ ๋ถ์:
- ํต์ฌ ์์ธ: ์ปจํ
์ด๋์์
DATA_SOURCE_NAME๊ฐ์ด ์ ์์ ์ผ๋ก ์ฃผ์ ๋์ง ์์, Exporter๊ฐ MySQL ์ ์ ์ ๋ณด๋ฅผ ์ธ์ํ์ง ๋ชปํจ - ๊ทธ ๊ฒฐ๊ณผ,
Exporter๊ฐ ๋ฉํธ๋ฆญ์ ๋ ธ์ถํ์ง ๋ชปํด Prometheus๊ฐ ํ๊ฒ์ ์ก์ง ๋ชปํจ
- ํต์ฌ ์์ธ: ์ปจํ
์ด๋์์
-
ํด๊ฒฐ:
- ํ๊ฒฝ๋ณ์ ์ฃผ์ ์ ์ฌ์ฉํ์ง ์๊ณ , ์ปจํ ์ด๋ ์์ ์ ๋ช ๋ น์ค ์ธ์(ํ๋๊ทธ)๋ก ์ง์ ๊ฐ ์ฃผ์ ํ๋๋ก ๋ณ๊ฒฝ
mysqld_exporter: image: prom/mysqld-exporter container_name: mysqld_exporter ports: - "9104:9104" depends_on: - mysql command: - "--mysqld.username=${MYSQL_USERNAME}:${MYSQL_PASSWORD}" - "--mysqld.address=mysql:${MYSQL_PORT}" networks: - monitoring
-
๊ฒฐ๊ณผ:
- Prometheus: Targets ํ์ด์ง์์
mysqld_exporter์ํ๊ฐ UP์ผ๋ก ํ์ - Exporter ์๋ํฌ์ธํธ: ๋ธ๋ผ์ฐ์ /CLI๋ก
http://localhost:9104/metrics์ ์ ์ ๋ฉํธ๋ฆญ ์๋ต ํ์ธ - ์ปจํ
์ด๋ ์ํ/๋ก๊ทธ:
docker compose ps mysqld_exporter,docker compose logs -f mysqld_exporter์์ ์ ์ ๊ธฐ๋ ํ์ธ
- Prometheus: Targets ํ์ด์ง์์
-
๋ฌธ์ ์ํฉ:
- ๊ธฐ์กด์ ์ธ์ฆ ๊ณผ์ ์
jwtFilter๋ฅผ ํตํด ์งํ๋๋ค. - ๊ทธ๋ฌ๋ ์น์์ผ์ผ๋ก ์ ๊ทธ๋ ์ด๋ํ ํ๋ถํฐ๋ ์ธ์ฆ ๊ณผ์ ์ด ๊ฑฐ์ณ์ง์ง ์์๋ค.
- ๊ธฐ์กด์ ์ธ์ฆ ๊ณผ์ ์
-
์์ธ ๋ถ์:
JwtFilter๊ฐ ์์๋ฐ์OncePerRequestFilter๋ Http ์์ฒญ์์๋ง ์๋ํ๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ํ๋กํ ์ฝ์ธ ์น์์ผ์ผ๋ก ์ ๊ทธ๋ ์ด๋ ํ๋ฉด ์๋ํ์ง ์๋๋ค.- ๋ฐ๋ผ์
JwtFilter๋ฅผ ํตํ ์ธ์ฆ๊ณผ์ ์ ๊ฑฐ์น ์ ์๋ค.
-
ํด๊ฒฐ:
Command(Connect, Subscribe, Send, Disconnect)๋ฅผ ํฌํจํ STOMP ํ๋ ์์ ํด๋ผ์ด์ธํธ๊ฐ ์ ์กํ๋ฉด ์ค๊ฐ์์ChannelInterceptor๊ฐ ๊ฐ๋ก์ฑ๋ค.- ์ฉ๋์ ๋ง๊ฒ ๊ฐ ๋ช ๋ น์ด ๋ณ๋ก ์ธ์ฆ ๊ณผ์ ์ ์์ฑํ๋ฉด ๊ทธ ์ฝ๋์ ๋ฐ๋ผ ๊ฒ์ฌํ ํ ์ธ์ฆ๋์์ ๊ฒฝ์ฐ ๋ฉ์ธ์ง๋ฅผ ์ ์กํ ์ ์๊ฒ ๋๋ค.
-
๊ฒฐ๊ณผ:
- ํ
์คํธ ์ ํ ํฐ์ ํฌํจํ์ง ์์ผ๋ฉด CONNECT ์์ฒด๊ฐ ๋ถ๊ฐ๋ฅํ๋ค. ๋ํ ํ ํฐ์ด ๋ง๋ฃ๋ ๊ฒฝ์ฐ์๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ๋ก๊ทธ์ธ์ ํตํด ํ ํฐ์ ๋ฐ๊ธ๋ฐ๊ณ ์ ํจํ ํ ํฐ์ ํฌํจํ์ฌ ํ
์คํธ๋ฅผ ํ์
๋ ์ ์์ ์ผ๋ก
CONNECT๊ฐ ๋์๊ณ ์ฑํ ๊ธฐ๋ฅ๋ ์ ์์ ์ผ๋ก ์๋ํ ๊ฒ์ ํ์ธํ ์ ์์๋ค.
- ํ
์คํธ ์ ํ ํฐ์ ํฌํจํ์ง ์์ผ๋ฉด CONNECT ์์ฒด๊ฐ ๋ถ๊ฐ๋ฅํ๋ค. ๋ํ ํ ํฐ์ด ๋ง๋ฃ๋ ๊ฒฝ์ฐ์๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ๋ก๊ทธ์ธ์ ํตํด ํ ํฐ์ ๋ฐ๊ธ๋ฐ๊ณ ์ ํจํ ํ ํฐ์ ํฌํจํ์ฌ ํ
์คํธ๋ฅผ ํ์
๋ ์ ์์ ์ผ๋ก
-
๋ฌธ์ ์ํฉ:
- ์ฅ๋ฐ๊ตฌ๋์ ์๋ก ๋ค๋ฅธ ๋งค์ฅ ๋ฉ๋ด๊ฐ ์์ฌ ๋ฐฐ๋ฌํ/์ต์์ฃผ๋ฌธ๊ธ์ก ๊ณ์ฐ์ด ๊นจ์ง
- ๋์ผ ๋ฉ๋ด๊ฐ ์ค๋ณตํ์ผ๋ก ์์ฌ ์ด์ก์ด ๋น์ ์
-
์์ธ ๋ถ์:
storeId์ ์ฝ ๋ฏธํก โ ์ถ๊ฐ/์์ ์ ๊ฒ์ฆ ๋๋ฝ(cart_id, menu_id)UNIQUE ์ ์ฝ ๋ถ์ฌ๋กupsert์คํจ
-
ํด๊ฒฐ:
- ๋จ์ผ ๋งค์ฅ ๊ท์น: ์ฒซ ์์ดํ
์
storeId๊ณ ์ , ๋ค๋ฅธ ๋งค์ฅ ์ถ๊ฐ ์ ๊ต์ฒด/์ถ๊ฐ์ทจ์ ์ ์ฑ - ์ค๋ณต ๋ณํฉ(upsert): ์๋น์ค ๋ ๋ฒจ
find โ ifPresent(quantity++) else insert - ์คํค๋ง ์ ์ฝ:
(cart_id, menu_id) UNIQUE๋ก ๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ณด์ฅ
- ๋จ์ผ ๋งค์ฅ ๊ท์น: ์ฒซ ์์ดํ
์
-
๊ฒฐ๊ณผ:
- ๊ฒฐ์ ๋จ๊ณ ์ค๋ฅ ์ ๊ฑฐ, CS ๊ฐ์
- ๋ฐ์ดํฐ ์ ํฉ์ฑ/๊ฐ๋ ์ฑ ํฅ์, ์ ์ง๋ณด์ ์ฉ์ด
-
๊ธฐ๋ฅ:
- ๊ธฐ์กด ๋งค์ฅ ๊ฒ์ ๊ธฐ๋ฅ์ ๊ฒฝ์ฐ Like ์ฟผ๋ฆฌ๋ฅผ ์ด์ฉํ ๋ฐฉ์์ผ๋ก ๊ฒ์ ๊ธฐ๋ฅ์ด ์๋
-
๋ฌธ์ ์ ์:
- Like ์ฟผ๋ฆฌ๋ฅผ ์ด์ฉํ ๋ฐฉ์์ผ๋ก ๊ฒ์ ๊ธฐ๋ฅ์ด ์๋ํ๋ค๋ณด๋
Full Scan Table๋ฌธ์ ๊ฐ ๋ฐ์ํ์ฌ, ๋งค์ฅ ์๊ฐ ์ฆ๊ฐํจ์๋ฐ๋ผ ์ฑ๋ฅ์ด ์ ํ๋ ์ฐ๋ ค
- Like ์ฟผ๋ฆฌ๋ฅผ ์ด์ฉํ ๋ฐฉ์์ผ๋ก ๊ฒ์ ๊ธฐ๋ฅ์ด ์๋ํ๋ค๋ณด๋
-
ํด๊ฒฐ:
Full Text IndexvsElasticsearch๋์ ๊ฒํFull Text Index๋ก ์ฑ๋ฅ ํ ์คํธ ์งํ ์ค, ๋ถ๋ถ ๊ฒ์ ๋ฏธ์ง์ ๋ฌธ์ ๋ฐ๊ฒฌ- ์ต์ข ์ ์ผ๋ก Elasticsearch ๋์
-
์ฑ๋ฅ ํ ์คํธ
- ํ
์คํธ ๋๊ตฌ:
Locust - ์๋๋ฆฌ์ค
- ์์ ๋งค์ฅ 30,000๊ฐ ์์ฑ (
๋ง์ง_1 ~ ๋ง์ง_9999 * 3) - ์ต๋ ๋์ ์ ์ ์ฌ์ฉ์ 1,000๋ช
- ์ด๋น 100๋ช ์ฉ ์ฆ๊ฐ
- ๊ฒ์ ์ ์ฐ์ฑ ๊ฒ์ฆ์ ์ํด
'๋ง','์ง'ํค์๋๋ก๋ ์์ฒญ
- ์์ ๋งค์ฅ 30,000๊ฐ ์์ฑ (
- ํ
์คํธ ๋๊ตฌ:
| URL (๊ฒ์ ํค์๋) | # Requests | # Fails | Median (ms) | 95%ile (ms) | 99%ile (ms) | Avg (ms) | Min | Max | Avg Size (B) | RPS | Failures/s |
|---|---|---|---|---|---|---|---|---|---|---|---|
/stores/search?keyword=๋ง |
25,350 | 0 | 2600 | 4300 | 5900 | 2655.31 | 34 | 17029 | 3514.89 | 60.3 | 0 |
/stores/search?keyword=๋ง์ง |
25,249 | 0 | 2600 | 4300 | 5900 | 2664.77 | 34 | 14819 | 3514.89 | 60.3 | 0 |
/stores/search?keyword=์กด์ฌํ์ง ์๋ ๋งค์ฅ |
25,292 | 25,292 | 2600 | 4300 | 5900 | 2636.52 | 21 | 11473 | 97.89 | 64.5 | 64.5 |
/stores/search?keyword=์ง |
25,341 | 0 | 2600 | 4300 | 5800 | 2643.49 | 31 | 12314 | 3514.89 | 57.8 | 0 |
| Aggregated | 101,232 | 25,292 | 2600 | 4300 | 5900 | 2650.02 | 21 | 17029 | 2661.18 | 242.9 | 64.5 |
| URL (๊ฒ์ ํค์๋) | # Requests | # Fails | Median (ms) | 95%ile (ms) | 99%ile (ms) | Avg (ms) | Min | Max | Avg Size (B) | RPS | Failures/s |
|---|---|---|---|---|---|---|---|---|---|---|---|
/stores/search?keyword=๋ง |
25,476 | 0 | 7 | 14 | 72 | 10.04 | 4 | 697 | 3407.89 | 166.6 | 0 |
/stores/search?keyword=๋ง์ง |
25,249 | 0 | 7 | 13 | 68 | 9.76 | 4 | 690 | 3407.89 | 166.7 | 0 |
/stores/search?keyword=์กด์ฌํ์ง ์๋ ๋งค์ฅ |
25,193 | 25,193 | 7 | 13 | 59 | 9.10 | 4 | 691 | 97.89 | 166.9 | 166.9 |
/stores/search?keyword=์ง |
25,445 | 0 | 7 | 14 | 64 | 9.72 | 4 | 692 | 3407.89 | 162.1 | 0 |
| Aggregated | 101,363 | 25,193 | 7 | 14 | 66 | 9.66 | 4 | 697 | 2585.21 | 662.3 | 166.9 |
| ํญ๋ชฉ | ๋์ ์ | ๋์ ํ | ๊ฐ์ ํญ |
|---|---|---|---|
| ํ๊ท ์๋ต ์๊ฐ | 2650.02 ms | 9.66 ms | ์ฝ 274๋ฐฐ ๊ฐ์ (-99.64%) |
| ์ ์ ํค์๋ ์คํจ์จ | 0% | 0% | ์ ์ง |
| ์ ์ฒด RPS | 242.9 | 662.3 | ์ฝ 2.7๋ฐฐ ์ฆ๊ฐ |
- ์ฑ๋ฅ ๊ฐ์ ์์ฝ
- ํ๊ท ์๋ต ์๊ฐ: ์ฝ 2600ms โ 10ms ์์ค์ผ๋ก ๋จ์ถ (260๋ฐฐ ์ด์ ๊ฐ์ )
- ์ด๋น ์ฒ๋ฆฌ๋(RPS): ์ฝ 240 โ 660 ์ด์ ์ฆ๊ฐ (2.7๋ฐฐ ํฅ์)
- ๊ฒ์ ๊ธฐ๋ฅ: ๋ถ๋ถ ๊ฒ์ ์ง์ (
'์ง'๋ฑ ํค์๋ ์ผ๋ถ๋ก๋ ๊ฒฐ๊ณผ ๋ฐํ ๊ฐ๋ฅ)
-
๊ธฐ๋ฅ:
- ์น์์ผ ์ฐ๊ฒฐ ๋ฐ ์ฑํ ๋ฐฉ ์ ์ฅ
- ์น์์ผ ์ธ์
์ ์ฅ
Map<roomId, Set<WebSocketSession>>- key๊ฐ ์ ์ฅํ ์ฑํ ๋ฐฉ์ id์ธ value๊ฐ Set์ ์์์ ์ธ์ ์ ์ฅ
- ๋ฉ์ธ์ง ์ ์ก
- ๋ฉ์ธ์ง ๋ฐ์ ์ ํด๋น ์ฑํ ๋ฐฉ์ ์ ์ ์ค์ธ ์ฌ์ฉ์, ์ฆ ์ ์ฅ๋ ์ธ์ ์ ์ฒด์ ๋ฉ์ธ์ง ์ ๋ฌ
- ์ฑํ
๋ฐฉ ํด์ฅ
- ์ฑํ ๋ฐฉ์์ ํด์ฅ ์ ์ ์ฅ๋ ์ธ์ ์ญ์ (๋ฉค๋ฒ ํํด๊ฐ ์๋๋ผ ์ฐ๊ฒฐ ์ข ๋ฃ)
-
๋ฌธ์ ์ ์:
- ์ ์์ ์ผ๋ก ์๋ํ์ง๋ง ๋จ์ผ ์๋ฒ์์๋ง ๋์
-
ํด๊ฒฐ:
- ์ฌ์ฉ ๊ธฐ์
- Kafka + STOMP
- Kafka: ์๋ฒ๊ฐ ์ฌ๋ฌ ๊ฐ์ธ ์ํฉ์์๋ ๋์ผํ ์ฑํ ๋ฐฉ์ ๋ชจ๋ ์ฌ์ฉ์๊ฐ ๋ฉ์ธ์ง๋ฅผ ๋ฐ์ ์ ์๋๋ก ๋์
- STOMP: ํ์คํ๋ ํ๋กํ ์ฝ ์ ๊ณต, ๊ตฌ๋ ์ ํตํด ์ฐ๊ฒฐ๋ ์ฌ์ฉ์ ๊ด๋ฆฌ ๊ฐ๋ฅ
- Kafka + STOMP
- ์น์์ผ ๊ตฌ์กฐ
- ์น์์ผ ์ฐ๊ฒฐ
- STOMP์
CONNECT์ JWT ์ธ์ฆ (ChannelInterceptor ์ฌ์ฉ) - ์๋ํฌ์ธํธ:
/ws
- STOMP์
- ๊ตฌ๋
- STOMP์
SUBSCRIBE๋ก ํน์ ์ฑํ ๋ฐฉ ๊ตฌ๋ - prefix:
/sub
- STOMP์
- ๋ฉ์ธ์ง ์ ์ก
- STOMP์
SEND๋ก ์๋ฒ์ ๋ฉ์ธ์ง ์ ์ก - ์๋ฒ์ Kafka Producer โ Kafka Broker ๋ฐํ
- Kafka Consumer โ Kafka Broker๋ก๋ถํฐ ์์ ํ STOMP Broker๋ก ๋ฐํ
- STOMP Broker โ ๊ตฌ๋ ์์๊ฒ ๋ธ๋ก๋์บ์คํธ
- prefix:
/pub
- STOMP์
- ์น์์ผ ์ฐ๊ฒฐ
- ์ฌ์ฉ ๊ธฐ์
-
์ฑ๋ฅ ํ ์คํธ
-
ํ๊ฒฝ
- ์๋ฒ 2๋ (
8080,8081ํฌํธ) - ๋์ผ ์ฑํ ๋ฐฉ์ ์์ชฝ ์๋ฒ์์ ๊ตฌ๋
- ์๋ฒ 2๋ (
-
๊ฒฐ๊ณผ
- ์๋ก ๋ค๋ฅธ ์๋ฒ์ ์๋ ์ฌ์ฉ์๋ ๋ฉ์ธ์ง ์์ ๊ฐ๋ฅ
- Kafka๋ฅผ ํตํ ๋ถ์ฐ ์ฒ๋ฆฌ๋ก ๋ค์ค ์๋ฒ ํ๊ฒฝ์์๋ ์ค์๊ฐ ์ฑํ ์ ์ ์๋
-
-
๊ธฐ๋ฅ:
- ์ ์ ๋๋ค์ or ์ด๋ฆ์ผ๋ก ๊ฒ์
-
๋ฌธ์ ์ ์:
- ํ์์ด ๋ง์ด ์ง ์๋ก ๋๋ค์์ด๋ ์ด๋ฆ ๊ฒ์ ์ ๋๋ ์ด๊ฐ ๋ฐ์
-
ํด๊ฒฐ:
- redis๋ฅผ ์ด์ฉํ ์บ์ฑ
- ์ธ๋ฑ์ฑ์ ์ด์ฉํ ๋น ๋ฅธ ๊ฒ์
- redis ์บ์ฑ + ์ธ๋ฑ์ฑ ๋์ ์ ์ฉ
-
์ฑ๋ฅ ํ ์คํธ
- ํ
์คํธ ๋๊ตฌ:
JMeter - ์๋๋ฆฌ์ค
- ๊ฐ์์ ์ฌ์ฉ์๋ฅผ 10,000๋ช ์ผ๋ก ์ค์
- 10,000๋ช ์ ์ฌ์ฉ์๊ฐ 600์ด ๋์ ๊ท ๋ฑํ๊ฒ ์ ์
- ๋์์ ์ ์ ์ ์๋ฒ์ ์ฑ๋ฅ ๋ฐ ์๋ต ์๊ฐ ์ธก์
- ํ
์คํธ ๋ฒ์ :
v1: ๊ธฐ์กด APIv2: ์ด๋ฆ/๋๋ค์ ์ธ๋ฑ์ฑ ์ ์ฉv3: Redis ์บ์ฑ ์ ์ฉv4: Redis ์บ์ฑ + ์ธ๋ฑ์ฑ
- ํ
์คํธ ๋๊ตฌ:
| ๋ฒ์ | # Samples | Average (ms) | Min (ms) | Max (ms) | Std. Dev. | Error % | Throughput | Received KB/sec | Sent KB/sec | Avg. Bytes |
|---|---|---|---|---|---|---|---|---|---|---|
| v1 | 10,000 | 3 | 2 | 38 | 1.02 | 0.00% | 16.7/sec | 9.60 | 6.93 | 589.9 |
| v2 | 10,000 | 2 | 1 | 26 | 0.94 | 0.00% | 16.7/sec | 9.60 | 6.98 | 589.9 |
| v3 | 10,000 | 2 | 1 | 27 | 0.86 | 0.00% | 16.7/sec | 9.60 | 6.98 | 589.9 |
| v4 | 10,000 | 1 | 1 | 59 | 1.00 | 0.00% | 16.7/sec | 9.63 | 7.00 | 591.9 |
v4์ ์ต๋ ์๋ต ์๊ฐ์ด ๋ค๋ฅธ ๋ฒ์ ๋ณด๋ค ๋๊ฒ ๋ํ๋ฌ์ผ๋, ์ด๋ ์บ์ ์ด๊ธฐ ์ ์ฉ ์๋ ํน์ ์ค๋ ๋์์ ์บ์ ๊ฐฑ์ ๋ฑ์ ์ํฅ์ผ๋ก ๋ฐ์ํ ์ผ์์ ์ง์ฐ์ผ๋ก ํ๋จ
๋๋ถ๋ถ ์์ฒญ์ 1ms ๋ด ์ฒ๋ฆฌ๋์ด ์ฑ๋ฅ์ด ๊ฐ์ฅ ์ฐ์ํ๋ฉฐ, ๋ฐ๋ผ์ v4 ๋ฒ์ ์ ์ฑํ
- ์ฑ๋ฅ ๊ฐ์ ์์ฝ
- ํ๊ท ์๋ต ์๊ฐ: 3ms โ 1ms ์์ค์ผ๋ก ๋จ์ถ (์ฝ 3๋ฐฐ ๊ฐ์ )
-
๊ธฐ๋ฅ:
- ๊ธฐ์กด ์ฃผ๋ฌธ ๋ด์ญ ๋จ์ผ ์กฐํ ๊ธฐ๋ฅ์ ๊ฒฝ์ฐ
QueryDSL์ ์ด์ฉํ ๋ฐฉ์์ผ๋ก ๊ฒ์ ๊ธฐ๋ฅ์ด ์๋
- ๊ธฐ์กด ์ฃผ๋ฌธ ๋ด์ญ ๋จ์ผ ์กฐํ ๊ธฐ๋ฅ์ ๊ฒฝ์ฐ
-
๋ฌธ์ ์ ์:
- ์ฃผ๋ฌธ ๋ด์ญ ๋จ์ผ ์กฐํ ์ ๋งค๋ฒ DB ์กฐํ๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ์๋ต์ด ์ง์ฐ๋ ์ฐ๋ ค
-
ํด๊ฒฐ:
- ์ฒซ ์กฐํ ์ DB์์ ๋ฐ์ดํฐ ์กฐํ ํ ์บ์์ ์ ์ฅ
- ์ดํ ๋์ผ ๋ด์ญ ์กฐํ ์ ์บ์์์ ๋น ๋ฅด๊ฒ ์กฐํ
-
์ฑ๋ฅ ํ ์คํธ
- ํ
์คํธ ๋๊ตฌ:
JMeter - ์๋๋ฆฌ์ค
- ์ ์ ๊ฐ ์ฃผ๋ฌธ ๋ด์ญ ๋จ์ผ ์กฐํ๋ฅผ 15๋ฒ ๋ฐ๋ณต(๊ฐ ์์ฒญ ๊ฐ๊ฒฉ 500ms)
- ํ
์คํธ ๋๊ตฌ:
| ๊ตฌ๋ถ | Average | Median | 90% Line | 95% Line | 99% Line | Min | Max |
|---|---|---|---|---|---|---|---|
| ์บ์ฑ ์ ์ฉ ์ | 13 | 13 | 16 | 16 | 30 | 11 | 30 |
| ์บ์ฑ ์ ์ฉ ํ | 9 | 8 | 10 | 10 | 37 | 7 | 37 |
- ์ฑ๋ฅ ๊ฐ์ ์์ฝ
- ํ๊ท ์๋ต ์๊ฐ: ์ฝ 13ms โ 9ms ์์ค์ผ๋ก ๋จ์ถ (์ฝ 31% ๊ฐ์)
- Max์ 99% Line ๊ฐ ์ฆ๊ฐ โ ์ฒซ ๋ฒ์งธ ์กฐํ ์ DB ์กฐํ + ์บ์ ์ ์ฅ์ผ๋ก ์ธํ ์ผ์์ ์ง์ฐ
-
๊ธฐ๋ฅ:
- ๊ธฐ์กด ์ฃผ๋ฌธ ๋ด์ญ ํ์ด์ง ์กฐํ ๊ธฐ๋ฅ์ ๊ฒฝ์ฐ
QueryDSL์ ์ด์ฉํ ๋ฐฉ์์ผ๋ก ๊ฒ์ ๊ธฐ๋ฅ์ด ์๋
- ๊ธฐ์กด ์ฃผ๋ฌธ ๋ด์ญ ํ์ด์ง ์กฐํ ๊ธฐ๋ฅ์ ๊ฒฝ์ฐ
-
๋ฌธ์ ์ ์:
-
์ฃผ๋ฌธ ๋ด์ญ์ด ๋ง์ ๊ฒฝ์ฐ ์กฐํ ์๋ ์ง์ฐ ๋ฐ DB ๋ถํ ๋ฐ์ ์ฐ๋ ค
-
ํด๊ฒฐ:
-
์ปค๋ฒ๋ง ์ธ๋ฑ์ค๋ฅผ ์ ์ฉ
order ID๋ชฉ๋ก์ ์กฐํ ํ ํด๋น ID๋ก ์ค์ ๋ฐ์ดํฐ ์กฐํ
-
์ ์ฉ ์ ์ฝ๋
@Override public Page<OrderResponseDto> findOrdersByUserId(Long userId, Pageable pageable, String menuName, String storeName, LocalDate startDate, LocalDate endDate, OrderStatus status) { List<OrderResponseDto> content = queryFactory.select(new QOrderResponseDto(order)) .from(order) .join(order.store).fetchJoin() // ์ฃผ๋ฌธ๊ณผ ๊ฐ๊ฒ ์ ๋ณด๋ฅผ ํ ๋ฒ์ ์กฐํ .where(searchOrder(userId, menuName, storeName, startDate, endDate, status)) .orderBy(order.createdAt.desc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); JPAQuery<Long> countQuery = queryFactory.select(order.count()) .from(order) .where(searchOrder(userId, menuName, storeName, startDate, endDate, status)); return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); }
- ์ ์ฉ ํ ์ฝ๋
@Override public Page<OrderResponseDto> findOrdersByUserId(Long userId, Pageable pageable, String menuName, String storeName, LocalDate startDate, LocalDate endDate, OrderStatus status) { // order id ๋จผ์ ์กฐํ List<Long> idList = queryFactory .select(order.id) .from(order) .where(searchOrder(userId, menuName, storeName, startDate, endDate, status)) .orderBy(order.createdAt.desc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); // idList๊ฐ ๋น์ด์๋ ๊ฒฝ์ฐ ๋น ํ์ด์ง ๋ฐํ if (idList.isEmpty()) { return new PageImpl<>(Collections.emptyList(), pageable, 0); } // id๋ก ์ค์ ๋ฐ์ดํฐ ์กฐํ List<OrderResponseDto> content = queryFactory.select(new QOrderResponseDto(order)) .from(order) .join(order.store).fetchJoin() // ์ฃผ๋ฌธ๊ณผ ๊ฐ๊ฒ ์ ๋ณด๋ฅผ ํ ๋ฒ์ ์กฐํ .where(order.id.in(idList)) .orderBy(order.createdAt.desc()) .fetch(); JPAQuery<Long> countQuery = queryFactory.select(order.count()) .from(order) .where(searchOrder(userId, menuName, storeName, startDate, endDate, status)); return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); }
-
-
-
์ฑ๋ฅ ํ ์คํธ
- ํ
์คํธ ๋๊ตฌ:
JMeter - ์๋๋ฆฌ์ค
- ์ ์ 1๋ช ์ด 500๊ฐ์ ์ฃผ๋ฌธ ๋ด์ญ ์กฐํ, 1์ด ๊ฐ๊ฒฉ 10ํ ๋ฐ๋ณต
- ํ
์คํธ ๋๊ตฌ:
| ๊ตฌ๋ถ | Average | Median | 90% Line | 95% Line | 99% Line | Min | Max |
|---|---|---|---|---|---|---|---|
| ์ธ๋ฑ์ค ์ ์ฉ ์ | 10.75 | 10.75 | 12.75 | 13 | 14.25 | 9.75 | 14.25 |
| ์ธ๋ฑ์ค ์ ์ฉ ํ | 8.25 | 8.25 | 9.25 | 9.25 | 10.5 | 7.75 | 10.5 |
- ์ฑ๋ฅ ๊ฐ์ ์์ฝ
- ํ๊ท ์๋ต ์๊ฐ: ์ฝ 10.75ms โ 8.25ms ์์ค์ผ๋ก ๋จ์ถ (์ฝ 23% ๊ฐ์)
- ์์ฒญ 9.25ms ์ด๋ด ์ฒ๋ฆฌ
-
๊ธฐ๋ฅ:
- ๊ณต์ ์ฅ๋ฐ๊ตฌ๋(์ฑํ ๋ฐฉ ๋จ์) ์กฐํ ์ **๋ถํ์ํ ์ถ๊ฐ ์ฟผ๋ฆฌ(N+1)**์ ์๋ฒ ๋ฉ๋ชจ๋ฆฌ ์ง๊ณ๋ฅผ ์ค์ฌ ์๋ต ์๊ฐ์ ์์ ํ
-
๋ฌธ์ ์ ์:
findBySharedCart_ChatRoom_Id(roomId)ํธ์ถ ์Menu/User/SharedCart๋ฅผ LAZY ๋ก๋ฉํ๋ฉฐ N+1 ๋ฐ์ ๊ฐ๋ฅ- ์ฐธ์ฌ์ ์๋ฅผ Java ์คํธ๋ฆผ์ผ๋ก
distinct()๊ณ์ฐ โ ๋ชจ๋ ํญ๋ชฉ์ ๋ฉ๋ชจ๋ฆฌ๋ก ๋์ด์ ์ง๊ณ ๋น์ฉ ์ฆ๊ฐ
-
ํด๊ฒฐ:
Fetch Join์ผ๋ก ํ ๋ฒ์ ๋ก๋ฉ (N+1 ์ ๊ฑฐ)- DB์์ ์ฐธ์ฌ์ ์ ์ง๊ณ (COUNT DISTINCT)
- ์๋น์ค ๋ก์ง ๊ต์ฒด (๋ฉ๋ชจ๋ฆฌ ์ง๊ณ โ DB ์ง๊ณ)
- ์ธ๋ฑ์ค ์ถ๊ฐ (์กฐํ/๊ถํ๊ฒ์ฌ ๊ฐ์)
-
์ฑ๋ฅ ํ ์คํธ
- ํ
์คํธ ๋๊ตฌ:
JMeter - ์๋๋ฆฌ์ค
- ๋์ผ ์ฑํ
๋ฐฉ์
N๊ฐ ํญ๋ชฉ, ๋ค์ ์ฌ์ฉ์ ์ฐธ์ฌ - ์ ์ 1๋ช ์ด GET /chats/{roomId}/.../shared-carts/items 10~15ํ ๋ฐ๋ณต (์์ฒญ ๊ฐ๊ฒฉ 500ms)
- ๋์ผ ์ฑํ
๋ฐฉ์
- ํ
์คํธ ๋๊ตฌ:
| ๊ตฌ๋ถ | Average | Median | 90% Line | 95% Line | 99% Line | Min | Max |
|---|---|---|---|---|---|---|---|
| ์ธ๋ฑ์ค ์ ์ฉ ์ | 11.50 | 11.50 | 13.50 | 14.25 | 16.00 | 9.75 | 16.00 |
| ์ธ๋ฑ์ค ์ ์ฉ ํ | 8.75 | 8.50 | 9.75 | 10.25 | 12.25 | 7.50 | 12.25 |
- ์ฑ๋ฅ ๊ฐ์ ์์ฝ
N+1์ ๊ฑฐ +DB์ง๊ณ + ์ธ๋ฑ์ค๋ก ํ๊ท 24% ๋จ์ถ- 95% ๊ตฌ๊ฐ 28% ๊ฐ์
ํด๋ฆญํ์ฌ ์ค์น ๋ฐ ์คํ ๋ฐฉ๋ฒ์ ํ์ธํ์ธ์
- ๋ ํฌ์งํ ๋ฆฌ ํด๋ก
- ํฐ๋ฏธ๋ ๋๋ ๋ช ๋ น ํ๋กฌํํธ์์ ๋ค์ ๋ช ๋ น์ด๋ฅผ ์คํํ์ฌ ํ๋ก์ ํธ ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ํด๋ก ํฉ๋๋ค.
git clone https://github.com/hojunyun-dev/Eat_Together.git- Docker ์คํ
docker-compose up -d --build- ์ค๋ฅ ๋ฐ์ ์:
docker-compose down -v- Docker์ ํ๋ก์ ํธ๊ฐ ์ ์์ ์ผ๋ก ์ฌ๋ผ์ ์๋์ง ํ์ธ
ํด๋ฆญํ์ฌ ์ฌ์ ์ค๋น๋ฅผ ํ์ธํ์ธ์
- JDK 17 ๋ฒ์ ์ ์ค๋นํด์ฃผ์ธ์.
- Docker๋ฅผ ์ค์นํด์ฃผ์ธ์ ( ์ต์ ๋ฒ์ ์ผ๋ก ์ค๋นํด์ฃผ์ธ์ )
- ํ๊ฒฝ ๋ณ์ ์ค์
- backend ๋๋ ํ ๋ฆฌ๋ก ์ด๋ํ์ฌ .env.example ํ์ผ์ ์ฐธ๊ณ ํด .env ํ์ผ์ ์์ฑํ๊ณ ํ์ํ ํ๊ฒฝ ๋ณ์๋ค์ ์ค์ ํฉ๋๋ค.
- cd backend
.env ํ์ผ ๋ด์ฉ ์์:
# Google
GOOGLE_OAUTH_CLIENT_ID = { Google_CLIENT_ID }
GOOGLE_OAUTH_CLIENT_SECRET = { Google_SECRET_KEY }
# Kakao
KAKAO_OAUTH_CLIENT_ID = { Kakao_CLIENT_ID }
KAKAO_OAUTH_CLIENT_SECRET = { Kakao_SECRET_KEY }
# naver
NAVER_OAUTH_CLIENT_ID = { Naver_CLIENT_ID }
NAVER_OAUTH_CLIENT_SECRET = { Naver_SECRET_KEY }
# Mysql
MYSQL_PORT = { MYSQL_PORT }
MYSQL_USERNAME = { DB_USERNAME }
MYSQL_PASSWORD = { DB_PASSWORD }
MYSQL_DBNAME = { DBNAME }
# Redis
REDIS_PORT = { REDIS_PORT }
# ์ด ๋ถ๋ถ์ ๊ฐ์ ์ค์ ์ ๋ง๊ฒ ์ค์ ํด์ฃผ์ธ์
- ์ด ํ๋ก์ ํธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ๊ธฐ์ฌ๋ฅผ ํ์ํฉ๋๋ค! ํ๋ก์ ํธ๋ฅผ ํจ๊ป ๋ฐ์ ์ํค๊ธฐ ์ํ ๊ฐ์ด๋๋ผ์ธ์ ๋ฐ๋ฆ ๋๋ค.
- ์ด์ ์์ฑ: ์๋ก์ด ๊ธฐ๋ฅ ์ ์, ๋ฒ๊ทธ ๋ฆฌํฌํธ, ๊ฐ์ ์ฌํญ ๋ฑ์ ๋ํด ๋จผ์ ์ด์๋ฅผ ์์ฑํ์ฌ ๋ ผ์ํฉ๋๋ค.
- ํฌํฌ (Fork): ๋ณธ ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ๊ฐ์ธ GitHub ๊ณ์ ์ผ๋ก ํฌํฌํฉ๋๋ค.
- ํด๋ก (Clone): ํฌํฌํ ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ๋ก์ปฌ ํ๊ฒฝ์ผ๋ก ํด๋ก ํฉ๋๋ค.
git clone https://github.com/hojunyun-dev/Eat_Together.git
- ๋ธ๋์น ์์ฑ: ์์ ํ ๋ด์ฉ์ ๋ง๋ ์ ๋ธ๋์น๋ฅผ ์์ฑํฉ๋๋ค.
- ์๋ก์ด ๊ธฐ๋ฅ: git checkout -b feature/OOO-๊ธฐ๋ฅ๋ช
- ์ฝ๋ ์์ฑ ๋ฐ ํ ์คํธ: ํด๋น ๋ธ๋์น์์ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์ถฉ๋ถํ ํ ์คํธ๋ฅผ ์งํํฉ๋๋ค.
- ์ปค๋ฐ (Commit): ์ปค๋ฐ ์ปจ๋ฒค์ ์ ๋ฐ๋ผ ๋ช ํํ ์ปค๋ฐ ๋ฉ์์ง๋ฅผ ์์ฑํฉ๋๋ค.
git commit -m "feat: OOO ๊ธฐ๋ฅ ์ถ๊ฐ"- ํธ์ (Push): ๋ณ๊ฒฝ์ฌํญ์ ๋ณธ์ธ์ ์๊ฒฉ ์ ์ฅ์์ ํธ์ํฉ๋๋ค.
git push origin feature/OOO-๊ธฐ๋ฅ๋ช- ํ ๋ฆฌํ์คํธ (Pull Request) ์์ฑ: ๋ณธ ๋ ํฌ์งํ ๋ฆฌ์ main ๋ธ๋์น๋ก ํ ๋ฆฌํ์คํธ๋ฅผ ์์ฑํฉ๋๋ค. ๋ณ๊ฒฝ ์ฌํญ์ ๋ํ ์์ธํ ์ค๋ช ์ ํฌํจํด์ฃผ์ธ์.
- ์ปค๋ฐ ๋ฉ์์ง: Conventional Commits ๊ท๊ฒฉ์ ๋ฐ๋ฆ ๋๋ค. (์: feat: add user authentication, fix: resolve login bug)
- ๋ธ๋์น ์ ๋ต: main ๋ธ๋์น๋ ํญ์ ์์ ์ ์ธ ๋ฒ์ ์ ์ ์งํ๋ฉฐ, ๊ฐ๋ฐ์ ํผ์ฒ ๋ธ๋์น์์ ์งํ๋ฉ๋๋ค.



















