Skip to content

hojunyun-dev/Eat_Together

Repository files navigation

๐Ÿ›ต ๊ฐ™์ด๋จน์ž ํ”„๋กœ์ ํŠธ ๐Ÿ›ต

  • ํ˜ผ์ž ๋ฐฐ๋‹ฌ ์Œ์‹์„ ์‹œ์ผœ ๋จน๋‹ค ๋ณด๋ฉด ๋ฉ”๋‰ด ์„ ํƒ๋„ ์–ด๋ ต๊ณ  ๋ฐฐ๋‹ฌ๋น„๋„ ๋ถ€๋‹ด๋˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ๊ฐ™์ด๋จน์ž๋Š” ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด, ๋ฐฐ๋‹ฌ์„ โ€˜ํ•จ๊ป˜โ€™๋กœ ๋ฐ”๊พธ๋Š” ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค.

    Banner

    Banner

๐Ÿ“Œ ์ฃผ์š” ๊ธฐ๋Šฅ

  • ๋ฉ”๋‰ด๋ฅผ ๊ณต์œ  ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ๋‹ด์•„ ํ•จ๊ป˜ ์ฃผ๋ฌธ
  • ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…์œผ๋กœ ์ฃผ๋ณ€ ์‚ฌ๋žŒ๊ณผ ๋ฉ”๋‰ด์™€ ๋ฐฐ๋‹ฌ ์กฐ์œจ

๐Ÿ’ก ํ•ต์‹ฌ ๊ฐ€์น˜ ๋ฐ ์ฐจ๋ณ„์ 

  • ํ˜ผ์ž์„œ ๋ฐฐ๋‹ฌ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๋ถ€๋‹ด ์ตœ์†Œํ™”
  • ์‹ค์‹œ๊ฐ„ ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜์œผ๋กœ ํ•จ๊ป˜ ์ฃผ๋ฌธ ํšจ์œจ ๊ทน๋Œ€ํ™”
  • ๋ฐฐ๋‹ฌ์„ ํ˜ผ์ž๊ฐ€ ์•„๋‹Œ ์—ฌ๋Ÿฟ์ด์„œ ๋” ํŽธ๋ฆฌํ•˜๊ณ  ์ฆ๊ฒ๊ฒŒ ์ฆ๊ธธ ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„

๐Ÿ—๏ธ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

ํด๋ฆญํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ํŒŒ์ผ ๊ตฌ์กฐ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

๐Ÿ“ฆ 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
โ”— ๐Ÿ“‚ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๐Ÿ’ป ๊ธฐ์ˆ  ์Šคํƒ

Backend

Database & Cache

Infra & CI/CD

Docker githubactions AWS EC2

Observability

Prometheus Grafana Kibana Elasticsearch Filebeat

Test Tools

Postmanr Apache Jmeter Locust

Collaboration

Slack Git GitHub Notion draw.io Google Docs

๐Ÿ“ Architecture

Architecture.png

๐Ÿ’ฌ ERD

ERD.png


๐Ÿ› ๏ธ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

โณ Redis ์บ์‹ฑ ์ ์šฉ ์‹œ LocalDateTime ์ง๋ ฌํ™” ์˜ค๋ฅ˜

  • ๋ฌธ์ œ ์ƒํ™ฉ: ์บ์‹ฑ๋œ 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 ์บ์‹œ ํ‚ค ์ถฉ๋Œ๋กœ ์ธํ•œ ๋ฉ”๋‰ด ์กฐํšŒ ๋ฌธ์ œ

  • ๋ฌธ์ œ์ƒํ™ฉ: ๋ฉ”๋‰ด ๋‹จ๊ฑด ์กฐํšŒ ์‹œ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋งค์žฅ์—์„œ Redis์— ์ €์žฅ๋œ ๋ฉ”๋‰ด๊ฐ€ ์กฐํšŒ ์„ฑ๊ณต์œผ๋กœ ์‘๋‹ต๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒ

    1. Redis์— menuId=3์ด ์ด๋ฏธ ์บ์‹ฑ๋˜์–ด ์žˆ์Œ

    2. ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋งค์žฅ storeId=333์—์„œ menuId=3 ์กฐํšŒ ์‹œ๋„

    3. ์กฐํšŒ๊ฐ€ ์„ฑ๊ณตํ•ด๋ฒ„๋ฆผ(์˜ค๋™์ž‘)

  • ์›์ธ ๋ถ„์„:

    • ์บ์‹œ ํ‚ค๊ฐ€ menuId ๋‹จ์ผ ๊ฐ’๋งŒ ํฌํ•จํ•˜๋„๋ก ์„ค๊ณ„๋จ
    • ์„œ๋กœ ๋‹ค๋ฅธ ๋งค์žฅ์ด๋ผ๋„ menuId๊ฐ€ ๊ฐ™์œผ๋ฉด ๊ฐ™์€ ์บ์‹œ ์—”ํŠธ๋ฆฌ๋ฅผ ์ฐธ์กฐ โ†’ ๋งค์žฅ ๊ฒ€์ฆ ์‹คํŒจ
  • ํ•ด๊ฒฐ:

    • ์บ์‹œ ํ‚ค ๊ตฌ์กฐ ๋ณ€๊ฒฝ
      • ๊ธฐ์กด: menu:{menuId}

      • ๋ณ€๊ฒฝ: menu:{storeId}:{menuId}

  • ๊ฒฐ๊ณผ:

    • ์บ์‹œ ํ‚ค์— storeId๋ฅผ ํฌํ•จ์‹œ์ผœ ๋งค์žฅ ๋‹จ์œ„ ๊ฒ€์ฆ์ด ๊ฐ€๋Šฅํ•ด์ง
    • ์ž˜๋ชป๋œ ๋งค์žฅ์—์„œ ๊ฐ™์€ menuId ์š”์ฒญ ์‹œ ์บ์‹œ ์žฌ์‚ฌ์šฉ์ด ์ฐจ๋‹จ๋˜์–ด, ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ํ™•๋ณด

๐Ÿ” ์ฃผ๋ฌธ ๋‚ด์—ญ ์กฐํšŒ ์‹œ N+1 ๋ฌธ์ œ ๋ฐœ์ƒ

  • ๋ฌธ์ œ ์ƒํ™ฉ:

    • ์ฃผ๋ฌธ ํŽ˜์ด์ง• ์กฐํšŒ ์‹œ ์ฃผ๋ฌธ ์ˆ˜ ๋งŒํผ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ๋ฐœ์ƒ (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() // ์ฃผ๋ฌธ์•„์ดํ…œ๊ณผ ๋ฉ”๋‰ด๋ฅผ ํ•œ ๋ฒˆ์— ์กฐํšŒ

๐Ÿณ Grafana & Prometheus / Docker-compose ์—ฐ๋™ ์˜ค๋ฅ˜

  • ๋ฌธ์ œ ์ƒํ™ฉ:

    • 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
    
  • ์›์ธ ๋ถ„์„:

    • ํ•ต์‹ฌ ์›์ธ: ์ปจํ…Œ์ด๋„ˆ์—์„œ 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์—์„œ ์ •์ƒ ๊ธฐ๋™ ํ™•์ธ

๐Ÿ” ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ํ›„ ์ธ์ฆ

  • ๋ฌธ์ œ ์ƒํ™ฉ:

    • ๊ธฐ์กด์˜ ์ธ์ฆ ๊ณผ์ •์€ jwtFilter๋ฅผ ํ†ตํ•ด ์ง„ํ–‰๋๋‹ค.
    • ๊ทธ๋Ÿฌ๋‚˜ ์›น์†Œ์ผ“์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•œ ํ›„๋ถ€ํ„ฐ๋Š” ์ธ์ฆ ๊ณผ์ •์ด ๊ฑฐ์ณ์ง€์ง€ ์•Š์•˜๋‹ค.
  • ์›์ธ ๋ถ„์„:

    • JwtFilter๊ฐ€ ์ƒ์†๋ฐ›์€ OncePerRequestFilter๋Š” Http ์š”์ฒญ์—์„œ๋งŒ ์ž‘๋™ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ํ”„๋กœํ† ์ฝœ์ธ ์›น์†Œ์ผ“์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ ํ•˜๋ฉด ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค.
    • ๋”ฐ๋ผ์„œ JwtFilter๋ฅผ ํ†ตํ•œ ์ธ์ฆ๊ณผ์ •์„ ๊ฑฐ์น  ์ˆ˜ ์—†๋‹ค.
  • ํ•ด๊ฒฐ:

    • Command(Connect, Subscribe, Send, Disconnect)๋ฅผ ํฌํ•จํ•œ STOMP ํ”„๋ ˆ์ž„์„ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ „์†กํ•˜๋ฉด ์ค‘๊ฐ„์—์„œ ChannelInterceptor๊ฐ€ ๊ฐ€๋กœ์ฑˆ๋‹ค.
    • ์šฉ๋„์— ๋งž๊ฒŒ ๊ฐ ๋ช…๋ น์–ด ๋ณ„๋กœ ์ธ์ฆ ๊ณผ์ •์„ ์ž‘์„ฑํ•˜๋ฉด ๊ทธ ์ฝ”๋“œ์— ๋”ฐ๋ผ ๊ฒ€์‚ฌํ•œ ํ›„ ์ธ์ฆ๋˜์—ˆ์„ ๊ฒฝ์šฐ ๋ฉ”์„ธ์ง€๋ฅผ ์ „์†กํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.
  • ๊ฒฐ๊ณผ:

    • ํ…Œ์ŠคํŠธ ์‹œ ํ† ํฐ์„ ํฌํ•จํ•˜์ง€ ์•Š์œผ๋ฉด 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 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ, ๋งค์žฅ ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ•จ์—๋”ฐ๋ผ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ์šฐ๋ ค
  • ํ•ด๊ฒฐ:

    • Full Text Index vs Elasticsearch ๋„์ž… ๊ฒ€ํ† 
    • Full Text Index๋กœ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์ง„ํ–‰ ์ค‘, ๋ถ€๋ถ„ ๊ฒ€์ƒ‰ ๋ฏธ์ง€์› ๋ฌธ์ œ ๋ฐœ๊ฒฌ
    • ์ตœ์ข…์ ์œผ๋กœ Elasticsearch ๋„์ž…
  • ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ

    • ํ…Œ์ŠคํŠธ ๋„๊ตฌ: Locust
    • ์‹œ๋‚˜๋ฆฌ์˜ค
      • ์ž„์˜ ๋งค์žฅ 30,000๊ฐœ ์ƒ์„ฑ (๋ง›์ง‘_1 ~ ๋ง›์ง‘_9999 * 3)
      • ์ตœ๋Œ€ ๋™์‹œ ์ ‘์† ์‚ฌ์šฉ์ž 1,000๋ช…
      • ์ดˆ๋‹น 100๋ช…์”ฉ ์ฆ๊ฐ€
      • ๊ฒ€์ƒ‰ ์œ ์—ฐ์„ฑ ๊ฒ€์ฆ์„ ์œ„ํ•ด '๋ง›', '์ง‘' ํ‚ค์›Œ๋“œ๋กœ๋„ ์š”์ฒญ

๐Ÿ“‰ ๋„์ž… ์ „ (LIKE ์ฟผ๋ฆฌ ๊ธฐ๋ฐ˜)

์ƒ์„ธ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
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

๐Ÿš€ ๋„์ž… ํ›„ (Elasticsearch ๊ธฐ๋ฐ˜)

์ƒ์„ธ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
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๋ฐฐ ํ–ฅ์ƒ)
    • ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ: ๋ถ€๋ถ„ ๊ฒ€์ƒ‰ ์ง€์› ('์ง‘' ๋“ฑ ํ‚ค์›Œ๋“œ ์ผ๋ถ€๋กœ๋„ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ ๊ฐ€๋Šฅ)

๐Ÿ’ฌ ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ํ๋ฆ„

  • ๊ธฐ๋Šฅ:

    1. ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ๋ฐ ์ฑ„ํŒ…๋ฐฉ ์ž…์žฅ
    2. ์›น์†Œ์ผ“ ์„ธ์…˜ ์ €์žฅ
      • Map<roomId, Set<WebSocketSession>>
      • key๊ฐ€ ์ž…์žฅํ•œ ์ฑ„ํŒ…๋ฐฉ์˜ id์ธ value๊ฐ’ Set์˜ ์š”์†Œ์— ์„ธ์…˜ ์ €์žฅ
    3. ๋ฉ”์„ธ์ง€ ์ „์†ก
      • ๋ฉ”์„ธ์ง€ ๋ฐœ์ƒ ์‹œ ํ•ด๋‹น ์ฑ„ํŒ…๋ฐฉ์— ์ ‘์† ์ค‘์ธ ์‚ฌ์šฉ์ž, ์ฆ‰ ์ €์žฅ๋œ ์„ธ์…˜ ์ „์ฒด์— ๋ฉ”์„ธ์ง€ ์ „๋‹ฌ
    4. ์ฑ„ํŒ…๋ฐฉ ํ‡ด์žฅ
      • ์ฑ„ํŒ…๋ฐฉ์—์„œ ํ‡ด์žฅ ์‹œ ์ €์žฅ๋œ ์„ธ์…˜ ์‚ญ์ œ(๋ฉค๋ฒ„ ํƒˆํ‡ด๊ฐ€ ์•„๋‹ˆ๋ผ ์—ฐ๊ฒฐ ์ข…๋ฃŒ)
  • ๋ฌธ์ œ ์ •์˜:

    • ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜์ง€๋งŒ ๋‹จ์ผ ์„œ๋ฒ„์—์„œ๋งŒ ๋™์ž‘
  • ํ•ด๊ฒฐ:

    • ์‚ฌ์šฉ ๊ธฐ์ˆ 
      • Kafka + STOMP
        • Kafka: ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์ธ ์ƒํ™ฉ์—์„œ๋„ ๋™์ผํ•œ ์ฑ„ํŒ…๋ฐฉ์˜ ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ๋„์ž…
        • STOMP: ํ‘œ์ค€ํ™”๋œ ํ”„๋กœํ† ์ฝœ ์ œ๊ณต, ๊ตฌ๋…์„ ํ†ตํ•ด ์—ฐ๊ฒฐ๋œ ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ๊ฐ€๋Šฅ
    • ์›น์†Œ์ผ“ ๊ตฌ์กฐ
      1. ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ
        • STOMP์˜ CONNECT ์‹œ JWT ์ธ์ฆ (ChannelInterceptor ์‚ฌ์šฉ)
        • ์—”๋“œํฌ์ธํŠธ: /ws
      2. ๊ตฌ๋…
        • STOMP์˜ SUBSCRIBE๋กœ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ ๊ตฌ๋…
        • prefix: /sub
      3. ๋ฉ”์„ธ์ง€ ์ „์†ก
        • STOMP์˜ SEND๋กœ ์„œ๋ฒ„์— ๋ฉ”์„ธ์ง€ ์ „์†ก
        • ์„œ๋ฒ„์˜ Kafka Producer โ†’ Kafka Broker ๋ฐœํ–‰
        • Kafka Consumer โ†’ Kafka Broker๋กœ๋ถ€ํ„ฐ ์ˆ˜์‹  ํ›„ STOMP Broker๋กœ ๋ฐœํ–‰
        • STOMP Broker โ†’ ๊ตฌ๋…์ž์—๊ฒŒ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ
        • prefix: /pub
  • ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ

    • ํ™˜๊ฒฝ

      • ์„œ๋ฒ„ 2๋Œ€ (8080, 8081 ํฌํŠธ)
      • ๋™์ผ ์ฑ„ํŒ…๋ฐฉ์„ ์–‘์ชฝ ์„œ๋ฒ„์—์„œ ๊ตฌ๋…
    • ๊ฒฐ๊ณผ

      • ์„œ๋กœ ๋‹ค๋ฅธ ์„œ๋ฒ„์— ์žˆ๋Š” ์‚ฌ์šฉ์ž๋„ ๋ฉ”์„ธ์ง€ ์ˆ˜์‹  ๊ฐ€๋Šฅ
      • Kafka๋ฅผ ํ†ตํ•œ ๋ถ„์‚ฐ ์ฒ˜๋ฆฌ๋กœ ๋‹ค์ค‘ ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ๋„ ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ์ •์ƒ ์ž‘๋™

๐Ÿ‘ฅ ์œ ์ € ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๋Šฅ ๊ฐœ์„ 

  • ๊ธฐ๋Šฅ:

    • ์œ ์ € ๋‹‰๋„ค์ž„ or ์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰
  • ๋ฌธ์ œ ์ •์˜:

    • ํšŒ์›์ด ๋งŽ์ด ์งˆ ์ˆ˜๋ก ๋‹‰๋„ค์ž„์ด๋‚˜ ์ด๋ฆ„ ๊ฒ€์ƒ‰ ์‹œ ๋”œ๋ ˆ์ด๊ฐ€ ๋ฐœ์ƒ
  • ํ•ด๊ฒฐ:

    1. redis๋ฅผ ์ด์šฉํ•œ ์บ์‹ฑ
    2. ์ธ๋ฑ์‹ฑ์„ ์ด์šฉํ•œ ๋น ๋ฅธ ๊ฒ€์ƒ‰
    3. redis ์บ์‹ฑ + ์ธ๋ฑ์‹ฑ ๋™์‹œ ์ ์šฉ
  • ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ

    • ํ…Œ์ŠคํŠธ ๋„๊ตฌ: JMeter
    • ์‹œ๋‚˜๋ฆฌ์˜ค
      • ๊ฐ€์ƒ์˜ ์‚ฌ์šฉ์ž๋ฅผ 10,000๋ช…์œผ๋กœ ์„ค์ •
      • 10,000๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ 600์ดˆ ๋™์•ˆ ๊ท ๋“ฑํ•˜๊ฒŒ ์ ‘์†
      • ๋™์‹œ์— ์ ‘์† ์‹œ ์„œ๋ฒ„์˜ ์„ฑ๋Šฅ ๋ฐ ์‘๋‹ต ์‹œ๊ฐ„ ์ธก์ •
      • ํ…Œ์ŠคํŠธ ๋ฒ„์ „:
        • v1 : ๊ธฐ์กด API
        • v2 : ์ด๋ฆ„/๋‹‰๋„ค์ž„ ์ธ๋ฑ์‹ฑ ์ ์šฉ
        • 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)

๐Ÿ“‰ ์บ์‹œ ์ ์šฉ ์ „

๐Ÿš€ ๋„์ž… ํ›„ (Elasticsearch ๊ธฐ๋ฐ˜)

๐Ÿ“ˆ ์„ฑ๋Šฅ ๊ฐœ์„  ์š”์•ฝ ๋น„๊ต

๊ตฌ๋ถ„ 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% ๊ฐœ์„ 

๐Ÿ”ง ์„ค์น˜ ๋ฐ ์‹คํ–‰ ๋ฐฉ๋ฒ•

ํด๋ฆญํ•˜์—ฌ ์„ค์น˜ ๋ฐ ์‹คํ–‰ ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•˜์„ธ์š”
  1. ๋ ˆํฌ์ง€ํ† ๋ฆฌ ํด๋ก 
  • ํ„ฐ๋ฏธ๋„ ๋˜๋Š” ๋ช…๋ น ํ”„๋กฌํ”„ํŠธ์—์„œ ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋ฅผ ํด๋ก ํ•ฉ๋‹ˆ๋‹ค.
git clone https://github.com/hojunyun-dev/Eat_Together.git
  1. 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 ๋ธŒ๋žœ์น˜๋Š” ํ•ญ์ƒ ์•ˆ์ •์ ์ธ ๋ฒ„์ „์„ ์œ ์ง€ํ•˜๋ฉฐ, ๊ฐœ๋ฐœ์€ ํ”ผ์ฒ˜ ๋ธŒ๋žœ์น˜์—์„œ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.

๐Ÿค ๋ฐฅ์‚ฌ์กฐ Project Members

ํŒ€์žฅ
์œคํ˜ธ์ค€
๋ผ์ด๋”
์•Œ๋ฆผ
CI/CD
๋ถ€ํŒ€์žฅ
๋ฐ•์†Œํฌ
์ฑ„ํŒ…
ํŒ€์›
์ž„์„œ์—ฐ
์ฃผ๋ฌธ
๊ฒฐ์ œ
ํŒ€์›
์ดํƒœ๊ฒธ
์žฅ๋ฐ”๊ตฌ๋‹ˆ
ํŒ€์›
๊น€ํ˜„์ฐฌ
๋งค์žฅ
๋ฉ”๋‰ด
ํŒ€์›
๊น€๋„ํ™˜
์œ ์ €
๋ชจ๋‹ˆํ„ฐ๋ง

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors