<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Jason | Solution Engineer - 마테크, 애드테크</title>
    <link>https://neep305.tistory.com/</link>
    <description>Adobe Analytics | Adobe Target | Adobe Experience Platform | Google Analytics | Google Tag Manager | Amplitude</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 22:36:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Jason Nam</managingEditor>
    <image>
      <title>Jason | Solution Engineer - 마테크, 애드테크</title>
      <url>https://tistory1.daumcdn.net/tistory/1631104/attach/826de60ec3c34acbb3d7a96ce9901768</url>
      <link>https://neep305.tistory.com</link>
    </image>
    <item>
      <title>AWS Solution Architect 준비: EBS - IOPS</title>
      <link>https://neep305.tistory.com/96</link>
      <description>&lt;h1&gt;AWS EBS IOPS&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 스토리지 성능의 핵심 지표, IOPS를 제대로 이해하고 활용하는 방법&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서 애플리케이션을 운영하다 보면 &quot;IOPS&quot;라는 용어를 자주 접하게 됩니다. 특히 데이터베이스나 ETL 작업처럼 디스크 I/O가 중요한 워크로드에서는 IOPS 설정이 성능과 비용을 좌우하는 핵심 요소가 됩니다. 이 글에서는 EBS IOPS의 개념부터 실전 최적화 전략까지 상세히 알아보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  IOPS란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**IOPS(Input/Output Operations Per Second)**는 스토리지 볼륨이 초당 처리할 수 있는 읽기/쓰기 작업의 수를 의미합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;측정 기준&lt;/b&gt;: 16KiB I/O 작업 단위로 측정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;워크로드 유형&lt;/b&gt;: 데이터베이스, 가상 데스크톱, 부트 볼륨 등 transactional workload에 중요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 지표&lt;/b&gt;: SSD 기반 볼륨의 주요 성능 측정 기준&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해, IOPS는 스토리지의 &quot;처리 속도&quot;를 나타내는 지표입니다. 높은 IOPS는 더 많은 데이터 요청을 동시에 처리할 수 있음을 의미합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ EBS 볼륨 타입별 IOPS 성능 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS EBS는 워크로드 특성에 따라 다양한 볼륨 타입을 제공합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSD 기반 볼륨 (Transactional Workload)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. &lt;b&gt;gp3 (General Purpose SSD)&lt;/b&gt; - 권장 옵션&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;최대 IOPS: 80,000
기본 제공: 3,000 IOPS (추가 비용 없음)
용량: 1 GB ~ 64 TB
가격: $0.08/GB-month + 추가 IOPS 비용
특징: 가장 비용 효율적, 대부분의 워크로드에 적합
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비용 구조:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 3,000 IOPS 무료&lt;/li&gt;
&lt;li&gt;3,000 IOPS 초과 시: $0.005/provisioned IOPS-month&lt;/li&gt;
&lt;li&gt;125 MB/s 무료, 초과 시: $0.04/provisioned MB/s-month&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. &lt;b&gt;gp2 (General Purpose SSD)&lt;/b&gt; - 레거시&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;최대 IOPS: 16,000
성능 확장: 3 IOPS/GiB (용량에 비례)
용량: 1 GB ~ 16 TB
특징: Burst 크레딧 시스템, gp3로 마이그레이션 권장
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;성능 계산:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;33.33 GiB 이하: 최소 100 IOPS&lt;/li&gt;
&lt;li&gt;33.33 GiB 초과: 용량(GiB) &amp;times; 3 IOPS&lt;/li&gt;
&lt;li&gt;5,334 GiB에서 최대 16,000 IOPS 도달&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. &lt;b&gt;io2 Block Express&lt;/b&gt; - 최고 성능&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;최대 IOPS: 256,000
IOPS/GiB 비율: 1,000:1
용량: 최대 64 TB
처리량: 4,000 MB/s
레이턴시: 평균 500 마이크로초 미만
가격: $0.125/GB-month + $0.065/provisioned IOPS-month
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최적 사용 케이스:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SAP HANA, Oracle, Microsoft SQL Server&lt;/li&gt;
&lt;li&gt;Mission-critical 데이터베이스&lt;/li&gt;
&lt;li&gt;99.999% 내구성 요구 워크로드&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. &lt;b&gt;io1&lt;/b&gt; - 고성능&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;최대 IOPS: 64,000
IOPS/GiB 비율: 50:1
용량: 4 GB ~ 16 TB
특징: io2로 업그레이드 권장 (동일 가격에 더 나은 성능)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HDD 기반 볼륨 (Throughput 중심)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;st1 (Throughput Optimized)&lt;/b&gt;: MapReduce, 로그 처리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sc1 (Cold HDD)&lt;/b&gt;: 아카이브, 저빈도 액세스&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚡ 실제 IOPS 성능 결정 요소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로비저닝한 IOPS를 모두 활용하려면 세 가지 요소를 고려해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 볼륨 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;볼륨 타입과 크기, 프로비저닝한 IOPS 값&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. EC2 인스턴스 제한&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중요&lt;/b&gt;: 인스턴스의 EBS 성능은 인스턴스 타입의 성능 제한 또는 연결된 볼륨의 총합 성능 중 더 작은 값으로 제한됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시: r6i.16xlarge에서 80,000 IOPS 달성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;옵션 1: gp2 볼륨 5개 &amp;times; 16,000 IOPS = 80,000 IOPS
옵션 2: gp3 볼륨 1개를 80,000 IOPS로 프로비저닝
옵션 3: io2 Block Express 1개
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 네트워크 대역폭&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EBS-Optimized 인스턴스 사용 필수&lt;/li&gt;
&lt;li&gt;인스턴스별 &quot;Dedicated EBS Bandwidth&quot; 확인&lt;/li&gt;
&lt;li&gt;예: m7i.large는 10 Gbps 전용 EBS 대역폭 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  IOPS 모니터링 및 최적화 전략&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CloudWatch 핵심 메트릭&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;필수 모니터링 메트릭:
  VolumeReadOps/VolumeWriteOps:
    설명: 실제 읽기/쓰기 작업 수
    활용: 초로 나누어 실제 IOPS 계산
    
  VolumeThroughputPercentage:
    설명: IOPS 사용률 (%)
    알람 기준: 80% 초과 시 알림
    
  VolumeQueueLength:
    설명: 대기 중인 I/O 작업 수
    알람 기준: 10 초과 시 병목 가능성
    
  BurstBalance:
    설명: gp2 볼륨의 burst 크레딧 잔량
    알람 기준: 20% 미만 시 성능 저하 경고
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CloudWatch 알람 설정 예시&lt;/h3&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;# IOPS 사용률 높음 알람
aws cloudwatch put-metric-alarm \
  --alarm-name high-iops-usage \
  --metric-name VolumeThroughputPercentage \
  --namespace AWS/EBS \
  --statistic Average \
  --period 300 \
  --threshold 80 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 2

# gp2 Burst Balance 낮음 알람
aws cloudwatch put-metric-alarm \
  --alarm-name low-burst-balance \
  --metric-name BurstBalance \
  --namespace AWS/EBS \
  --statistic Average \
  --period 300 \
  --threshold 20 \
  --comparison-operator LessThanThreshold \
  --evaluation-periods 1
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  비용 최적화 실전 가이드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. gp2 &amp;rarr; gp3 마이그레이션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;절감 효과: 약 20%&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# 마이그레이션 전 (gp2)
volume_size = 1000  # GB
cost_gp2 = 1000 * 0.10  # $100/month
auto_iops = 1000 * 3    # 3,000 IOPS

# 마이그레이션 후 (gp3)
cost_gp3_storage = 1000 * 0.08  # $80/month
cost_gp3_iops = 0                # 3,000 IOPS는 무료
total_cost_gp3 = 80              # $80/month

# 절감: $20/month (20%)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 과다 프로비저닝 방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 팀이 &quot;혹시 몰라서&quot; 10,000~16,000 IOPS를 프로비저닝하지만, 실제 사용량은 2,000 IOPS 미만인 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최적화 절차:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. CloudWatch에서 지난 2주간 메트릭 확인
2. 95th percentile IOPS 확인
3. 프로비저닝 값이 95th percentile보다 훨씬 높으면 감축
4. 30% 여유분만 유지 (예: 95th percentile이 2,000이면 2,600 IOPS)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 인스턴스 타입 매칭&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;안티패턴:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;❌ io2 Block Express (64,000 IOPS) + t3.medium 인스턴스
&amp;rarr; t3.medium은 최대 2,880 IOPS만 지원
&amp;rarr; 비싼 볼륨 비용 낭비
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;권장:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;✅ 워크로드 분석 &amp;rarr; 필요 IOPS 산정 &amp;rarr; 적절한 인스턴스 선택
✅ EBS-Optimized 인스턴스 사용
✅ 인스턴스 스펙 페이지에서 &quot;Maximum IOPS&quot; 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 실전 사례: ETL 워크로드 IOPS 설계&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시나리오&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCP BigQuery &amp;rarr; AWS Redshift 데이터 마이그레이션&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;임시 프로젝트 (6개월)&lt;/li&gt;
&lt;li&gt;이벤트 기반 처리&lt;/li&gt;
&lt;li&gt;레코드별 처리 (배치 아님)&lt;/li&gt;
&lt;li&gt;VPN 터널링 환경&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아키텍처별 IOPS 설정&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Lambda 함수 (데이터 추출 &amp;rarr; S3)&lt;/h4&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;lambda_storage_config = {
    &quot;volume_type&quot;: &quot;gp3&quot;,
    &quot;size_gb&quot;: 100,
    &quot;iops&quot;: 3000,        # 기본값, 추가 비용 없음
    &quot;throughput_mbps&quot;: 125,  # 기본값
    &quot;rationale&quot;: &quot;짧은 실행 시간, 가벼운 I/O, 비용 효율성&quot;
}

# 예상 비용
monthly_cost = 100 * 0.08  # $8/month
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Glue ETL 작업 (데이터 변환)&lt;/h4&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;glue_storage_config = {
    &quot;volume_type&quot;: &quot;gp3&quot;,
    &quot;size_gb&quot;: 500,
    &quot;iops&quot;: 5000,        # 3,000 초과 시 비용 발생
    &quot;throughput_mbps&quot;: 250,
    &quot;rationale&quot;: &quot;중간 크기 변환 작업, 확장 가능성&quot;
}

# 예상 비용
storage_cost = 500 * 0.08           # $40/month
iops_cost = (5000 - 3000) * 0.005   # $10/month
throughput_cost = (250 - 125) * 0.04 # $5/month
total_cost = 55  # $55/month
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Redshift (대상 데이터베이스)&lt;/h4&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Redshift는 자체 managed storage 사용
별도 EBS 볼륨 불필요
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모니터링 전략&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Phase 1 - 초기 배포:
  - 보수적 IOPS 설정 (3,000~5,000)
  - CloudWatch 상세 모니터링 활성화
  - 1주일간 실제 사용 패턴 수집

Phase 2 - 최적화:
  - 95th percentile 기준 IOPS 조정
  - VolumeQueueLength &amp;gt; 5이면 IOPS 증가
  - VolumeThroughputPercentage &amp;lt; 50%이면 IOPS 감소

Phase 3 - 안정화:
  - 주간 리뷰로 지속 최적화
  - 프로젝트 종료 시 리소스 즉시 삭제
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  하이브리드 클라우드 환경 고려사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VPN 터널링과 IOPS&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCP-AWS 간 VPN 연결 시:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EBS IOPS는 로컬 성능만 보장&lt;/li&gt;
&lt;li&gt;네트워크 레이턴시 추가 발생&lt;/li&gt;
&lt;li&gt;io2 Block Express의 500 마이크로초 레이턴시 + VPN 레이턴시&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;권장 사항:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 엔드투엔드 성능 테스트 필수
2. 네트워크 병목 vs 스토리지 병목 구분
3. CloudWatch Logs로 전체 파이프라인 레이턴시 추적
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;보안과 성능&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;암호화 영향:
  EBS 암호화: IOPS 성능 영향 거의 없음
  AWS KMS: 투명하게 적용
  전송 중 암호화: VPN/TLS 오버헤드 고려

권장 설정:
  - EBS 기본 암호화 활성화
  - KMS Customer Managed Key 사용
  - 암호화로 인한 IOPS 손실 걱정 불필요
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  성능 벤치마킹 가이드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FIO를 이용한 IOPS 테스트&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 랜덤 읽기 IOPS 테스트
sudo fio --filename=/dev/xvdf \
  --direct=1 \
  --rw=randread \
  --bs=16k \
  --ioengine=libaio \
  --iodepth=256 \
  --runtime=120 \
  --numjobs=4 \
  --time_based \
  --group_reporting \
  --name=iops-test-job

# 랜덤 쓰기 IOPS 테스트
sudo fio --filename=/dev/xvdf \
  --direct=1 \
  --rw=randwrite \
  --bs=16k \
  --ioengine=libaio \
  --iodepth=256 \
  --runtime=120 \
  --numjobs=4 \
  --time_based \
  --group_reporting \
  --name=iops-test-job
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과 해석&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;예상 결과:
  gp3 (3,000 IOPS): ~2,800-3,000 IOPS
  gp3 (10,000 IOPS): ~9,500-10,000 IOPS
  io2 (50,000 IOPS): ~48,000-50,000 IOPS

성능 미달 시 체크리스트:
  ☐ EBS-Optimized 인스턴스인가?
  ☐ 인스턴스 최대 IOPS 제한은?
  ☐ 다른 볼륨이 대역폭을 소비하는가?
  ☐ gp2의 Burst Balance가 고갈되었는가?
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  워크로드별 IOPS 권장사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터베이스&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;OLTP (트랜잭션 처리):
  추천: io2 Block Express
  IOPS: 10,000 ~ 64,000
  이유: 낮은 레이턴시, 일관된 성능

OLAP (분석):
  추천: gp3
  IOPS: 5,000 ~ 16,000
  이유: 순차 읽기 중심, 비용 효율적

개발/테스트:
  추천: gp3
  IOPS: 3,000 (기본값)
  이유: 프로덕션 대비 저비용
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;빅데이터 &amp;amp; ETL&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Spark/Hadoop:
  추천: st1 (Throughput Optimized HDD)
  이유: 대용량 순차 I/O, MB/s 중심

Lambda 기반 ETL:
  추천: gp3
  IOPS: 3,000 ~ 5,000
  이유: 짧은 실행, 비용 효율

Glue/EMR:
  추천: gp3
  IOPS: 5,000 ~ 10,000
  이유: 중간 크기 변환, 확장 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;웹/애플리케이션 서버&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;프로덕션:
  추천: gp3
  IOPS: 3,000 ~ 5,000
  이유: 적절한 성능, 비용 효율

고트래픽:
  추천: gp3
  IOPS: 10,000+
  이유: 동시 요청 처리

부트 볼륨:
  추천: gp3
  IOPS: 3,000 (기본값)
  크기: 30-50 GB
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  흔한 실수와 해결책&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실수 1: Burst Balance 무시 (gp2)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;gp2 100GB 볼륨
&amp;rarr; 기본 300 IOPS (100GB &amp;times; 3)
&amp;rarr; Burst로 3,000 IOPS 가능
&amp;rarr; Burst 크레딧 고갈 시 300 IOPS로 급락
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;✅ gp3로 마이그레이션
✅ 또는 볼륨 크기를 1,000GB로 증가 (3,000 IOPS 보장)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실수 2: 인스턴스 타입 미스매치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;io2 Block Express 64,000 IOPS + t3.large
&amp;rarr; t3.large는 최대 2,780 IOPS
&amp;rarr; 61,220 IOPS 낭비
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;✅ 인스턴스 타입 업그레이드 (예: m6i.4xlarge)
✅ 또는 볼륨 IOPS를 인스턴스 한계에 맞춤
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실수 3: 과다 프로비저닝&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;&quot;혹시 몰라&quot; 16,000 IOPS 프로비저닝
&amp;rarr; 실제 사용: 평균 800 IOPS
&amp;rarr; 불필요한 비용 발생
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;✅ 2주간 모니터링 후 적정 크기 산정
✅ 95th percentile + 30% 여유분
✅ Auto Scaling 고려 (Elastic Volumes)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Elastic Volumes로 동적 조정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS Elastic Volumes 기능으로 중단 없이 IOPS 조정 가능:&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 볼륨 IOPS 증가 (gp3)
aws ec2 modify-volume \
  --volume-id vol-12345678 \
  --iops 10000

# 볼륨 타입 변경 (gp2 &amp;rarr; gp3)
aws ec2 modify-volume \
  --volume-id vol-12345678 \
  --volume-type gp3 \
  --iops 5000

# 변경 상태 확인
aws ec2 describe-volumes-modifications \
  --volume-id vol-12345678
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의사항:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;6시간에 한 번만 수정 가능&lt;/li&gt;
&lt;li&gt;일부 변경은 파일시스템 확장 필요&lt;/li&gt;
&lt;li&gt;프로덕션 환경은 점진적 변경 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  핵심 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;IOPS 선택 체크리스트&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;☐ 워크로드 유형 파악 (OLTP vs OLAP vs ETL)
☐ 필요 IOPS 산정 (CloudWatch 기반)
☐ 볼륨 타입 선택 (gp3 vs io2)
☐ 인스턴스 타입 호환성 확인
☐ 비용 대비 성능 평가
☐ 모니터링 및 알람 설정
☐ 정기적 리뷰 및 최적화
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;볼륨 타입 선택 플로우차트&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;시작
 ├─ 임시/개발 환경? &amp;rarr; gp3 (3,000 IOPS)
 ├─ 범용 웹/앱? &amp;rarr; gp3 (3,000-10,000 IOPS)
 ├─ 데이터베이스?
 │   ├─ OLTP? &amp;rarr; io2 Block Express (10,000-64,000 IOPS)
 │   └─ OLAP? &amp;rarr; gp3 (5,000-16,000 IOPS)
 ├─ 빅데이터 순차 I/O? &amp;rarr; st1 (HDD)
 └─ Mission-critical? &amp;rarr; io2 Block Express (최고 성능)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS IOPS는 단순한 숫자가 아니라, 애플리케이션 성능과 비용을 직접 좌우하는 핵심 요소입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;성공적인 IOPS 관리의 3대 원칙:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;측정 우선&lt;/b&gt;: 추측이 아닌 데이터 기반 결정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;점진적 최적화&lt;/b&gt;: 작게 시작해서 필요에 따라 확장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지속적 모니터링&lt;/b&gt;: 워크로드 변화에 맞춰 조정&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 하이브리드 클라우드 환경이나 ETL 워크로드에서는 초기 보수적 설정으로 시작해, 실제 사용 패턴을 수집한 후 최적화하는 것이 가장 안전하고 비용 효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS는 Elastic Volumes 기능을 통해 중단 없는 IOPS 조정을 지원하므로, 과도한 오버 프로비저닝보다는 적정 수준에서 시작해 필요시 확장하는 전략을 추천합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ebs/latest/userguide/ebs-volume-types.html&quot;&gt;AWS EBS Volume Types 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ebs/latest/userguide/ebs-io-characteristics.html&quot;&gt;Amazon EBS I/O Characteristics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ebs/latest/userguide/provisioned-iops.html&quot;&gt;EBS Provisioned IOPS SSD Volumes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html&quot;&gt;EBS-Optimized Instances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/ebs/volume-types/&quot;&gt;gp2 to gp3 Migration Calculator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글이 도움이 되셨다면 공유해주세요! 궁금한 점은 댓글로 남겨주시면 답변드리겠습니다.  &lt;/p&gt;</description>
      <category>Data &amp;amp; MarTech/AWS</category>
      <author>Jason Nam</author>
      <guid isPermaLink="true">https://neep305.tistory.com/96</guid>
      <comments>https://neep305.tistory.com/96#entry96comment</comments>
      <pubDate>Sat, 13 Dec 2025 15:53:39 +0900</pubDate>
    </item>
    <item>
      <title>[Firebase] iOS Swizzling 기능 이해하기</title>
      <link>https://neep305.tistory.com/95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;firebase-swizzling.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CWKzF/btsMmoOyRH1/etrWodun0Bk6owlKxJZOQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CWKzF/btsMmoOyRH1/etrWodun0Bk6owlKxJZOQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CWKzF/btsMmoOyRH1/etrWodun0Bk6owlKxJZOQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCWKzF%2FbtsMmoOyRH1%2FetrWodun0Bk6owlKxJZOQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;300&quot; data-filename=&quot;firebase-swizzling.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;iOS&amp;nbsp;Swizzling&amp;nbsp;기능&amp;nbsp;이해하기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;Firebase&amp;nbsp;Swizzling이란?&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 개발에서 말하는 &lt;span&gt;&lt;b&gt;Method Swizzling&lt;/b&gt;&lt;/span&gt;은 &lt;b&gt;&amp;ldquo;메서드 동적 교체&amp;rdquo;&lt;/b&gt;라고 번역할 수 있는데, 이는 특정 메서드의 동작을 런타임에 바꿔치기하는 기술을 의미한다. iOS&amp;nbsp;앱&amp;nbsp;개발에서&amp;nbsp;Firebase를&amp;nbsp;활용하면&amp;nbsp;푸시&amp;nbsp;알림,&amp;nbsp;애널리틱스,&amp;nbsp;딥링크&amp;nbsp;등&amp;nbsp;다양한&amp;nbsp;기능을&amp;nbsp;쉽게&amp;nbsp;구현할&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;이러한&amp;nbsp;편리함의&amp;nbsp;핵심&amp;nbsp;기술&amp;nbsp;중&amp;nbsp;하나가&amp;nbsp;Method&amp;nbsp;Swizzling이다.&amp;nbsp;이번&amp;nbsp;포스트에서는&amp;nbsp;Firebase&amp;nbsp;Swizzling이&amp;nbsp;무엇인지,&amp;nbsp;왜&amp;nbsp;활성화되어&amp;nbsp;있는지,&amp;nbsp;그리고&amp;nbsp;이를&amp;nbsp;비활성화하는&amp;nbsp;방법에&amp;nbsp;대해&amp;nbsp;알아보겠다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Firebase에서&amp;nbsp;Swizzling을&amp;nbsp;사용하는&amp;nbsp;이유&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Swizzling은 iOS의 Objective-C 런타임 기능을 활용하여 특정 메서드를 런타임에 동적으로 교체하는 기술이다. Firebase는 이 기법을 사용하여 앱의 코드 수정 없이도 자동으로 특정 기능을 활성화할 수 있도록 돕는다. 따라서 개발자가 직접 여러 가지 설정을 하지 않아도 Firebase가 자동으로 필요한 작업을 수행하도록 하며 대표적인 예는 다음과 같다:&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;1.&amp;nbsp;푸시&amp;nbsp;알림(APNs)&amp;nbsp;자동&amp;nbsp;등록&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Firebase&amp;nbsp;Cloud&amp;nbsp;Messaging(FCM)은&amp;nbsp;앱이&amp;nbsp;실행될&amp;nbsp;때&amp;nbsp;자동으로&amp;nbsp;APNs&amp;nbsp;토큰을&amp;nbsp;등록하고&amp;nbsp;Firebase&amp;nbsp;서버로&amp;nbsp;전송한다.&amp;nbsp;Swizzling이&amp;nbsp;활성화되어&amp;nbsp;있으면&amp;nbsp;개발자가&amp;nbsp;didRegisterForRemoteNotificationsWithDeviceToken을&amp;nbsp;직접&amp;nbsp;구현하지&amp;nbsp;않아도&amp;nbsp;된다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;2.&amp;nbsp;Firebase&amp;nbsp;Analytics&amp;nbsp;자동&amp;nbsp;이벤트&amp;nbsp;수집&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Firebase&amp;nbsp;Analytics는&amp;nbsp;Swizzling을&amp;nbsp;이용하여&amp;nbsp;화면&amp;nbsp;이동이나&amp;nbsp;특정&amp;nbsp;이벤트를&amp;nbsp;자동&amp;nbsp;감지하고&amp;nbsp;데이터를&amp;nbsp;수집한다.&amp;nbsp;이를&amp;nbsp;통해&amp;nbsp;개발자가&amp;nbsp;별도로&amp;nbsp;코드&amp;nbsp;추가&amp;nbsp;없이도&amp;nbsp;사용자&amp;nbsp;행동을&amp;nbsp;분석할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;3.&amp;nbsp;Firebase&amp;nbsp;Dynamic&amp;nbsp;Links&amp;nbsp;자동&amp;nbsp;처리&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Firebase&amp;nbsp;Dynamic&amp;nbsp;Links는&amp;nbsp;Swizzling을&amp;nbsp;통해&amp;nbsp;앱이&amp;nbsp;특정&amp;nbsp;URL을&amp;nbsp;통해&amp;nbsp;실행될&amp;nbsp;경우&amp;nbsp;자동으로&amp;nbsp;관련&amp;nbsp;정보를&amp;nbsp;가져와&amp;nbsp;처리한다.&amp;nbsp;이를&amp;nbsp;통해&amp;nbsp;딥링크&amp;nbsp;관련&amp;nbsp;추가&amp;nbsp;코드를&amp;nbsp;최소화할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Firebase&amp;nbsp;Swizzling&amp;nbsp;비활성화&amp;nbsp;방법&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Swizzling이&amp;nbsp;편리하지만,&amp;nbsp;특정한&amp;nbsp;이유로&amp;nbsp;직접&amp;nbsp;제어하고&amp;nbsp;싶다면&amp;nbsp;비활성화할&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;Swizzling을&amp;nbsp;끄는&amp;nbsp;방법은&amp;nbsp;Firebase&amp;nbsp;서비스별로&amp;nbsp;다르다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;1.&amp;nbsp;FCM&amp;nbsp;Swizzling&amp;nbsp;비활성화&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;푸시&amp;nbsp;알림에서&amp;nbsp;Swizzling을&amp;nbsp;끄려면&amp;nbsp;Info.plist에&amp;nbsp;아래&amp;nbsp;설정을&amp;nbsp;추가하면&amp;nbsp;된다.&lt;/p&gt;
&lt;pre id=&quot;code_1739884742538&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;key&amp;gt;FirebaseAppDelegateProxyEnabled&amp;lt;/key&amp;gt;
&amp;lt;false/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Firebase가 자동으로 푸시 알림을 설정하지 않으며, 직접 UNUserNotificationCenterDelegate와 UIApplicationDelegate의 didRegisterForRemoteNotificationsWithDeviceToken을 구현해야 한다. 이번에 테스트한 케이스에서 FirebaseApp.configure() 함수를 선언하고 이후에 registerForRemoteNotifications를 호출하면 swizzling이 발생하는 것을 확인하였다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;2.&amp;nbsp;Firebase&amp;nbsp;Analytics&amp;nbsp;Swizzling&amp;nbsp;비활성화&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;애널리틱스&amp;nbsp;이벤트&amp;nbsp;자동&amp;nbsp;추적을&amp;nbsp;비활성화하려면&amp;nbsp;&lt;b&gt;Info.plist&lt;/b&gt;에&amp;nbsp;다음&amp;nbsp;키를&amp;nbsp;추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739885000465&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;key&amp;gt;FIREBASE_ANALYTICS_COLLECTION_ENABLED&amp;lt;/key&amp;gt;
&amp;lt;false/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게&amp;nbsp;하면&amp;nbsp;Firebase가&amp;nbsp;자동으로&amp;nbsp;애널리틱스&amp;nbsp;데이터를&amp;nbsp;수집하지&amp;nbsp;않으며,&amp;nbsp;개발자가&amp;nbsp;수동으로&amp;nbsp;Analytics.logEvent를&amp;nbsp;호출해야&amp;nbsp;한다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;3.&amp;nbsp;Firebase&amp;nbsp;Dynamic&amp;nbsp;Links&amp;nbsp;Swizzling&amp;nbsp;비활성화&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Dynamic&amp;nbsp;Links&amp;nbsp;Swizzling을&amp;nbsp;비활성화하려면&amp;nbsp;Info.plist에&amp;nbsp;아래&amp;nbsp;설정을&amp;nbsp;추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1739884949840&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;key&amp;gt;FirebaseDeepLinkAutomaticRetrievalEnabled&amp;lt;/key&amp;gt;
&amp;lt;false/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게&amp;nbsp;하면&amp;nbsp;Firebase가&amp;nbsp;자동으로&amp;nbsp;딥링크를&amp;nbsp;처리하지&amp;nbsp;않으며,&amp;nbsp;개발자가&amp;nbsp;직접&amp;nbsp;handleOpenURL&amp;nbsp;또는&amp;nbsp;continueUserActivity를&amp;nbsp;구현해야&amp;nbsp;한다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Firebase&amp;nbsp;Swizzling은&amp;nbsp;개발자의&amp;nbsp;편의를&amp;nbsp;위해&amp;nbsp;자동화된&amp;nbsp;기능을&amp;nbsp;제공하지만,&amp;nbsp;프로젝트에&amp;nbsp;따라&amp;nbsp;불필요하거나&amp;nbsp;원치&amp;nbsp;않는&amp;nbsp;동작을&amp;nbsp;할&amp;nbsp;수도&amp;nbsp;있다.&amp;nbsp;앱의&amp;nbsp;동작을&amp;nbsp;더욱&amp;nbsp;세밀하게&amp;nbsp;제어하고&amp;nbsp;싶다면&amp;nbsp;Swizzling을&amp;nbsp;비활성화하고&amp;nbsp;필요한&amp;nbsp;기능을&amp;nbsp;직접&amp;nbsp;구현하는&amp;nbsp;것이&amp;nbsp;좋다.&amp;nbsp;Swizzling을&amp;nbsp;끄는&amp;nbsp;경우,&amp;nbsp;반드시&amp;nbsp;관련된&amp;nbsp;기능을&amp;nbsp;수동으로&amp;nbsp;설정해야&amp;nbsp;한다는&amp;nbsp;점을&amp;nbsp;잊지&amp;nbsp;말자.&lt;/p&gt;</description>
      <category>Solution Engineering</category>
      <category>FCM</category>
      <category>Firebase</category>
      <category>IOS</category>
      <category>swizzling</category>
      <category>앱개발</category>
      <category>파이어베이스</category>
      <author>Jason Nam</author>
      <guid isPermaLink="true">https://neep305.tistory.com/95</guid>
      <comments>https://neep305.tistory.com/95#entry95comment</comments>
      <pubDate>Tue, 18 Feb 2025 22:31:22 +0900</pubDate>
    </item>
    <item>
      <title>Web to app 어트리뷰션 트래킹하기</title>
      <link>https://neep305.tistory.com/94</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfPaYV/btsKOlTgixJ/y1tE9iVkTnej9cxGkk94PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfPaYV/btsKOlTgixJ/y1tE9iVkTnej9cxGkk94PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfPaYV/btsKOlTgixJ/y1tE9iVkTnej9cxGkk94PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfPaYV%2FbtsKOlTgixJ%2Fy1tE9iVkTnej9cxGkk94PK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;Web to app 어트리뷰션 트래킹하기&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;디지털 마케터들이 꼭 알아야 할 웹투앱 어트리뷰션에 대해 상세히 알아보겠습니다. 특히 Singular의 솔루션을 중심으로, 실제 구현 방법과 활용 전략까지 자세히 다뤄보겠습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. 웹투앱 어트리뷰션이란?&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;웹투앱 어트리뷰션은 사용자가 모바일 웹사이트에서 네이티브 앱으로 전환되는 여정을 추적하는 기술입니다. 예를 들어, 페이스북 광고를 통해 웹사이트에 방문한 사용자가 나중에 앱을 설치하는 경우, 이 설치가 어떤 웹 광고로부터 발생했는지 추적할 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;왜 중요할까요?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정확한 마케팅 ROI 측정&lt;/li&gt;
&lt;li&gt;크로스 플랫폼 사용자 경험 이해&lt;/li&gt;
&lt;li&gt;웹과 앱 전환 최적화 가능&lt;/li&gt;
&lt;li&gt;통합된 마케팅 성과 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. Singular의 웹투앱 어트리뷰션 솔루션 살펴보기&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Singular에서는 두 가지 핵심 요소를 통해 웹투앱 어트리뷰션을 구현합니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Singular Links&lt;/b&gt;: 스마트한 추적 링크 시스템&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Singular Web SDK&lt;/b&gt;: 웹사이트 데이터 수집 도구&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 웹투앱 어트리뷰션을 실행하기 위해서는 웹 SDK와 앱 SDK를 모두 적용해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxlckQ/btsKOlF8HCL/r6lvaEPfCjSiWKfvIyyk6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxlckQ/btsKOlF8HCL/r6lvaEPfCjSiWKfvIyyk6k/img.png&quot; data-alt=&quot;웹투앱 플로우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxlckQ/btsKOlF8HCL/r6lvaEPfCjSiWKfvIyyk6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxlckQ%2FbtsKOlF8HCL%2Fr6lvaEPfCjSiWKfvIyyk6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1432&quot; height=&quot;299&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;웹투앱 플로우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현코드 예시&lt;/p&gt;
&lt;pre id=&quot;code_1732067341450&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기본적인 앱 오픈 구현
singularSdk.openApp(&quot;https://mydomain.sng.link/Buour/55cx&quot;);

// 상세 파라미터를 포함한 구현
singularSdk.openApp(&quot;https://mydomain.sng.link/Auour/55ba&quot;,
&quot;deeplink_value&quot;, 
&quot;passthrough_value&quot;, 
&quot;deferred_value&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. UTM 파라미터 활용하기&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Singular는 웹 마케팅에서 흔히 사용하는 UTM 파라미터를 자동으로 매핑합니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UTM 파라미터 &amp;lt;--&amp;gt; Singular 매핑&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;utm_source&lt;/td&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;utm_campaign&lt;/td&gt;
&lt;td&gt;Campaign Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;utm_content&lt;/td&gt;
&lt;td&gt;Creative Name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;utm_term&lt;/td&gt;
&lt;td&gt;Keyword&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;4. 실전 활용 팁&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;구현 전 체크리스트&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Singular Web SDK 최신 버전 설치 (v1.0.8 이상)&lt;/li&gt;
&lt;li&gt;기본 Singular Link 생성&lt;/li&gt;
&lt;li&gt;딥링크 지원 설정 (필요한 경우)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;리포팅 활용하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Mobile Web to App&quot; 트래커로 웹투앱 전환 모니터링&lt;/li&gt;
&lt;li&gt;Link Type 차원을 통한 상세 분석&lt;/li&gt;
&lt;li&gt;UTM 기반 웹 캠페인 성과 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5. 마무리&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;웹투앱 어트리뷰션은 현대 디지털 마케팅에서 필수적인 도구입니다. 특히 옴니채널 전략을 구사하는 기업이라면, 이 기술을 통해 더 정확한 마케팅 성과 측정과 최적화가 가능합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;다음 단계 추천&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Singular 웹 SDK 설치&lt;/li&gt;
&lt;li&gt;테스트 캠페인 실행&lt;/li&gt;
&lt;li&gt;데이터 분석 및 최적화&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;더 자세한 내용이 궁금하시다면 댓글로 남겨주세요! 다음 포스트에서는 웹투앱 어트리뷰션의 고급 활용 전략에 대해 다루도록 하겠습니다.&lt;/p&gt;</description>
      <category>Data &amp;amp; MarTech/MMP</category>
      <category>attribution</category>
      <category>MMP</category>
      <category>web tracking</category>
      <category>오블완</category>
      <category>웹트래킹</category>
      <author>Jason Nam</author>
      <guid isPermaLink="true">https://neep305.tistory.com/94</guid>
      <comments>https://neep305.tistory.com/94#entry94comment</comments>
      <pubDate>Wed, 20 Nov 2024 13:45:52 +0900</pubDate>
    </item>
    <item>
      <title>[Langchain] AWS Bedrock과 Langchain 활용한 LLM 어플리케이션 개발</title>
      <link>https://neep305.tistory.com/93</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;[Langchain]&amp;nbsp;AWS&amp;nbsp;Bedrock과&amp;nbsp;Langchain&amp;nbsp;활용한&amp;nbsp;LLM&amp;nbsp;어플리케이션&amp;nbsp;개발&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;langchain-bedrock-blog.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhk70d/btsKDP8goAF/TxtDJ9QOou5LUBlrzHdTZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhk70d/btsKDP8goAF/TxtDJ9QOou5LUBlrzHdTZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhk70d/btsKDP8goAF/TxtDJ9QOou5LUBlrzHdTZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhk70d%2FbtsKDP8goAF%2FTxtDJ9QOou5LUBlrzHdTZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;300&quot; data-filename=&quot;langchain-bedrock-blog.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 및 Agent를 활용하여 솔루션 엔지니어링에 도입하기에 앞서 AWS Bedrock과 Langchain을 활용하여 LLM(Large Language Model) 어플리케이션을 개발하는 방법에 대해 단계별로 알아보려고 합니다. 우선 개념에 대한 내용을 정리한 후 &amp;nbsp;솔루션 엔지니어링 가이드 및 헬프센터 내용을 RAG를 통해 이해하고 답변을 하는 어플리케이션 개발을 진행할 예정이며, 이번 아티클에서는 LLM 개발을 처음 시작할 때 필요한 기초개념 및 기본 설계에 대한 내용을 다룹니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Langchain에 대한 이해&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Langchain은 LLM 어플리케이션 개발을 위한 프레임워크로 다양한 컴포넌트들을 제공하여 LLM 기반 어플리케이션을 쉽게 구축할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;LLM 컴포넌트&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Models: LLMs, Chat Models, Embeddings&lt;/li&gt;
&lt;li&gt;Prompts: 템플릿, Few-shot Examples&lt;/li&gt;
&lt;li&gt;Memory: 대화버퍼(이전 대화내용 저장), 요약메모리(긴 대화 내용을 요약하여 저장)&lt;/li&gt;
&lt;li&gt;Chains: LLMChain, 순차체인&lt;/li&gt;
&lt;li&gt;Agents: Zero-shot Agent, Structured Agent&lt;/li&gt;
&lt;li&gt;Tools: 검색(웹), 계산기&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AWS Bedrock에 대한 이해&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;AWS Bedrock은 Amazon Web Services에서 제공하는 완전 관리형 서비스로, 다양한 기초 모델(Foundation Models)을 API를 통해 쉽게 사용할 수 있게 해주는 플랫폼입니다. Anthropic의 Claude, OpenAI의 GPT AI21 Labs의 Jurassic, Amazon의 Titan 등 최고 수준의 AI 모델들을 단일 API로 통합하여 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Bedrock의 특징&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다양한 모델 선택 가능&lt;/li&gt;
&lt;li&gt;보안 및 프라이버시&lt;/li&gt;
&lt;li&gt;비용효율성&lt;/li&gt;
&lt;li&gt;AWS 솔루션과의 통합 용이&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Langchain - Bedrock 연동 플로우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Langchain에서 Bedrock을 통해 LLM Application을 구축할 경우, 직접 LLM Models(OpenAI GPT, Anthropic Claude 등)의 API를 호출하지 않고, AWS Bedrock을 통해 간접적으로 모델과 통신하게 됩니다. 즉 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;Langchain Framework ▶&amp;nbsp;AWS SDK ▶ Bedrock Runtime ▶ LLM Model 순서&lt;/b&gt;&lt;/span&gt;로 통신을 합니다. Langchain을 통해 직접 LLM 어플리케이션 개발과 달리 AWS Bedrock을 통해 간접적으로 LLM 모델과 통신하므로 직접 OpenAI, Anthropic 등에서 API 키를 발급받지 않아도 되며, 과금 또한 AWS 사용비용으로 대체됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boS9gB/btsKF762Qr5/z0y7ERblIQElwr88YsmkdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boS9gB/btsKF762Qr5/z0y7ERblIQElwr88YsmkdK/img.png&quot; data-alt=&quot;Lanchain - AWS Bedrock 연동 플로우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boS9gB/btsKF762Qr5/z0y7ERblIQElwr88YsmkdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboS9gB%2FbtsKF762Qr5%2Fz0y7ERblIQElwr88YsmkdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;729&quot; height=&quot;726&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Lanchain - AWS Bedrock 연동 플로우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Bedrock 역할&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 게이트웨이 역할&lt;/li&gt;
&lt;li&gt;인증(Authentication)/인가(Authorization)&lt;/li&gt;
&lt;li&gt;요청 및 응답&lt;/li&gt;
&lt;li&gt;모델 버전 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Langchain 역할&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프롬프트 엔지니어링&lt;/li&gt;
&lt;li&gt;Chain/Agent 로직 처리&lt;/li&gt;
&lt;li&gt;응답 후 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1431&quot; data-origin-height=&quot;259&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buAovz/btsKEdOEI7m/1S7y6w0WNCGNWh3p1X6Y81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buAovz/btsKEdOEI7m/1S7y6w0WNCGNWh3p1X6Y81/img.png&quot; data-alt=&quot;API Key 연동 및 이에 따른 과금이 다름&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buAovz/btsKEdOEI7m/1S7y6w0WNCGNWh3p1X6Y81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuAovz%2FbtsKEdOEI7m%2F1S7y6w0WNCGNWh3p1X6Y81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1431&quot; height=&quot;259&quot; data-origin-width=&quot;1431&quot; data-origin-height=&quot;259&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;API Key 연동 및 이에 따른 과금이 다름&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Note. AWS Bedrock 연동시 장점&lt;/span&gt;&lt;br /&gt;- 보안 강화: AWS IAM을 통한 접근제어&lt;br /&gt;- 관리 용이성: AWS 모니터링 및 로깅 활용 가능&lt;br /&gt;- 확장성: Bedrock을 통한 다양한 파운데이션 모델 사용 가능&lt;br /&gt;- 비용효율성: 사용량에 비례하여 AWS 비용 과금&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;연동 상세&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사전 준비사항&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;LLM 어플리케이션 개발을 위해 아래 환경을 준비합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python 3.8 이상: Poetry를 활용한 설치 권장&lt;/li&gt;
&lt;li&gt;AWS 계정: Bedrock 접근권한 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Bedrock 기본권한 정책 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정책 생성&lt;/p&gt;
&lt;pre id=&quot;code_1731985643861&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aws iam create-policy \
    --policy-name BedrockAccessPolicy \
    --policy-document '{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;BedrockFullAccess&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;bedrock:*&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Sid&quot;: &quot;BedrockModelAccess&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;bedrock:InvokeModel&quot;,
                &quot;bedrock:InvokeModelWithResponseStream&quot;
            ],
            &quot;Resource&quot;: &quot;arn:aws:bedrock:*:281606047546:model/*&quot;
        }
    ]
}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM User에 정책추가&lt;/p&gt;
&lt;pre id=&quot;code_1731987539252&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aws iam attach-user-policy \
    --user-name bedrock-user \
    --policy-arn arn:aws:iam::&amp;lt;User-ID&amp;gt;:policy/BedrockAccessPolicy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LJ4wY/btsKNAbcSr6/zg9q2uF9egUXgq81vznvv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LJ4wY/btsKNAbcSr6/zg9q2uF9egUXgq81vznvv0/img.png&quot; data-alt=&quot;BedrockAccessPolicy 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LJ4wY/btsKNAbcSr6/zg9q2uF9egUXgq81vznvv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLJ4wY%2FbtsKNAbcSr6%2Fzg9q2uF9egUXgq81vznvv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;585&quot; height=&quot;112&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BedrockAccessPolicy 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정책추가 확인&lt;/p&gt;
&lt;pre id=&quot;code_1731987883416&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aws iam list-attached-user-policies --user-name bedrock-user&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Python 환경 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Bedrock을 활용하여 테스트하기 위해 &lt;a href=&quot;https://python.langchain.com/docs/integrations/chat/bedrock/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Langchain 공식문서&lt;/a&gt;의 ChatBedrock을 사용하였습니다. boto3를 활용한 AWS SDK기반 구현도 가능한 옵션입니다. 테스트시 anthropic.claude-3-5-sonnet-20240620-v1:0을 사용하였고, &amp;nbsp;Python 가상환경 및 의존성 설치는 Poetry를 활용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Poetry shell&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732049363545&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;poetry shell&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Poetry 환경에서 모듈 설치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732048839519&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;poetry add langchain_aws&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ChatBedrock:&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732049425472&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from dotenv import load_dotenv
from langchain_aws import ChatBedrock
from langchain_teddynote import logging
import time
from botocore.exceptions import UnknownEndpointError

load_dotenv()

logging.langsmith(&quot;langchain_on_aws&quot;)

def get_bedrock_client():
    return ChatBedrock(
        model=&quot;anthropic.claude-3-5-sonnet-20240620-v1:0&quot;,
        model_kwargs={
            &quot;temperature&quot;: 0.7,
            &quot;max_tokens&quot;: 1024,
            &quot;top_p&quot;: 0.9
        },
        region=&quot;ap-northeast-2&quot;
    )

def get_bedrock_llm():
    try:
        llm = get_bedrock_client()
        return llm
    except Exception as e:
        print(f&quot;Error creating Bedrock client: {e}&quot;)
        return None
    

def main():
    llm = get_bedrock_llm()
    if llm is None:
        print(&quot;LLM 초기화 실패&quot;)
        return
    # invocation
    message = [
        (
            &quot;system&quot;,
            &quot;You are a helpful assistant. Answer the user's question. Translate the answer to Korean.&quot;
        ),
        (&quot;human&quot;, &quot;What is the capital of Korea?&quot;)
    ]

    # 최대 재시도 횟수와 대기 시간 설정
    max_retries = 3
    retry_delay = 2  # 초단위
    
    for attempt in range(max_retries):
        try:
            response = llm.invoke(message)
            print(response.content)
            break
        except UnknownEndpointError as e:
            if attempt &amp;lt; max_retries - 1:
                print(f&quot;요청이 제한되었습니다. {retry_delay}초 후 재시도합니다... ({attempt + 1}/{max_retries})&quot;)
                time.sleep(retry_delay)
                retry_delay *= 2  # 지수 백오프
            else:
                print(&quot;최대 재시도 횟수를 초과했습니다.&quot;)
                raise

if __name__ == &quot;__main__&quot;:
    main()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행결과&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdTmg8/btsKOrMpRGS/rBhyo9GAfQX45xHMj4xQjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdTmg8/btsKOrMpRGS/rBhyo9GAfQX45xHMj4xQjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdTmg8/btsKOrMpRGS/rBhyo9GAfQX45xHMj4xQjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdTmg8%2FbtsKOrMpRGS%2FrBhyo9GAfQX45xHMj4xQjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1116&quot; height=&quot;201&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Data &amp;amp; MarTech</category>
      <category>Ai</category>
      <category>AWS</category>
      <category>aws bedrock</category>
      <category>ChatGPT</category>
      <category>claude</category>
      <category>GPT</category>
      <category>langchain</category>
      <category>LLM</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>Jason Nam</author>
      <guid isPermaLink="true">https://neep305.tistory.com/93</guid>
      <comments>https://neep305.tistory.com/93#entry93comment</comments>
      <pubDate>Wed, 20 Nov 2024 07:13:42 +0900</pubDate>
    </item>
    <item>
      <title>FastAPI 기반 이커머스 아키텍쳐 설계 - 1. 대용량 트래픽 아키텍쳐 개요</title>
      <link>https://neep305.tistory.com/92</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clcOhh/btsKtmyzeVM/uXOLGdTO4Mkk0Im4t1Khsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clcOhh/btsKtmyzeVM/uXOLGdTO4Mkk0Im4t1Khsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clcOhh/btsKtmyzeVM/uXOLGdTO4Mkk0Im4t1Khsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclcOhh%2FbtsKtmyzeVM%2FuXOLGdTO4Mkk0Im4t1Khsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;FastAPI 기반 이커머스 아키텍쳐 설계 - 1. 대용량&amp;nbsp;트래픽&amp;nbsp;아키텍쳐 개요&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이커머스 서비스를 구현하기 위해 대용량 트래픽 처리를 고려한 아키텍쳐 설계 방안에 대해 정리해 보았습니다. 프론트엔드는 Next.js 기반, 백엔드는 FastAPI, 인프라는 AWS를 활용하여 서비스 개발을 진행하도록 아키텍쳐를 구성하였습니다. 대용량 트래픽 처리가 가능한 시스템 구현시 &lt;b&gt;확장성 및 높은 동시성을 확보&lt;/b&gt;하는 것이 중요합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;대용량 트래픽 처리시 &quot;확장성&quot;과 &quot;동시성&quot;의 개념에 대한 이해&lt;/span&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;확장성(Scalability):&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 증가나 데이터 확장에 따라 성능을 유지하며 성장할 수 있는지 여부를 의미합니다. 이는 시스템이 변하는 부하를 효과적으로 처리하고, 사용자가 증가하더라도 안정성과 성능을 유지할 수 있도록 하는 설계와 구조를 갖추는 것입니다. 확장성은 크게 두가지로 구분할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;수평적 확장성(Horizontal Scaling):&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 수를 확장함으로서 성능을 높이는 방식으로 여러 서버를 네트워크로 연결. 개별 서버에 부하가 집중되지 않도록 &lt;b&gt;분산처리&lt;/b&gt;하는 것을 고려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;■&lt;/b&gt;&amp;nbsp;방안: &lt;b&gt;로드밸런서(e.g. NGINX)&lt;/b&gt;를 통해 트래픽을 각 서버로 고르게 분산하고 이를 통해 시스템 전체가 더 많은 요청을 안정적으로 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;■&lt;/b&gt; 특성 및 고려사항&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 개별 서버의 성능에 &lt;u&gt;상대적으로 덜 의존&lt;/u&gt;하게 되며, &lt;b&gt;필요에 따라 새로운 서버를 추가함으로써 트래픽 처리 용량을 늘릴 수 있음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 고려사항: 복잡한 관리, 데이터 일관성, 네트워크 지연, 디버깅 및 모니터링 복잡성, 로드 밸런싱의 한계 고려 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) &lt;b&gt;복잡한 관리: 수평적 확장성을 고려하여 대용량 트래픽 처리가 가능하도록 설계 시 관리상의 복잡도가 증가할 수 있으며, 이를 보완하기 위해 컨테이너화, 오케스트레이션 도구 활용, IaC 등을 고려할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어플리케이션 측면&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. &lt;b&gt;컨테이너화&lt;/b&gt;: 도커와 같은 컨테이너 기술을 활용하여 일관된 어플리케이션 환경을 제공하여, 서버관리의 복잡성을 줄일 수 있음&lt;br /&gt;2. &lt;b&gt;오케스트레이션 도구 활용&lt;/b&gt;: 쿠버네티스(k8s), 도커 스웜(Docker Swarm) 같은 오케스트레이션 도구를 활용하여 서버배포,운영을 자동화하여 관리 부담을 줄일 수 있음&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인프라 측면&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. &lt;b&gt;Infrastructure as Code(IaC)&lt;/b&gt;: Terraform, Ansible 등을 사용해 인프라 코드를 관리하여 반복적인 설정 및 변경작업을 자동화하고 일관성을 유지할 수 있음&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) &lt;b&gt;데이터 일관성 문제&lt;/b&gt;: 분산 시스템에서는 여러 서버에 데이터를 나누어 저장하거나 처리하기 때문에, 데이터 일관성을 유지하는 데 어려움이 있을 수 있습니다. 예를 들어, 데이터베이스 샤딩을 적용했을 때 데이터 간의 동기화를 보장하는 데 추가적인 노력이 필요합니다. 이를 보완하기 위해 Apache Cassandra나 Google Spanner 등의 분산 DB를 사용하여 데이터 일관성 및 분산된 환경에서의 가용성을 보장하는 방법을 고려할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) &lt;b&gt;비용&lt;/b&gt;: 서버를 추가할 때마다 추가적인 하드웨어 비용뿐만 아니라, 네트워크 인프라, 유지 보수, 전력 비용 등도 증가합니다. 클라우드 서비스를 사용하더라도 트래픽 분산과 관련된 비용이 높아질 수 있습니다. 이를 보완하기 위해 자동 스케일링, 서버리스 컴퓨팅 도입을 고려할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;자동 스케일링&lt;/b&gt;: 클라우드 서비스에서 제공하는 자동 스케일링 기능을 활용하여 트래픽 증가시에만 서버 인스턴스를 자동으로 늘리고 부하가 줄어들면 인스턴스를 축소하여 비용을 절감할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버리스 컴퓨팅&lt;/b&gt; 도입: AWS Lambda, Google Cloud Functions 등의 서버리스 기능을 활용하여 트래픽 변화에 따라 유연하게 대응할 수 있으며, 실행된 만큼 비용이 청구되므로 이를 통한 경제적인 운영이 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) &lt;b&gt;네트워크 지연&lt;/b&gt;: 여러 서버가 분산되어 있으면 요청과 응답 과정에서 네트워크 지연(latency)이 발생할 수 있습니다. 특히, 서버 간의 데이터 전송이 필요한 경우 지연 시간이 성능에 영향을 줄 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;CDN&lt;/b&gt; 활용: 정적 자산(static asset) 및 캐시가능한 컨텐츠를 CDN에 배포하여 클라이언트-서버간 지연을 줄일 수 있음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Static asset&lt;/b&gt;: HTML, CSS, Javascript(클라이언트 측 스크립트), 이미지파일, 폰트파일, 동영상 및 오디오 컨텐츠, 아이콘 및 벡터 이미지, 애플리케이션 번들 및 라이브러리, 변경빈도가 적은 API 응답&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지연 최소화 전략&lt;/b&gt;: 각 서버의 지리적 위치를 사용자와 가깝게 두거나, edge 서버를 활용한 시간을 줄이는 방안&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 최적화&lt;/b&gt;: 트래픽 우선순위 설정 및 압축기법을 적용해 네트워크 대역폭(bandwidth)를 효율적으로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) &lt;b&gt;디버깅 및 모니터링 복잡성&lt;/b&gt;: 분산된 여러 서버에서 문제가 발생했을 때, 디버깅과 모니터링이 어렵습니다. 로그를 중앙에서 관리하고, 각 서버의 상태를 추적하는 시스템이 필요하며 이로 인한 운영상의 부담을 증가시킬 수 있습니다. 이를 보완하기 위해 통합로깅, 분산추적시스템, 모니터링 도구 도입을 고려하는 것이 필요합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;통합로깅시스템 도입: ELK, Grafana 등의 툴을 활용하여 모든 서버의 로그를 중앙에 수집하고 이를 시각화하여 관리&lt;/li&gt;
&lt;li&gt;분산추적시스템: Jaeger, Zipkin 등을 통해 분산된 시스템에서의 요청지점을 분석하고 병목지점을 파악&lt;/li&gt;
&lt;li&gt;모니터링 도구: Prometheus와 같은 모니터링 툴을 활용해 각 서버의 상태, 자원 사용률, 응답시간을 실시간으로 추적하고 경고알림을 설정하여 빠르게 대응할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6) &lt;b&gt;로드 밸런싱의 한계&lt;/b&gt;: 로드 밸런서가 모든 서버에 트래픽을 균등하게 분산해도, 각 서버의 상태나 성능이 다를 경우 특정 서버에 과부하가 걸릴 수 있습니다. 로드 밸런싱 자체가 병목점이 되지 않도록 주의해야 합니다. 이를 보완하기 위해 지능형 로드밸런싱, 부하분산전략 다중화를 고려할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지능형 로드밸런싱: 단순한 라운드로빈방식 대신, 각 서버의 상태를 고려해 트래픽을 분산하는 지능형 로드밸런서 도입: AWS ELB, NGINX의 상태기반 로드밸런싱 기능&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;부하분산전략 다중화: 로드밸런서 이중화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;수직적 확장성(Vertical Scaling): &lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;기존 서버의 하드웨어 성능을 업그레이드&lt;/b&gt;&lt;/span&gt;하여 더 많은 요청처리가 가능하도록 하는 방식으로 CPU, RAM, 스토리지 등의 자원을 추가하거나 교체하여 처리성능을 개선하게 되어 이를 통해 더 많은 요청을 처리할 수 있습니다. 이를 위해 CPU 코어를 늘리거나 더 빠른 메모리, 더 큰 스토리지를 추가해 성능을 높입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 시스템을 상대적으로 간단한 구조로 유지할 수 있으며, 복잡한 네트워크 구성 없이 처리 성능을 올릴 수 있습니다.&lt;/li&gt;
&lt;li&gt;단점: 하드웨어 한계에 도달시 추가 확장이 어렵다는 점, 하드웨어 관련 비용증가를 고려해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;동시성(Concurrency):&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;아키텍쳐 레벨의 동시성: &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;수천~수만의 사용자가 &lt;u&gt;&lt;b&gt;동시에 서비스를 요청할때 이를 효율적으로 트래픽을 처리&lt;/b&gt;&lt;/u&gt;할 수 있는 아키텍쳐가 필요합니다. 이 레벨에서는 다수의 서버, 로드밸런싱, 분산처리시스템 등이 동시성을 보장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로드밸런서&lt;/li&gt;
&lt;li&gt;CDN&lt;/li&gt;
&lt;li&gt;분산 데이터베이스&lt;/li&gt;
&lt;li&gt;메시지큐: Kafka, RabbitMQ 등의 시스템을 활용하여 대량의 데이터를 비동기적으로 처리할 수 있습니다. 이는 트래픽 급증 시 서버가 즉시 모든 요청을 처리할 필요없이 작업을 큐에 쌓아두고 차례대로 처리하도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;b&gt;단일 어플리케이션 레벨의 동시성:&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;I/O 대기시간을 최소화하고 단일 서버가 최대한 많은 클라이언트 요청을 처리하도록 하는 것이 목적&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;현재 개발시 활용중인 Fast API를 통해 어플리케이션 레벨에서의 비동기 처리가 가능합니다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;비동기 엔드포인트: 아래 예시는 FastAPI와 asyncio를 사용하여 엔드포인트 호출시 비동기적으로 대기를 진행하면서 다른 클라이언트의 요청을 비동기처리하는 것을 보여줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1730545509158&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import FastAPI
import asyncio

app = FastAPI()

@app.get(&quot;/process-data&quot;)
async def process_data():
    await asyncio.sleep(5)  # 비동기적으로 대기, 다른 요청을 병행 처리 가능
    return {&quot;message&quot;: &quot;Data processed&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 데이터베이스 라이브러리 지원: asyncpg와 같은 비동기 데이터베이스 클라이언트를 사용하여 DB와의 비동기 연동을 지원할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;기본 아키텍쳐 구성&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;861&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bT55cn/btsKuOfVAwy/aFW9isaRqTzxTobPTulaZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bT55cn/btsKuOfVAwy/aFW9isaRqTzxTobPTulaZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bT55cn/btsKuOfVAwy/aFW9isaRqTzxTobPTulaZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbT55cn%2FbtsKuOfVAwy%2FaFW9isaRqTzxTobPTulaZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;892&quot; height=&quot;861&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;861&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Software Engineering</category>
      <category>ecommerce</category>
      <category>FastAPI</category>
      <category>웹개발</category>
      <author>Jason Nam</author>
      <guid isPermaLink="true">https://neep305.tistory.com/92</guid>
      <comments>https://neep305.tistory.com/92#entry92comment</comments>
      <pubDate>Sat, 2 Nov 2024 20:55:14 +0900</pubDate>
    </item>
    <item>
      <title>Python - Shallow Copy와 Deep Copy의 차이점 및 사용법</title>
      <link>https://neep305.tistory.com/90</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;shallowcopy.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oLciJ/btsJg6C9oZH/bFwunJWipDqTU5yXV6J8P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oLciJ/btsJg6C9oZH/bFwunJWipDqTU5yXV6J8P1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oLciJ/btsJg6C9oZH/bFwunJWipDqTU5yXV6J8P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoLciJ%2FbtsJg6C9oZH%2FbFwunJWipDqTU5yXV6J8P1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;300&quot; data-filename=&quot;shallowcopy.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;Shallow Copy와 Deep Copy의 차이점 및 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 개발자로서 코드를 작성하다 보면 객체를 복사해야 하는 상황이 자주 발생합니다. 객체를 복사하는 방법에는 크게 두 가지가 있습니다: Shallow Copy(얕은 복사)와 Deep Copy(깊은 복사). 이 두 가지 복사 방식은 동작 방식과 결과가 다르기 때문에, 언제 어떤 방식을 사용해야 하는지 정확히 이해하는 것이 중요합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Shallow Copy (얕은 복사)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shallow Copy는 객체의 가장 바깥쪽 객체만 복사하고, 그 객체가 참조하는 내부 객체들은 원본 객체와 동일하게 참조하는 방식입니다. 즉, 복사된 객체 내부의 가변 객체(리스트, 딕셔너리 등)는 원본 객체와 동일한 참조를 가리키게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 얕은 복사는 copy 모듈의 copy 함수를 사용하거나, 리스트의 경우에는 슬라이싱을 이용해 생성할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 예시&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;import copy

# 원본 리스트 생성
original_list = [[1, 2, 3], [4, 5, 6]]

# 얕은 복사 생성
shallow_copied_list = copy.copy(original_list)

# 값 변경
shallow_copied_list[0][0] = 99

print(&quot;원본 리스트:&quot;, original_list)
print(&quot;얕은 복사 리스트:&quot;, shallow_copied_list)

&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예상결과값&lt;/h4&gt;
&lt;pre id=&quot;code_1719107104091&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;원본 리스트: [[1, 2, 3], [4, 5, 6]]
깊은 복사 리스트: [[99, 2, 3], [4, 5, 6]]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 shallow_copied_list의 내부 리스트 요소를 변경하면, original_list도 영향을 받습니다. 이는 얕은 복사가 내부 객체를 동일하게 참조하기 때문입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Deep Copy (깊은 복사)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Deep Copy는 객체와 그 객체가 참조하는 모든 객체를 재귀적으로 복사하여 완전히 독립적인 복사본을 만듭니다. 따라서 복사된 객체와 원본 객체는 서로 전혀 영향을 주지 않습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 깊은 복사는 copy 모듈의 deepcopy 함수를 사용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 예시&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;import copy

# 원본 리스트 생성
original_list = [[1, 2, 3], [4, 5, 6]]

# 깊은 복사 생성
deep_copied_list = copy.deepcopy(original_list)

# 값 변경
deep_copied_list[0][0] = 99

print(&quot;원본 리스트:&quot;, original_list)
print(&quot;깊은 복사 리스트:&quot;, deep_copied_list)

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상&amp;nbsp;결과값&lt;/p&gt;
&lt;pre id=&quot;code_1719107212019&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;원본 리스트: [[1, 2, 3], [4, 5, 6]]
깊은 복사 리스트: [[99, 2, 3], [4, 5, 6]]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 deep_copied_list의 내부 리스트 요소를 변경해도 original_list는 영향을 받지 않습니다. 이는 깊은 복사가 모든 객체를 재귀적으로 복사하여 독립적인 객체를 생성하기 때문입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제 사용해야 할까?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Shallow Copy&lt;/b&gt;는 복사된 객체와 원본 객체가 일부 데이터를 공유해도 상관없을 때 사용합니다. 예를 들어, 불변 객체나 단순한 데이터 구조를 사용할 때 적합합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Deep Copy&lt;/b&gt;는 복사된 객체와 원본 객체가 완전히 독립적이어야 할 때 사용합니다. 예를 들어, 중첩된 데이터 구조가 있는 경우나 복사본이 원본 데이터에 영향을 주지 않도록 보장해야 하는 경우에 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 Shallow Copy와 Deep Copy는 각각의 특성과 용도에 맞게 적절히 사용해야 합니다. Shallow Copy는 객체의 가장 바깥쪽만 복사하고 내부 객체는 참조를 공유하는 반면, Deep Copy는 모든 객체를 재귀적으로 복사하여 독립적인 복사본을 만듭니다. 이를 잘 이해하고 상황에 맞게 활용하면 더 안정적이고 예측 가능한 코드를 작성할 수 있습니다. 파이썬의 copy 모듈을 통해 쉽게 Shallow Copy와 Deep Copy를 구현할 수 있으니, 필요에 따라 적절한 방법을 선택하여 사용하시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Understanding the Difference Between Shallow Copy, Deep Copy, and References in Python&lt;/blockquote&gt;
&lt;figure id=&quot;og_1724767941894&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Understanding the Difference Between Shallow Copy, Deep Copy, and References in Python&quot; data-og-description=&quot;Understanding shallow and deep copy in Python is crucial for effective data manipulation and maintaining data integrity. These concepts are essential when working with complex data structures like lists and dictionaries.&quot; data-og-host=&quot;www.linkedin.com&quot; data-og-source-url=&quot;https://www.linkedin.com/pulse/understanding-difference-between-shallow-copy-deep-python-torabi-nnh5e/&quot; data-og-url=&quot;https://www.linkedin.com/pulse/understanding-difference-between-shallow-copy-deep-python-torabi-nnh5e&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vIWLE/hyWSlNLj2G/DGXm3Y69XWQZ3Wbm8DeGg1/img.jpg?width=763&amp;amp;height=519&amp;amp;face=0_0_763_519,https://scrap.kakaocdn.net/dn/bWVHlc/hyWV5WO5ee/OEzHQo5dN6NGYjAEZ5kIU0/img.jpg?width=763&amp;amp;height=519&amp;amp;face=0_0_763_519,https://scrap.kakaocdn.net/dn/bvrLyT/hyWVV0ZSxC/WQqYYLP0Pxx51ukMiiVCa0/img.jpg?width=763&amp;amp;height=519&amp;amp;face=0_0_763_519&quot;&gt;&lt;a href=&quot;https://www.linkedin.com/pulse/understanding-difference-between-shallow-copy-deep-python-torabi-nnh5e/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.linkedin.com/pulse/understanding-difference-between-shallow-copy-deep-python-torabi-nnh5e/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vIWLE/hyWSlNLj2G/DGXm3Y69XWQZ3Wbm8DeGg1/img.jpg?width=763&amp;amp;height=519&amp;amp;face=0_0_763_519,https://scrap.kakaocdn.net/dn/bWVHlc/hyWV5WO5ee/OEzHQo5dN6NGYjAEZ5kIU0/img.jpg?width=763&amp;amp;height=519&amp;amp;face=0_0_763_519,https://scrap.kakaocdn.net/dn/bvrLyT/hyWVV0ZSxC/WQqYYLP0Pxx51ukMiiVCa0/img.jpg?width=763&amp;amp;height=519&amp;amp;face=0_0_763_519');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Understanding the Difference Between Shallow Copy, Deep Copy, and References in Python&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Understanding shallow and deep copy in Python is crucial for effective data manipulation and maintaining data integrity. These concepts are essential when working with complex data structures like lists and dictionaries.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.linkedin.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Software Engineering/Python</category>
      <category>Deep Copy</category>
      <category>Python</category>
      <category>Shallow Copy</category>
      <category>파이썬</category>
      <author>Jason Nam</author>
      <guid isPermaLink="true">https://neep305.tistory.com/90</guid>
      <comments>https://neep305.tistory.com/90#entry90comment</comments>
      <pubDate>Sun, 23 Jun 2024 10:43:33 +0900</pubDate>
    </item>
    <item>
      <title>Airflow 로컬 환경 설치 및 테스트</title>
      <link>https://neep305.tistory.com/89</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;airflow-etl.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crfepi/btsHGXf0ojS/1ZjLk91Kn1PfOUmmgKBlD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crfepi/btsHGXf0ojS/1ZjLk91Kn1PfOUmmgKBlD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crfepi/btsHGXf0ojS/1ZjLk91Kn1PfOUmmgKBlD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcrfepi%2FbtsHGXf0ojS%2F1ZjLk91Kn1PfOUmmgKBlD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;300&quot; data-filename=&quot;airflow-etl.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;Airflow&amp;nbsp;로컬&amp;nbsp;환경&amp;nbsp;설치&amp;nbsp;및&amp;nbsp;테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ETL(Extract, Transform, Load)은 데이터 통합 및 분석의 핵심 프로세스입니다. Apache Airflow는 이러한 ETL 파이프라인을 관리하고 자동화하는 강력한 도구로 자리잡았습니다. 이 글에서는 Airflow를 활용하여 ETL 파이프라인을 설계, 구현, 운영하는 방법을 단계별로 설명하겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Airflow 소개&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apache Airflow는 데이터 파이프라인을 작성, 스케줄링 및 모니터링하기 위한 오픈 소스 플랫폼입니다. Airflow의 주요 특징은 다음과 같습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다양한 연산 지원&lt;/b&gt;: Python을 기반으로 DAG(Directed Acyclic Graph)를 작성하여 다양한 작업을 정의할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스케줄링&lt;/b&gt;: 원하는 시간에 작업을 자동으로 실행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모니터링&lt;/b&gt;: 작업의 상태를 시각적으로 확인하고 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성&lt;/b&gt;: 플러그인을 통해 다양한 데이터 소스와 통합할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Airflow 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Airflow를 설치하려면 Python과 pip이 필요합니다. 기본 설치 과정은 다음과 같습니다:&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;pip install apache-airflow
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Airflow를 설치한 후 초기화 및 웹 서버를 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 데이터베이스 초기화
airflow db init

# 사용자 생성 (선택 사항)
airflow users create \\\\
    --username admin \\\\
    --firstname FIRST_NAME \\\\
    --lastname LAST_NAME \\\\
    --role Admin \\\\
    --email admin@example.com

# 웹 서버 실행
airflow webserver --port 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 터미널에서 스케줄러를 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;airflow scheduler
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. ETL 파이프라인 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ETL 파이프라인을 설계할 때 각 단계는 개별 작업(task)으로 정의됩니다. 예제에서는 간단한 CSV 파일을 추출, 변환, 로드하는 파이프라인을 생성해보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 추출 단계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 추출하는 작업을 정의합니다. 예를 들어, 원격 서버에서 CSV 파일을 다운로드하는 작업입니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from airflow import DAG
from airflow.operators.python_operator import PythonOperator
from datetime import datetime
import requests

def extract_data(**kwargs):
    url = '&amp;lt;https://example.com/data.csv&amp;gt;'
    response = requests.get(url)
    with open('/path/to/save/data.csv', 'wb') as f:
        f.write(response.content)

with DAG(dag_id='etl_pipeline', start_date=datetime(2023, 1, 1), schedule_interval='@daily') as dag:
    extract_task = PythonOperator(
        task_id='extract_data',
        python_callable=extract_data
    
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 변환 단계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추출한 데이터를 변환하는 작업을 정의합니다. 예를 들어, 데이터를 정리하고 필요한 형식으로 변경합니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import pandas as pd

def transform_data(**kwargs):
    df = pd.read_csv('/path/to/save/data.csv')
    df['new_column'] = df['existing_column'].apply(lambda x: x * 2)
    df.to_csv('/path/to/save/transformed_data.csv', index=False)

transform_task = PythonOperator(
    task_id='transform_data',
    python_callable=transform_data
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3 로드 단계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변환한 데이터를 데이터베이스 또는 데이터 웨어하우스로 로드하는 작업을 정의합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import sqlalchemy

def load_data(**kwargs):
    engine = sqlalchemy.create_engine('postgresql://user:password@localhost:5432/mydatabase')
    df = pd.read_csv('/path/to/save/transformed_data.csv')
    df.to_sql('my_table', engine, if_exists='replace', index=False)

load_task = PythonOperator(
    task_id='load_data',
    python_callable=load_data
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.4 작업 연결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 작업을 DAG에 추가하고 의존성을 정의합니다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;extract_task &amp;gt;&amp;gt; transform_task &amp;gt;&amp;gt; load_task
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. ETL 파이프라인 실행 및 모니터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Airflow 웹 UI를 통해 파이프라인의 상태를 모니터링하고 실행 결과를 확인할 수 있습니다. 각 작업의 상태(성공, 실패, 진행 중)를 직관적으로 파악할 수 있으며, 실패한 작업에 대한 로그를 확인하여 문제를 해결할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apache Airflow를 활용하면 ETL 파이프라인을 효율적으로 관리하고 자동화할 수 있습니다. 이 글에서 설명한 예제는 기본적인 ETL 파이프라인을 다루었지만, 실제 환경에서는 다양한 연산과 데이터 소스를 다룰 수 있습니다. Airflow의 강력한 기능을 활용하여 복잡한 데이터 워크플로우를 쉽게 관리해보세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/index.html&quot;&gt;Apache Airflow 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pandas.pydata.org/pandas-docs/stable/&quot;&gt;Pandas 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sqlalchemy.org/en/14/&quot;&gt;SQLAlchemy 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/composer/docs/run-apache-airflow-dag?hl=ko&quot;&gt;Google Cloud Composer 공식문서 가이드&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Data &amp;amp; MarTech</category>
      <author>Jason Nam</author>
      <guid isPermaLink="true">https://neep305.tistory.com/89</guid>
      <comments>https://neep305.tistory.com/89#entry89comment</comments>
      <pubDate>Wed, 29 May 2024 16:43:05 +0900</pubDate>
    </item>
    <item>
      <title>Pandas를 이용해 데이터를 Merge하는 방법과 로깅 활용하기</title>
      <link>https://neep305.tistory.com/88</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;pandas-data-merge.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnhn2L/btsHEXOvfTV/ke5UnmmhzqnDkzNKKNZwU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnhn2L/btsHEXOvfTV/ke5UnmmhzqnDkzNKKNZwU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnhn2L/btsHEXOvfTV/ke5UnmmhzqnDkzNKKNZwU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbnhn2L%2FbtsHEXOvfTV%2Fke5UnmmhzqnDkzNKKNZwU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;300&quot; data-filename=&quot;pandas-data-merge.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;Pandas를 이용해 데이터를 Merge하는 방법과 로깅 활용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pandas는 Python의 강력한 데이터 처리 라이브러리로, 다양한 데이터를 쉽고 빠르게 처리할 수 있도록 도와줍니다. 이번 글에서는 여러 CSV 파일을 하나로 병합하고, 이를 로깅을 통해 기록하는 방법을 소개하겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;준비물&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Pandas 라이브러리&lt;/li&gt;
&lt;li&gt;CSV 파일&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 설명&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;필요한 라이브러리 임포트&lt;/h4&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import pandas as pd
import logging
import os&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pandas는 데이터 처리를 위해, logging은 로깅을 위해, os는 파일 존재 여부 확인을 위해 사용합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CSV 파일 리스트 준비&lt;/h4&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;csv_files = ['./origin/sample_historical_install.csv','./origin/sample_historical_install_2.csv','./origin/sample_historical_install_3.csv']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 CSV 파일을 하나의 리스트에 저장합니다. 이 리스트에 포함된 모든 파일을 병합할 것입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로깅 설정&lt;/h4&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;logging.basicConfig(level=logging.INFO, filename=&quot;py_log.log&quot;, filemode=&quot;w&quot;,
                    format=&quot;%(asctime)s %(levelname)s %(message)s&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅을 설정하여 로그 메시지를 파일에 기록하도록 합니다. level=logging.INFO는 정보 수준 이상의 로그만 기록하겠다는 의미입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터프레임 리스트 생성 및 CSV 파일 읽기&lt;/h4&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;dfs = []

try:
    for i, file in enumerate(csv_files):
        if os.path.exists(file):
            df = pd.read_csv(file)
            dfs.append(df)
            logging.info(f&quot;Successfully appended DataFrame from {file}&quot;)
        else:
            logging.error(f&quot;File not found: {file}&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 csv_files 리스트에 있는 각 파일을 읽어 데이터프레임으로 변환한 후, dfs 리스트에 추가합니다. 각 파일을 성공적으로 읽었는지 로그에 기록합니다. 파일이 존재하지 않을 경우, 에러 로그를 남깁니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터프레임 병합&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;    if dfs:
        merged_df = pd.concat(dfs, axis=0, ignore_index=True)
        output_path = './origin/merged_sample_install.csv'
        merged_df.to_csv(output_path, index=False)

        if os.path.exists(output_path):
            logging.info(&quot;Successfully merged and saved DataFrame&quot;)
    else:
        logging.warning(&quot;No DataFrames to merge&quot;)
except Exception as e:
    logging.error(f&quot;An error occurred: {e}&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 데이터프레임을 하나로 병합한 후, 새로운 CSV 파일로 저장합니다. 병합된 파일이 성공적으로 생성되었는지 확인하고, 최종 로그를 기록합니다. FileNotFoundError 외에 다른 예외 상황도 처리하여 더욱 안정적인 코드를 만들었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Pandas concat 함수 설명&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pd.concat() 함수는 여러 데이터프레임을 하나로 합칠 때 사용합니다. 이 함수는 다양한 옵션을 제공하며, 데이터 병합을 매우 유연하게 처리할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;axis=0: 행을 따라 데이터프레임을 병합합니다. 즉, 데이터프레임들이 위아래로 쌓이게 됩니다. 반대로 axis=1이면 열을 따라 병합되어, 데이터프레임들이 좌우로 병합됩니다.&lt;/li&gt;
&lt;li&gt;ignore_index=True: 병합된 데이터프레임의 인덱스를 재설정합니다. 원본 데이터프레임의 인덱스를 무시하고, 병합된 새로운 데이터프레임에 대해 0부터 시작하는 새로운 인덱스를 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전체 코드&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import pandas as pd
import logging
import os

csv_files = ['./origin/sample_historical_install.csv','./origin/sample_historical_install_2.csv','./origin/sample_historical_install_3.csv']

dfs = []

logging.basicConfig(level=logging.INFO, filename=&quot;py_log.log&quot;, filemode=&quot;w&quot;,
                    format=&quot;%(asctime)s %(levelname)s %(message)s&quot;)

try:
    for i, file in enumerate(csv_files):
        if os.path.exists(file):
            df = pd.read_csv(file)
            dfs.append(df)
            logging.info(f&quot;Successfully appended DataFrame from {file}&quot;)
        else:
            logging.error(f&quot;File not found: {file}&quot;)

    if dfs:
        merged_df = pd.concat(dfs, axis=0, ignore_index=True)
        output_path = './origin/merged_sample_install.csv'
        merged_df.to_csv(output_path, index=False)

        if os.path.exists(output_path):
            logging.info(&quot;Successfully merged and saved DataFrame&quot;)
    else:
        logging.warning(&quot;No DataFrames to merge&quot;)
except Exception as e:
    logging.error(f&quot;An error occurred: {e}&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 Pandas를 사용하여 여러 CSV 파일을 병합하고, 로깅을 통해 프로세스를 기록하는 방법을 배웠습니다. pd.concat() 함수를 활용하 여 여러 데이터셋을 행을 따라 데이터프레임을 병합하고, 인덱스를 재설정할 수 있습니다. 또한 로깅을 통해 코드의 실행 과정을 기록하면 디버깅과 유지 보수가 훨씬 쉬워집니다. 이 코드를 바탕으로 다양한 데이터를 효과적으로 처리해보세요!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Logging in Python: A Developer&amp;rsquo;s Guide&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1716966186720&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Logging in Python: A Developer&amp;rsquo;s Guide&quot; data-og-description=&quot;Have you ever had a tough time debugging your Python code? If yes, learning how to set up logging in Python can help you streamline your&amp;hellip;&quot; data-og-host=&quot;blog.sentry.io&quot; data-og-source-url=&quot;https://blog.sentry.io/logging-in-python-a-developers-guide/&quot; data-og-url=&quot;https://blog.sentry.io/logging-in-python-a-developers-guide/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bp7yPh/hyWdhDKlSG/BiD7GJGG5mJhu3naU0euVk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bguwLF/hyV91P21lH/q1xG76CDPABqcKRbDKkRHK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dhcjaA/hyWdsyvdDI/vhW7K39zOKVr61oAoIs28K/img.png?width=2520&amp;amp;height=945&amp;amp;face=0_0_2520_945&quot;&gt;&lt;a href=&quot;https://blog.sentry.io/logging-in-python-a-developers-guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.sentry.io/logging-in-python-a-developers-guide/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bp7yPh/hyWdhDKlSG/BiD7GJGG5mJhu3naU0euVk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bguwLF/hyV91P21lH/q1xG76CDPABqcKRbDKkRHK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dhcjaA/hyWdsyvdDI/vhW7K39zOKVr61oAoIs28K/img.png?width=2520&amp;amp;height=945&amp;amp;face=0_0_2520_945');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Logging in Python: A Developer&amp;rsquo;s Guide&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Have you ever had a tough time debugging your Python code? If yes, learning how to set up logging in Python can help you streamline your&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.sentry.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Data &amp;amp; MarTech</category>
      <category>data manipulation</category>
      <category>Data Munging</category>
      <category>ETL</category>
      <category>pandas</category>
      <category>Python</category>
      <category>Transformation</category>
      <category>데이터엔지니어링</category>
      <author>Jason Nam</author>
      <guid isPermaLink="true">https://neep305.tistory.com/88</guid>
      <comments>https://neep305.tistory.com/88#entry88comment</comments>
      <pubDate>Wed, 29 May 2024 11:17:55 +0900</pubDate>
    </item>
    <item>
      <title>[iOS 앱 개발의 핵심 이해] SceneDelegate와 AppDelegate의 차이점</title>
      <link>https://neep305.tistory.com/87</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/efNinF/btsHDMsFtnd/IFAZCsH1MkuCXfWuN5GC60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/efNinF/btsHDMsFtnd/IFAZCsH1MkuCXfWuN5GC60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/efNinF/btsHDMsFtnd/IFAZCsH1MkuCXfWuN5GC60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FefNinF%2FbtsHDMsFtnd%2FIFAZCsH1MkuCXfWuN5GC60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SceneDelegate와 AppDelegate의 차이점: iOS 앱 개발의 핵심 이해&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 앱 개발에서는 AppDelegate와 SceneDelegate가 중요한 역할을 합니다. iOS 13부터는 멀티 윈도우 환경을 지원하기 위해 SceneDelegate가 도입되었습니다. 이 두 클래스는 각각 다른 책임을 가지며, 앱의 생명주기 및 상태 관리를 처리합니다. 이 글에서는 AppDelegate와 SceneDelegate의 차이점과 각각의 역할을 예제를 통해 설명하겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AppDelegate&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppDelegate는 앱의 전반적인 생명주기를 관리합니다. 앱이 시작되고 종료되며, 백그라운드나 포그라운드로 전환되는 등의 전반적인 이벤트를 처리합니다. AppDelegate는 주로 다음과 같은 작업을 수행합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;앱 초기화&lt;/b&gt;: 앱이 시작될 때 초기 설정을 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;푸시 알림 설정&lt;/b&gt;: 푸시 알림을 설정하고 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;백그라운드 작업&lt;/b&gt;: 앱이 백그라운드로 전환될 때 필요한 작업을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 AppDelegate의 예제 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&amp;gt; Bool {
        // 앱 초기화 코드
        print(&quot;앱이 시작되었습니다.&quot;)
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {
        // 앱이 비활성화될 때 호출
        print(&quot;앱이 비활성화 상태로 전환됩니다.&quot;)
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        // 앱이 백그라운드로 전환될 때 호출
        print(&quot;앱이 백그라운드로 전환되었습니다.&quot;)
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        // 앱이 다시 포그라운드로 전환될 때 호출
        print(&quot;앱이 포그라운드로 전환됩니다.&quot;)
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        // 앱이 활성화 상태로 전환될 때 호출
        print(&quot;앱이 활성화 상태로 전환되었습니다.&quot;)
    }

    func applicationWillTerminate(_ application: UIApplication) {
        // 앱이 종료될 때 호출
        print(&quot;앱이 종료됩니다.&quot;)
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SceneDelegate&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SceneDelegate는 iOS 13 이후로 도입된 클래스이며, 앱의 각 장면(Scene)에 대한 생명주기를 관리합니다. 멀티 윈도우 환경을 지원하기 위해 도입되었으며, 앱이 여러 UI 창을 가질 수 있는 경우 각 창의 상태를 독립적으로 관리합니다. SceneDelegate는 주로 다음과 같은 작업을 수행합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;UI 상태 관리&lt;/b&gt;: 각 장면의 UI 상태를 관리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장면 생명주기 이벤트 처리&lt;/b&gt;: 장면이 활성화되거나 비활성화될 때 이벤트를 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 SceneDelegate의 예제 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene,
               willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {
        // 장면이 연결될 때 호출
        guard let _ = (scene as? UIWindowScene) else { return }
        print(&quot;장면이 연결되었습니다.&quot;)
    }

    func sceneDidDisconnect(_ scene: UIScene) {
        // 장면이 분리될 때 호출
        print(&quot;장면이 분리되었습니다.&quot;)
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // 장면이 활성화될 때 호출
        print(&quot;장면이 활성화되었습니다.&quot;)
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // 장면이 비활성화될 때 호출
        print(&quot;장면이 비활성화됩니다.&quot;)
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // 장면이 포그라운드로 전환될 때 호출
        print(&quot;장면이 포그라운드로 전환됩니다.&quot;)
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // 장면이 백그라운드로 전환될 때 호출
        print(&quot;장면이 백그라운드로 전환되었습니다.&quot;)
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;guard let _ = (scene as? UIWindowScene) else { return }&lt;/b&gt; 이 코드는 Swift에서 사용되는 guard 구문을 활용한 옵셔널 바인딩입니다. 이 코드의 목적은 주어진 scene 객체를 UIWindowScene 타입으로 안전하게 캐스팅(casting)하고, 만약 캐스팅에 실패하면 함수의 실행을 중단시키는 것입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 설명&lt;/h3&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;guard let _ = (scene as? UIWindowScene) else { return }

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 다음과 같은 의미를 갖습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;guard 구문&lt;/b&gt;: 조건이 만족되지 않으면 코드 블록을 빠져나가기 위해 사용됩니다. 여기서는 조건이 false일 경우 else 블록이 실행됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;옵셔널 바인딩&lt;/b&gt;: scene as? UIWindowScene 표현식은 scene 객체를 UIWindowScene 타입으로 안전하게 캐스팅하려고 시도합니다. 이 캐스팅은 성공할 수도 있고 실패할 수도 있으며, 실패할 경우 nil을 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;let _&lt;/b&gt;: 캐스팅이 성공할 경우, 결과 값을 _에 바인딩합니다. _는 특별한 식별자(identifier)로, 실제로 사용하지 않겠다는 의미입니다. 따라서 바인딩한 값이 필요하지 않은 경우 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;else { return }&lt;/b&gt;: 캐스팅이 실패하면 else 블록이 실행됩니다. 여기서는 return이 호출되어 현재 함수 또는 메서드의 실행을 중단합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 주어진 scene이 UIWindowScene 타입인지 확인하고, 그렇지 않으면 함수를 종료합니다. UIWindowScene은 iOS 13에서 도입된 클래스로, 앱의 UI를 구성하는 창(window)을 나타냅니다. 이 코드는 일반적으로 SceneDelegate의 scene(_:willConnectTo:options:) 메서드 내에서 사용됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 SceneDelegate 내에서 이 코드를 사용하는 예제입니다:&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // scene이 UIWindowScene으로 캐스팅되지 않으면 함수 종료
        guard let windowScene = (scene as? UIWindowScene) else { return }

        // window를 생성하고 UIWindowScene에 연결
        let window = UIWindow(windowScene: windowScene)
        let viewController = UIViewController()
        viewController.view.backgroundColor = .white
        window.rootViewController = viewController
        self.window = window
        window.makeKeyAndVisible()
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;scene 객체가 UIWindowScene으로 캐스팅됩니다.&lt;/li&gt;
&lt;li&gt;캐스팅에 성공하면 windowScene에 바인딩되고, 실패하면 함수가 종료됩니다.&lt;/li&gt;
&lt;li&gt;성공한 경우 window를 생성하고 windowScene에 연결한 후, 루트 뷰 컨트롤러를 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 guard 구문을 사용함으로써 코드의 가독성을 높이고, 불필요한 중첩을 피할 수 있습니다. 또한, 예상치 못한 타입의 객체를 처리할 때 안전하게 대처할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 차이점 정리&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;역할&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AppDelegate: 앱 전체의 생명주기를 관리.&lt;/li&gt;
&lt;li&gt;SceneDelegate: 각 장면의 생명주기를 관리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;도입 시기&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AppDelegate: iOS 초기 버전부터 존재.&lt;/li&gt;
&lt;li&gt;SceneDelegate: iOS 13부터 도입.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 목적&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AppDelegate: 전반적인 앱 상태 관리 (앱 시작, 종료, 백그라운드 진입 등).&lt;/li&gt;
&lt;li&gt;SceneDelegate: 멀티 윈도우 환경에서 각 장면의 상태 관리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 앱 개발에서 AppDelegate와 SceneDelegate의 차이점을 이해하는 것은 중요합니다. AppDelegate는 여전히 앱의 전반적인 생명주기를 관리하지만, SceneDelegate는 멀티 윈도우 지원을 위해 각 장면의 생명주기를 관리합니다. 이를 통해 개발자는 더 유연하고 강력한 앱을 만들 수 있습니다. 이 두 클래스의 역할을 잘 이해하고 활용하면, 보다 안정적이고 사용자 친화적인 앱을 개발할 수 있을 것입니다.&lt;/p&gt;</description>
      <category>Solution Engineering</category>
      <category>IOS</category>
      <category>Swift</category>
      <category>생명주기관리</category>
      <category>앱개발</category>
      <author>Jason Nam</author>
      <guid isPermaLink="true">https://neep305.tistory.com/87</guid>
      <comments>https://neep305.tistory.com/87#entry87comment</comments>
      <pubDate>Tue, 28 May 2024 12:24:06 +0900</pubDate>
    </item>
    <item>
      <title>[GA4] WebhookNotification 연동하기 - GCP 및 Slack 활용</title>
      <link>https://neep305.tistory.com/86</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;GA4 Webhook Notification 연동하기&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ga4-webhook-main.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BLxdj/btsGNFtwwWc/5pXORfkUbZ9MkklV00JQR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BLxdj/btsGNFtwwWc/5pXORfkUbZ9MkklV00JQR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BLxdj/btsGNFtwwWc/5pXORfkUbZ9MkklV00JQR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBLxdj%2FbtsGNFtwwWc%2F5pXORfkUbZ9MkklV00JQR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;300&quot; data-filename=&quot;ga4-webhook-main.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시작하기 Getting Started&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GA4는 시간이 걸리는 작업에 대한 처리완료 시 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;GA4 Data API에 &lt;b&gt;웹훅 알림(Webhook Notification)&lt;/b&gt;을&amp;nbsp;설정하여 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;지정한 웹훅 서버로&lt;span&gt; 데이터 변경에 대한 알림을&lt;/span&gt;&lt;/span&gt; 보내는 기능을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;웹훅이란?&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-size=&quot;size23&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #4e5968; text-align: left;&quot;&gt;웹훅이란&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;데이터가 변경되었을 때 실시간으로 알림을 받을 수 있는 기능&lt;/b&gt;&lt;span style=&quot;color: #4e5968; text-align: left;&quot;&gt;입니다. 웹 서비스의 이벤트 데이터를 전달하는 HTTP 기반 콜백 함수입니다. 특정 이벤트가 발생하면 웹훅이 클라이언트에게 이벤트 데이터를 보내요. 웹훅이라는 단어는 2007년에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;color: #3182f6; text-align: left;&quot; href=&quot;https://progrium.github.io/blog/2007/05/03/web-hooks-to-revolutionize-the-web/&quot;&gt;Jeff Lindsay에 의해 처음 사용되었어요&lt;/a&gt;&lt;span style=&quot;color: #4e5968; text-align: left;&quot;&gt;. HTTP 기반의 웹 특징과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;color: #3182f6; text-align: left;&quot; href=&quot;https://ko.wikipedia.org/wiki/%ED%9B%84%ED%82%B9&quot;&gt;훅(Hook) 기능&lt;/a&gt;&lt;span style=&quot;color: #4e5968; text-align: left;&quot;&gt;을 합친 용어죠.&amp;nbsp;&lt;br /&gt;(출처: &lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://docs.tosspayments.com/resources/glossary/webhook&quot;&gt;토스페이먼츠 개발자센터&lt;/a&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #4e5968; text-align: left;&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rYBqK/btsGN2PyUDY/IUqeKZmK4zP4dzGlGjJubk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rYBqK/btsGN2PyUDY/IUqeKZmK4zP4dzGlGjJubk/img.png&quot; data-alt=&quot;API 폴링과 웹훅의 차이점 (출처: 토스페이먼츠 개발자센터)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rYBqK/btsGN2PyUDY/IUqeKZmK4zP4dzGlGjJubk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrYBqK%2FbtsGN2PyUDY%2FIUqeKZmK4zP4dzGlGjJubk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;442&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;API 폴링과 웹훅의 차이점 (출처: 토스페이먼츠 개발자센터)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;준비사항&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GA4 Data API&amp;nbsp;&lt;/li&gt;
&lt;li&gt;GCP Cloud Functions&lt;/li&gt;
&lt;li&gt;Slack API: Incoming Wehbhook&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;API 훑어보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebhookNotification은 &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/properties.audienceLists&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;audienceList&lt;/a&gt; API 속성에 추가하여 사용하면 됩니다. 웹훅 기능을 검증하기 위해&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/properties.audienceLists/create&quot;&gt;v1alpha.properties.audienceLists.create&lt;/a&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Webhook 속성값을 추가하여 테스트를 진행하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dn9TrB/btsGL1EJeAb/FKcUKHmXQdwNQeOGFKcoeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dn9TrB/btsGL1EJeAb/FKcUKHmXQdwNQeOGFKcoeK/img.png&quot; data-alt=&quot;properties.audienceLists REST API&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dn9TrB/btsGL1EJeAb/FKcUKHmXQdwNQeOGFKcoeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdn9TrB%2FbtsGL1EJeAb%2FFKcUKHmXQdwNQeOGFKcoeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1394&quot; height=&quot;709&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;properties.audienceLists REST API&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XWKCu/btsGN225n5w/4z5Z2hmETKBJCLgr2Bgcrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XWKCu/btsGN225n5w/4z5Z2hmETKBJCLgr2Bgcrk/img.png&quot; data-alt=&quot;WebhookNotification API 규격 상세&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XWKCu/btsGN225n5w/4z5Z2hmETKBJCLgr2Bgcrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXWKCu%2FbtsGN225n5w%2F4z5Z2hmETKBJCLgr2Bgcrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1394&quot; height=&quot;709&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;WebhookNotification API 규격 상세&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;WebhookNotification 상세&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;적용가능 API: &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Data API&lt;/a&gt; &amp;gt; v1alpha &amp;gt; &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/properties.audienceLists&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;properties.audienceLists&lt;/a&gt; 또는 &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/properties.recurringAudienceLists&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;properties.recurringAudienceLists&lt;/a&gt;에 적용 가능&lt;/li&gt;
&lt;li&gt;Note: Google Analytics 리포트 데이터에 접근하기 위해서는 Data API를 사용해야 합니다. WebhookNotification 속성은  uri와 channelToken으로 구성됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JSON 포맷&lt;/blockquote&gt;
&lt;pre id=&quot;code_1711455273723&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;uri&quot;: string,
  &quot;channelToken&quot;: string
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;uri를 설정할 때 아래와 같은 특징 및 제약사항을 확인해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹훅 알림을 받을 웹주소&lt;/li&gt;
&lt;li&gt;웹훅 수신메시지는 POST request로 리턴됨&lt;/li&gt;
&lt;li&gt;sentTimestamp: 보낸 시간(UNIX microseconds). 재전송되었는지 확인할 때 사용&lt;/li&gt;
&lt;li&gt;HTTPS만 가능하며, 유효한 SSL 인증서가 있는 웹서버여야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Audience List API&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;HTTP Request&lt;/blockquote&gt;
&lt;pre id=&quot;code_1713607007952&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST https://analyticsdata.googleapis.com/v1alpha/{parent=properties/*}/audienceLists&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Path Parameter&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;properties값은 GA Admin &amp;gt; Property &amp;gt; Property Detail 메뉴에서 확인하세요. 위 {parent=properties/*}에 아래와 같은 형식으로 Property값을 추가하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713607015750&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;properties/262940150&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Request&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GA Admin API 조회에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;받아온&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;audience값을 Request audience값에 추가합니다. dimensionName은 deviceId로 추가합니다. uri에는 웹훅서버용으로 생성한 GCP Cloud Functions URL(https://us-central1-singular-XXXXXX.cloudfunctions.net/ga4-webhook)을 추가하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713608103377&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;audience&quot;: &quot;properties/262940150/audiences/5816450822&quot;,
  &quot;dimensions&quot;: [
    {
      &quot;dimensionName&quot;: &quot;deviceId&quot;
    }
  ],
  &quot;webhookNotification&quot;: {
    &quot;uri&quot;: &quot;https://us-central1-singular-XXXXXX.cloudfunctions.net/ga4-webhook&quot;    
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Response&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 리턴 예시에서  state가 CREATING으로 리턴된 것을 볼 수 있습니다. 작업이 완료되면 state가 ACTIVE로 변경되어 한번 더 웹훅이 리턴됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709435595087&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;operations/262940150pa2597756&quot;,
  &quot;response&quot;: {
    &quot;@type&quot;: &quot;type.googleapis.com/google.analytics.data.v1alpha.AudienceList&quot;,
    &quot;name&quot;: &quot;properties/262940150/audienceLists/2597756&quot;,
    &quot;audience&quot;: &quot;properties/262940150/audiences/5816450822&quot;,
    &quot;audienceDisplayName&quot;: &quot;GA Data API 연동하기 - 1. Overview 페이지 방문자&quot;,
    &quot;dimensions&quot;: [
      {
        &quot;dimensionName&quot;: &quot;deviceId&quot;
      }
    ],
    &quot;state&quot;: &quot;CREATING&quot;,
    &quot;beginCreatingTime&quot;: &quot;2024-04-20T11:02:28.096195246Z&quot;,
    &quot;rowCount&quot;: 0,
    &quot;percentageCompleted&quot;: 0,
    &quot;webhookNotification&quot;: {
      &quot;uri&quot;: &quot;https://us-central1-singular-XXXXXX.cloudfunctions.net/ga4-webhook&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GCP 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloud IAM 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webhook uri 부분 가이드에 따라 역할을 부여합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Webhook 가이드 내용&lt;br /&gt;In Cloud IAM, you will need to grant the service account permissions to the &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;&lt;u&gt;Cloud Run Invoker (roles/run.invoker) &amp;amp; Cloud Functions Invoker (roles/cloudfunctions.invoker) roles&lt;/u&gt;&lt;/b&gt;&lt;/span&gt; for the webhook post request to pass Google Cloud Functions authentication.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가이드 내용과 같이 google-analytics-audience-export@system.gserviceaccount.com&lt;/b&gt;에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Cloud Run 호출자&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Cloud Functions 호출자&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;역할을 추가했습니다. (아래 그림)&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;text-align: center; caret-color: transparent; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; src=&quot;https://blog.kakaocdn.net/dn/6GQ92/btsFm7lGGaW/J00so7UnFtE00RnUzXWrRK/img.png&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;695&quot; data-is-animation=&quot;false&quot; /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Cloud Function 생성&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Cloud Functions 함수 만들기&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;환경: 2세대(2nd gen)&lt;/li&gt;
&lt;li&gt;Function name: ga4-webhook&lt;/li&gt;
&lt;li&gt;Region: us-central1(선택)&lt;/li&gt;
&lt;li&gt;Trigger
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Trigger type: HTTPS&lt;/li&gt;
&lt;li&gt;Authentication: Require authentication 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYi5H/btsFsnHG6D2/CTZEU4qVQZOSSINn8YUXG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYi5H/btsFsnHG6D2/CTZEU4qVQZOSSINn8YUXG0/img.png&quot; data-alt=&quot; Cloud Functions 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYi5H/btsFsnHG6D2/CTZEU4qVQZOSSINn8YUXG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYi5H%2FbtsFsnHG6D2%2FCTZEU4qVQZOSSINn8YUXG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;546&quot; height=&quot;700&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; Cloud Functions 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;웹훅 코드 추가&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/owviP/btsGNagAg0h/9XMBBpipCgp0qCGZXnWhCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/owviP/btsGNagAg0h/9XMBBpipCgp0qCGZXnWhCk/img.png&quot; data-alt=&quot;웹훅 코드 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/owviP/btsGNagAg0h/9XMBBpipCgp0qCGZXnWhCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FowviP%2FbtsGNagAg0h%2F9XMBBpipCgp0qCGZXnWhCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1394&quot; height=&quot;708&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;웹훅 코드 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;샘플코드&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;package.json&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713618510579&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;dependencies&quot;: {
    &quot;@google-cloud/functions-framework&quot;: &quot;^3.0.0&quot;,
    &quot;axios&quot;: &quot;1.6.7&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;index.js&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713618470044&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const functions = require('@google-cloud/functions-framework');
const http = require('axios');

functions.http('ga4webhook', async (req, res) =&amp;gt; {
  // res.send(`Hello ${req.query.name || req.body.name || 'World'}!`);
  const postback_body = req.body;
  console.log(`postback: ${JSON.stringify(req.body)}`);

  const response = await http.post('&amp;lt;Slack Incoming Webhook URL&amp;gt;',{
    text: JSON.stringify(req.body)
  });
  console.log(`response: ${response.data}`);
  res.json({ &quot;result&quot;: response.data }).status(200);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Slack 앱 생성 및 웹훅 주소 가져오기&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;앱 생성&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://api.slack.com/apps&quot;&gt;https://api.slack.com/apps&lt;/a&gt;에서 슬랙앱을 생성합니다.&lt;/li&gt;
&lt;li&gt;GA4웹훅을 슬랙으로 받기 위해 GA4 Report 앱을 생성하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGmdKr/btsGNOjFqEi/T1XtzpxyiPZFRpgvh8v6f1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGmdKr/btsGNOjFqEi/T1XtzpxyiPZFRpgvh8v6f1/img.png&quot; data-alt=&quot;슬랙 앱 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGmdKr/btsGNOjFqEi/T1XtzpxyiPZFRpgvh8v6f1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGmdKr%2FbtsGNOjFqEi%2FT1XtzpxyiPZFRpgvh8v6f1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;693&quot; height=&quot;371&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;슬랙 앱 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Incoming Webhook 주소 확인&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 앱을 클릭하면 아래와 같이 Incoming Webhooks 메뉴를 확인할 수 있습니다. 아래 Webhook URL에 있는 URL을 복사하여 GCP Cloud Functions post URL에 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L8RSt/btsGNz77GxT/sYVCxjg6C53iJ04ZFafyF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L8RSt/btsGNz77GxT/sYVCxjg6C53iJ04ZFafyF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L8RSt/btsGNz77GxT/sYVCxjg6C53iJ04ZFafyF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL8RSt%2FbtsGNz77GxT%2FsYVCxjg6C53iJ04ZFafyF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1394&quot; height=&quot;708&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Cloud Functions 웹훅 수신결과&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmLzYW/btsFuN60Fug/aJxBxEQPf69phk1yxVpWj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmLzYW/btsFuN60Fug/aJxBxEQPf69phk1yxVpWj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmLzYW/btsFuN60Fug/aJxBxEQPf69phk1yxVpWj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmLzYW%2FbtsFuN60Fug%2FaJxBxEQPf69phk1yxVpWj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1375&quot; height=&quot;654&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Slack Incoming Webhook 수신결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림과 같이 CREATING 상태에 대한 웹훅이 먼저 수신되고, 데이터처리가 완료된 후 ACTIVE 상태에 대한 웹훅이 추가로 수신된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1071&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HvuL3/btsGMMAlD6v/Fgb99KFF4qKZKC9uFkJY8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HvuL3/btsGMMAlD6v/Fgb99KFF4qKZKC9uFkJY8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HvuL3/btsGMMAlD6v/Fgb99KFF4qKZKC9uFkJY8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHvuL3%2FbtsGMMAlD6v%2FFgb99KFF4qKZKC9uFkJY8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1071&quot; height=&quot;265&quot; data-origin-width=&quot;1071&quot; data-origin-height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;WebhookNotification&lt;/blockquote&gt;
&lt;figure id=&quot;og_1709431022454&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;WebhookNotification &amp;nbsp;|&amp;nbsp; Data API &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. WebhookNotification 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 리소스가 업데이트될 때 Google Analytics Da&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/WebhookNotification&quot; data-og-url=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/WebhookNotification?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bwy8P4/hyVuq9ew1m/vT6U87kh84y1nppMwSSFRK/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/WebhookNotification&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/WebhookNotification&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bwy8P4/hyVuq9ew1m/vT6U87kh84y1nppMwSSFRK/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;WebhookNotification &amp;nbsp;|&amp;nbsp; Data API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. WebhookNotification 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 리소스가 업데이트될 때 Google Analytics Da&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Data API - proeperties.audeinceLists&lt;/blockquote&gt;
&lt;figure id=&quot;og_1709435352806&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;REST Resource: properties.audienceLists &amp;nbsp;|&amp;nbsp; Data API &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. REST Resource: properties.audienceLists 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 리소스: AudienceList 잠재&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/properties.audienceLists&quot; data-og-url=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/properties.audienceLists?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Eo71S/hyVujWA4mq/RC5JFRAdfqRFvkWkUSrcp1/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/properties.audienceLists&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1alpha/properties.audienceLists&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Eo71S/hyVujWA4mq/RC5JFRAdfqRFvkWkUSrcp1/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;REST Resource: properties.audienceLists &amp;nbsp;|&amp;nbsp; Data API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. REST Resource: properties.audienceLists 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 리소스: AudienceList 잠재&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Axios module - POST requests&lt;/blockquote&gt;
&lt;figure id=&quot;og_1710054631852&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;POST Requests |&amp;nbsp;Axios Docs&quot; data-og-description=&quot;POST Requests How to perform POST requests with Axios Performing a POST request JSON axios.post('/user', { firstName: 'Fred', lastName: 'Flintstone' }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); &quot; data-og-host=&quot;axios-http.com&quot; data-og-source-url=&quot;https://axios-http.com/docs/post_example&quot; data-og-url=&quot;https://axios-http.com/docs/post_example&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://axios-http.com/docs/post_example&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://axios-http.com/docs/post_example&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;POST Requests |&amp;nbsp;Axios Docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;POST Requests How to perform POST requests with Axios Performing a POST request JSON axios.post('/user', { firstName: 'Fred', lastName: 'Flintstone' }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;axios-http.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;슬랙 API: 앱 생성&lt;/blockquote&gt;
&lt;figure id=&quot;og_1713619158987&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Slack API: Applications | Slack&quot; data-og-description=&quot;Your Apps Don't see an app you're looking for? Sign in to another workspace.&quot; data-og-host=&quot;api.slack.com&quot; data-og-source-url=&quot;https://api.slack.com/apps&quot; data-og-url=&quot;https://api.slack.com/apps&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.slack.com/apps&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.slack.com/apps&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Slack API: Applications | Slack&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Your Apps Don't see an app you're looking for? Sign in to another workspace.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.slack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Data &amp;amp; MarTech/Google Marketing Platform</category>
      <category>GA4</category>
      <category>GA4 API</category>
      <category>WebHook</category>
      <category>webhook notification</category>
      <category>웹훅</category>
      <author>Jason Nam</author>
      <guid isPermaLink="true">https://neep305.tistory.com/86</guid>
      <comments>https://neep305.tistory.com/86#entry86comment</comments>
      <pubDate>Sat, 20 Apr 2024 22:21:22 +0900</pubDate>
    </item>
  </channel>
</rss>