728x90
반응형

DynamicUpdate를 찾던 중 원하는 컬럼만 변경하는데 효율적이라고 생각했는데,
DynamicUpdate을 쓰지 않고 항상 모든 컬럼을 변경하는게 효율적이라는 시각을 보고 알아보게 됐다!

 

@DynamicUpdate

@DynamicUpdate는 JPA에서 특정 엔티티 클래스에 사용 가능한 어노테이션이다.
이 어노테이션을 붙이면 업데이트할 때, 실제로 변경된 컬럼만을 업데이트하는 쿼리를 만들어주는데, 즉, 전체 컬럼을 모두 업데이트하는 대신, 변경이 일어난 필드만 업데이트하도록 최적화해주는 역할을 한다.

기본적으로 JPA는 엔티티가 변경될 때, 해당 엔티티의 모든 필드 값을 포함한 UPDATE 쿼리를 생성한다.
예를 들어, User 엔티티에서 name 필드만 변경하더라도, 기본 동작은 UPDATE user SET name = ?, email = ?, age = ? WHERE id = ?와 같이 모든 필드를 업데이트한다.

이와 반대로, @DynamicUpdate를 사용하면 실제 변경된 필드만 업데이트 쿼리에 포함된다.
User 엔티티에서 name 필드만 변경되었을 때, UPDATE user SET name = ? WHERE id = ?와 같이 필요한 필드만 쿼리에 포함된다.

 

@DynamicUpdate의 장점

1. 성능 최적화: 불필요하게 전체 필드를 업데이트하지 않고, 변경된 필드만 업데이트하기 때문에, 데이터베이스로 전송되는 데이터 양이 줄어든다.

2. 업데이트 시 충돌 방지: 여러 클라이언트가 동시에 특정 엔티티의 다른 필드를 업데이트할 때, 불필요한 충돌이 줄어든다.
예를 들어, 한 클라이언트가 name을 수정하고 다른 클라이언트가 email을 수정할 경우, @DynamicUpdate 덕분에 서로의 필드에 영향을 미치지 않는다.

 

@DynamicUpdate의 단점

@DynamicUpdate를 사용하면 쿼리 캐시 효율이 떨어질 수 있다고한다.
JPA는 결국 내부적으로 JDBC와 PreparedStatement를 사용하는데, 이때 PreparedStatement는 SQL 쿼리 구문을 캐시해서 동일한 쿼리 구문을 재사용하는 방식으로 성능을 최적화한다.

1. PreparedStatement 캐싱 저하: @DynamicUpdate를 쓰지 않으면 쿼리 구조가 일정해서, PreparedStatement가 이를 캐시에 저장해두고 반복 사용 가능하다. 예를 들어, 매번 UPDATE user SET name = ?, email = ? WHERE id = ?와 같은 구조를 재사용한다.

2. 쿼리 다양화로 인한 캐시 히트율 저하: 반면 @DynamicUpdate는 변경된 필드만 포함한 쿼리를 만든다. 그래서 한 번은 name만, 또 다른 번에는 email만, 또는 nameage만 포함된 쿼리를 각각 새로 생성할 수 있다. 이 경우 매번 쿼리가 달라져 캐시 히트율이 떨어진다.

즉, @DynamicUpdate가 쿼리를 매번 다르게 만들기 때문에, 동일한 쿼리 구문이 캐시되기 어렵고, 이는 성능 저하로 이어질 수 있다.

 

결론

@DynamicUpdate필요한 경우에만 신중히 사용하는 것이 좋다고한다.
전체 업데이트가 불필요하거나, 동시성 문제로 인해 특정 필드만 업데이트하는 게 유리할 때는 효율적이고,
반대로, 쿼리 캐싱이 중요한 경우에는 캐시 히트율이 낮아져 성능 저하가 발생할 수 있기때문에 자제하는게 좋을 것 같다.

따라서, 데이터베이스 쿼리 성능과 캐시 효율성을 고려하여 상황에 맞게 사용하는 것이 최적의 성능을 얻는 방법으로 보인다 !

728x90
반응형
728x90
반응형

brew 설치가 안되어있다는 가정으로 brew부터 설치한다

1.homebrew 설치

homebrew 사이트에서 설치할 수 있는 명령어 카피

https://brew.sh/

 

Homebrew

The Missing Package Manager for macOS (or Linux).

brew.sh

터미널에 명령어 입력

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

설치 후에 brew 입력 시 동작하지 않는다면 환경변수 처리

echo 'export PATH="/opt/homebrew/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

 

2. php 설치

brew를 이용하여 원하는 버전의 php 설치

brew install php@8.1

설치 후에 php 입력 시 동작하지 않는다면 환경변수 처리

echo 'export PATH="/opt/homebrew/opt/php@8.1/bin:$PATH"' >> ~/.zshrc
echo 'export PATH="/opt/homebrew/opt/php@8.1/sbin:$PATH"' >> ~/.zshrc
brew services restart php@8.1

 

3. composer 설치

공식문서에서 안내하는 방식대로 컴포저 설치

https://getcomposer.org/download/

 

Composer

Download Composer Latest: v2.7.8 To quickly install Composer in the current directory, run the following script in your terminal. To automate the installation, use the guide on installing Composer programmatically. php -r "copy('https://getcomposer.org/ins

getcomposer.org

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === 'dac665fdc30fdd8ec78b38b9800061b4150413ff2e3b6f88543c636f7cd84f6db9189d43a81e5503cda447da73c7e5b6') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"

 

php composer.phar 하면 컴포저가 되지만 composer 만 치면 여전히 안됨

아래 명령어를 통해 컴포저를 옮겨줘야함. 경로가 없다고 실패한다면 /usr/local/bin 경로 만들고 하면 됨

sudo mv composer.phar /usr/local/bin/composer

 

그러면 성공 !

 

4. larabel 설치

composer global require "laravel/installer"
vi ~/.bash_profile

.bash_profile을 열어 아래 한줄 추가

export PATH="$PATH:$HOME/.composer/vendor/bin"

추가 후 아래 명령어 입력

source ~/.bash_profile
composer global require laravel/valet
valet install

valet install 에서 이런 에러가 뜬다면

 

아래처럼 하면됨

valet use php@8.1

그럼 진짜 완료

728x90
반응형
728x90
반응형

요즘 docker를 이용한 서비스가 많아졌다. 컨테이너 기술을 활용하여 애플리케이션을 일관되게 실행할 수 있어서 많이 사용하고 있다. 나도 서비스 새로 만들때 웬만하면 도커를 쓰는 편인데, 이미지 생성해서 ECR에 올리고 EC2 서버에 배포하는 식으로 사용했었다.

그런데 ECS라고 컨테이너 기반 애플리케이션을 쉽게 배포할 수 있는 서비스를 알게 되었다. 그래서 이런 컨테이너 기반 애플리케이션을 EC2을 통해 배포하는 것과 ECS을 통해 배포하는 것의 차이를 알아보고자 한다.

여기서 다루는 ECS는 서버리스 기반의 Fargate를 가정으로 한다. (서버리스가 아니라면 어차피 ECS랑 EC2랑 연동하는거라..)

 

일단 ECR, EC2, ECS는 다음과 같다.

  • ECR(Elastic Container Registry) : 이미지를 저장하는 레지스트리 서비스. 나는 도커 이미지를 만들어 클라우드에 저장하는 저장소의 개념으로 사용하고 있다.
  • EC2(Elastic Compute Cloud) : 가상 서버 인스턴스를 제공하는 서비스. 그냥 원격 컴퓨터라고 생각하면 된다.
  • ECS(Elastic Container Service) : 이미지를 가져와서 컨테이너를 실행하고 관리할 수 있는 서비스.

 

ECS로 ECR을 배포했을 때의 장점

설명으로만 보아서는 EC2보다는 ECS가 좀 더 ECR과 연동이 잘 될 것 같다.
실제로 ECR은 AWS 기반 컨테이너 오케스트레이션을 제공해서 ECR와는 찰떡같은 궁합이다.

  • ECR의 이미지를 가져와 쉽게 배포, 관리, 확장할 수 있다.
  • 운영 부담이 적다.
  • 효율적으로 컨테이너를 관리할 수 있다.

 

 

단편적으로 보면 EC2로 배포하는 건 서버도 직접 관리해야하고 도커도 따로 설치해야하고 컨테이너 관리랑 업데이트도 직접 해야하고 설정도 번거로운데 왜 사용할까?

EC2로 ECR을 배포했을 때의 장점

우선, 개발중의 여러 선택사항과 마찬가지로 운영 환경에 따라 달라진다.
EC2로 개발할 때에는 초기 설정 및 관리가 까다롭지만, 더 높은 유연성 제어를 제공한다. 

 

ECR에 비해 EC2는 다음과 같은 지원을 제공한다.

  • 인스턴스 옵션 : EC2의 경우 다양한 인스턴스 유형과 크기 조정을 제공하는 등 워크로드에 맞는 인스턴스를 선택할 수 있다. 반면에 ECR은 vCPU와 메모리의 조합만 선택할 수 있다.
  • 네트워킹 옵션 : EC2의 경우 ENI, VPC 피어링, Direct Connect등 고급 네트워크 기능을 지원한다, 반면 ECR의 경우 Fargate에서는 ENI를 직접 제어하는데 제한이 있다. Fargate의 경우 고정 IP를 직접 할당할 수 없다.
  • 스토리지 옵션 : Fargate는 EBS 볼륨을 직접 사용할 수 없고 임시 스토리지만 제공되어 세부적인 스토리지 옵션이 제한적이다.

 

 

이와 같이 ECS로 배포했을 때, EC2로 배포했을 때의 차이가 있다.
이렇게 제품의 요구사항에 따라 ECS와 EC2 중에 선택할 수도 있겠지만, 우리에겐 비용도 꽤 중요하다...
비용 관점에서 비교해보자.

 

ECS와 EC2의 비용 비교

비용은 서버와 서버리스의 비교와 비슷하다.

ECS Fargate의 경우 서버리스이기 때문에 유휴 자원에 대한 비용 부담이 없다. 
당연하게도 트래픽이 많아질 경우 사용한 만큼 비용이 발생하기 때문에 비용이 급격히 상승할 수 있다.

EC2의 경우 선택한 인스턴스의 타입에 따라 시간 당 비용이 발생한다. 트래픽이 적을 경우 비용 효율성이 떨어진다.
예약 인스턴스, 스팟 인스턴스 등 비용 절감 옵션을 제공한다. 한 인스턴스에 여러 개의 컨테이너를 올릴 수 있다.

만약 ECS + EC2의 조합이라면 그냥 같은 인스턴스 유형으로 비교할 때 EC2만 쓰는거에 비해 조금 더 비용이 나온다고 한다. 

따라서, 대규모 트래픽의 경우에 EC2를 사용하는 것이 더 경제적이고, 트래픽이 적으면 사용한 만큼 비용이 발생하는 ECS를 사용하는 것이 좋아 보인다.

 

성능 비교

마지막으로는 성능의 관점인데, 다음 글을 참고하면 좋을 것 같다. EC2이 ECS보다 성능이 조금 더 뛰어나다고 한다.
https://filia-aleks.medium.com/ec2-versus-fargate-performance-comparison-34b1002fbbaa

 

EC2 versus Fargate: performance comparison

Nowadays, Fargate is very popular. It solves such ECS with EC2 problems as:

filia-aleks.medium.com

 

마무리

결과적으로, 어떤 형태의 서비스를 개발하고 트래픽이 많느냐 적느냐, 개발을 편하게 할 것이냐 귀찮으나 유연성을 택할것인 가 에서의 선택인 거 같다.
아직은 EC2가 오랜시간 서비스를 제공해왔기에 ECS에 비해 다양한 옵션을 제공하여 이 부분에서는 EC2가 나은 것 같지만, 나중 가서는 ECS도 다양한 옵션을 제공하지 않을까 싶기도 하다.

나라면 비용을 우선적으로 생각할 것 같긴하다. 비즈니스를 만드는 회사에서 AWS 비용을 절감하면 인력 몇명의 비용을 아끼는것과 같은 효과를 가져오니까 회사도 좋아한다.

나중에 나도 새로 서비스를 구축할 때 다시한번 고민해볼 것 같고, 마지막으로 인프랩에서 했던 EC2, ECS와 관련한 글을 구경하며 좋을 것 같다!


https://tech.inflab.com/20240227-finops-for-startup/

 

스타트업 엔지니어의 AWS 비용 최적화 경험기

인프랩이 어떻게 월 $25,000 넘게 AWS 비용을 절약할 수 있었는지 경험과 노하우를 소개합니다.

tech.inflab.com

 

728x90
반응형
728x90
반응형

평소 좋아하는 '널널한 개발자'님의 영상을 보다가
헤더에 관한 얘기를 듣다보니 네트워크 속도가 1mb/s일 때 데이터 1mb보내는건 1초보다 더걸리겠다 싶어서
맞는지 궁금해서 좀 더 찾아보았다!

(이 글에서 물론 여기서 latency나 대역폭 개념은 무시한다. 수많은 원인이 있기 때문..!)

https://www.youtube.com/watch?v=K9L9YZhEjC0

 

기본적으로 데이터를 PC끼리 주고받을 때 OSI 7계층을 오간다.

각 계층을 이동할 때마다 '이전 계층에서 받은 값' + 해당 계층 프로토콜의 헤더가 붙는다.

만약 1mb의 데이터를 보낼 때 한번에 1mb를 보내는게 아니고 패킷이라는 단위로 나누어 하나씩 보내는데, 
패킷의 크기는 MTC(최대 패킷 크기)에 의존한다. TCP/IP 스택에 따라서 달라지기도 한다!

 

1mb를 보낼 때 필요한 패킷 개수 계산

일반적으로 MTC는 1500byte인데, 여기에 IP 헤더, TCP 헤더가 포함되므로
실제 데이터 크기는 1500byte보다 작은 1460byte 정도 된다.

  • IP 헤더: 일반적으로 20 바이트
  • TCP 헤더: 일반적으로 20 바이트

=> 1mb 데이터를 전송하기 위해 약 719개의 패킷이 필요하게 된다.

그리고 이더넷 네트워크의 경우 아래 만큼의 용량이 더 필요해서 이더넷 프레임의 크기는 1518byte가 된다.

1460 bytes (데이터)+20 bytes (TCP 헤더)+20 bytes (IP 헤더)+14 bytes (이더넷 헤더)+4 bytes (FCS)=1518 bytes

  • 이더넷 헤더: 14 바이트
  • 이더넷 트레일러 (FCS): 4 바이트

그래서 이 가정에서는 1mb를 보낼 때 1518byte짜리의 이더넷 프레임을 719개 주고받아야한다.

 

 

1mb/s 네트워크에서 1mb의 데이터를 보낼 때 걸리는 시간 계산

네트워크 속도와 데이터 크기의 차이는 결국에 이 헤더들이 붙기 때문에 발생하게 되는데 GPT가 간단하게 계산해준걸로 참고하자면 

계산에 따르면 네트워크 속도가 1mb/s 일 때 전체 전송 데이터 양을 전송한데 필요한 시간은 약 1.1초 정도 걸린다.

그래서 평소에 네트워크를 사용해서 파일을 다운받을 때 네트워크 속도가 데이터 용량을 못따라가는데? 라고 생각했던건 우연이 아니었던 것이다! (물론 이 가정에서 핸드셰이크 등 연결을 위한 지연 시간 등은 무시됐긴 함)

 

결론

실제로 데이터를 주고받을 때에는 헤더들이 붙어서 더 큰 크기의 데이터를 주고받는 것이고
대략적으로 네트워크 속도보다 더 오랜 시간이 소요된다...

 

그럼 여기서 궁금해지는건 UDP는 헤더가 더 작은데 더 빨리주고받나? 싶은데 그건 다음에 알아보도록 하자아

 

728x90
반응형
728x90
반응형

쉘 스크립트의 return은 다른 언어와 다르게 작동한다는 것을 듣고 gpt의 도움을 받아 실습해보고자 했다! 

우선, 쉘 스크립트의 함수에서 return은 값을 반환하지 않고 상태코드 (0~255)을 반환한다.

함수가 성공적으로 실행되었는지 나타나는데 쓰인다. 일반적으로 0은 성공, 0이 아닌 값은 실패를 의미한다.

고로 쉘 스크립트에서 return은 숫자를 반환해서 그냥 문자열을 넣으면 에러가 발생한다. 

 

 

1. return 에 숫자를 넣을 경우 

my_function() {
  echo "Hello, World!"
  return 0  # 성공을 의미
}

my_function
echo $?  # 0이 출력됨

2. return 에 문자열을 넣을 경우 

my_function() {
  return "Hello, World!"
}

my_function

결과

./script.sh: line 2: return: Hello,: numeric argument required

 

그러나 쉘 스크립트로 무중단 배포를 구현할 때, nginx를 사용하여 새로운 버전의 애플리케이션을 배포하는 동안 문자열을 반환하고 싶을 수 있는 몇 가지 상황이 있을 수 있다.

예를 들어, 배포 과정에서 상태를 확인하고 싶다거나, 환경 설정 값을 반환한다거나 로그 메시지를 반환하는 등 !

이런 경우를 위해 문자열을 반환하는 방법이 몇가지 있다.

 

1. 명령 치환 (가장 흔하게 사용되는 방법)

my_function() {
  local result="Hello, World!"
  echo "$result"
}

result=$(my_function)
echo "$result"  # "Hello, World!" 출력됨

 

2. 백틱 사용(구식)

my_function() {
  local result="Hello, World!"
  echo "$result"
}

result=`my_function`
echo "$result"  # "Hello, World!" 출력됨

 

3. 전역 변수 사용

result=""

my_function() {
  result="Hello, World!"
}

my_function
echo "$result"  # "Hello, World!" 출력됨

코드가 간단할 때에는 가장 편할거같지만 함수가 좀만 복잡해지면,,, 안전하지 않을 것 같다.

728x90
반응형
728x90
반응형

Tools

  • Prometheus: v2.45.3
  • Grafana: v10.3.0
  • Node Exporter: v1.7.0

 

Prometheus, Node Exporter, Grafana 설치 작업

1. 패키지 업데이트

sudo apt update​
 

2. 디렉터리 생성

sudo mkdir /etc/prometheus sudo mkdir /opt/prometheus
 

3. Prometheus 설치

공식 홈페이지에서 최신 버전 확인하여 다운로드

https://prometheus.io/download/

 

Download | Prometheus

An open-source monitoring system with a dimensional data model, flexible query language, efficient time series database and modern alerting approach.

prometheus.io

 

wget https://github.com/prometheus/prometheus/releases/download/v2.45.3/prometheus-2.45.3.linux-amd64.tar.gz
 
압축 해제
tar xvfz prometheus*
 
패키지 구성 파일 복사
cd prometheus-2.45.3.linux-amd64
sudo cp prometheus /usr/local/bin/
sudo cp promtool /usr/local/bin/

sudo cp -r prometheus.yml /etc/prometheus
sudo cp -r consoles /etc/prometheus
sudo cp -r console_libraries /etc/prometheus

 

4. Prometheus 서비스 데몬 작성

sudo vi /etc/systemd/system/prometheus.service

# prometheus.service 파일 내에 아래 내용 작성
[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target

[Service]
User=root
Group=root
Type=simple
ExecStart=/usr/local/bin/prometheus \
    --config.file /etc/prometheus/prometheus.yml \
    --storage.tsdb.path /opt/prometheus/ \
    --web.console.templates=/etc/prometheus/consoles \
    --web.console.libraries=/etc/prometheus/console_libraries

[Install]
WantedBy=multi-user.target

5. Prometheus config 설정

 
sudo vi /etc/prometheus/prometheus.yml

# prometheus.yml 파일 내에 아래 내용 작성
# my global config
global:
  scrape_interval: 15s # 기본 수집 주기
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: node
    scrape_interval: 5s

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
      - targets: ["localhost:9100"] # node-exporter 주소
 

6. Prometheus 서비스 데몬 등록 및 실행

sudo systemctl daemon-reload
sudo systemctl start prometheus
sudo systemctl enable prometheus
sudo systemctl status prometheus

 

7. Node Exporter 설치

공식 홈페이지에서 최신 버전 확인하여 다운로드

https://github.com/prometheus/node_exporter/releases/

 

Releases · prometheus/node_exporter

Exporter for machine metrics. Contribute to prometheus/node_exporter development by creating an account on GitHub.

github.com

wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz
tar -xzvf node_exporter-1.7.0.linux-amd64.tar.gz

# 폴더명 변경 로그아웃
sudo cp node_exporter-1.7.0.linux-amd64/node_exporter /usr/local/bin/
 

8. Node Exporter 서비스 데몬 작성

sudo vi /etc/systemd/system/node_exporter.service

# node_exporter.service 파일 내에 아래 내용 작성
[Unit]
Description=Prometheus Node Exporter
Documentation=https://prometheus.io/docs/guides/node-exporter/
Wants=network-online.target
After=network-online.target

[Service]
User=root
Group=root
Restart=on-failure
ExecStart=/usr/local/bin/node_exporter

[Install]
WantedBy=multi-user.target
 

9. Node Exporter 서비스 데몬 등록 및 실행

sudo systemctl daemon-reload
sudo systemctl start node_exporter
sudo systemctl enable node_exporter

sudo systemctl status node_exporter
 

10. Grafana 설치

공식 홈페이지에서 최신 버전 확인하여 다운로드

Download Grafana | Grafana Labs

sudo apt-get install -y adduser libfontconfig1 musl
wget https://dl.grafana.com/oss/release/grafana_10.3.0_amd64.deb
sudo dpkg -i grafana_10.3.0_amd64.deb
 

11. Grafana 포트 변경

기본 포트 3000. 포트 룰에 맞추어 9000으로 변경

sudo vi /etc/grafana/grafana.ini

# 포트 변경 내용 추가 
http_port = 9000
 
 

12. Grafana config 설정

sudo systemctl daemon-reload
sudo systemctl start grafana-server
sudo systemctl enable grafana-server

sudo systemctl status grafana-server​

13. 모든 서비스 재시작

sudo systemctl restart prometheus
sudo systemctl restart node_exporter
sudo systemctl restart grafana-server

14. Grafana 접속

브라우저에서 {서버IP}:9000으로 접속

 

Grafana 대시보드 셋팅 작업

1. 데이터 소스 연결

  1. Grafana Home > Menu > Connections > Data sources > + Add new data source
  2. Prometheus 선택
  3. Connection에 Prometheus URL 입력 (예: http://localhost:9090)
  4. Save & Test 하여 성공 확인

2. 대시보드 템플릿 선택

  1. https://grafana.com/grafana/dashboards/ 접속
  2. 목적에 따라 원하는 대시보드 템플릿 선택하여 JSON 다운로드
  3. 서버 모니터링 목적일 경우, Node Exporter Full 템플릿 선택

3. 대시보드 셋팅

  1. Grafana Home > Menu > Dashboards > New dashboard
  2. 2. 대시보드 템플릿 선택에서 다운받은 대시보드 템플릿 사용을 위해 Import a dashboard 선택
  3. Upload dashboard JSON file에 다운받은 JSON 파일 드롭
  4. 데이터 소스 연결에서 추가한 Prometheus 데이터 소스 선택
  5. Import

4. 연결된 대시보드 확인

 

AWS 보안 그룹 설정 작업

1. 실행 포트 적용

  • 포트 현황
    • Prometheus: 9090
    • Grafana: 9000
    • Node Exporter: 9100

2. 서버 모니터링

  • 보안 그룹 생성
  • 9000, 9090, 9100 포트 인바운드 규칙 적용

참고 레퍼런스

728x90
반응형

+ 최근 게시글