<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>cherry_rin.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Wed, 13 Nov 2024 07:35:46 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>cherry_rin.log</title>
            <url>https://velog.velcdn.com/images/julia_heo/profile/6ee5060e-a9f4-495c-983e-fbdfce56a9be/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. cherry_rin.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/julia_heo" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[SWEA/C++] 1954. 달팽이 숫자]]></title>
            <link>https://velog.io/@julia_heo/SWEAC-1954.-%EB%8B%AC%ED%8C%BD%EC%9D%B4-%EC%88%AB%EC%9E%90</link>
            <guid>https://velog.io/@julia_heo/SWEAC-1954.-%EB%8B%AC%ED%8C%BD%EC%9D%B4-%EC%88%AB%EC%9E%90</guid>
            <pubDate>Wed, 13 Nov 2024 07:35:46 GMT</pubDate>
            <description><![CDATA[<p>이번에도 D2 문제를 풀어본다!</p>
<p>생각한 풀이 방식은 다음과 같다
맨 윗줄 ➡️ 맨 오른쪽 줄 ⬇️ 맨 아랫줄 ⬅️ 맨 왼쪽 줄 ⬆️으로 숫자를 하나씩 증가시키며 채워 나가되, 사각형의 크기를 점점 줄여주는 것이다.</p>
<img src="https://velog.velcdn.com/images/julia_heo/post/dfaac8e0-7932-4387-bbcc-91a2feee49df/image.png" width="350">
열심히 그렸는데 하나도 안와닿는 그림이군. 삐뚤빼뚤

<p>아무튼,
<code>start = 0</code>, <code>end = N-1</code>로 설정하고, <code>for</code>문 네 개로 가장 바깥쪽 테두리의 숫자를 채운 후,
<code>start++</code>, <code>end--</code>로 범위를 좁혀가며 <code>end</code>가 <code>start</code>보다 작아질 때까지 반복한다.</p>
<p>시간 복잡도는 O(T*N^2)로, <code>1 ≤ N ≤ 10</code>라서 충분하다!</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
#include &lt;vector&gt;
using namespace std;


int main(int argc, char** argv)
{
    int test_case;
    int T, N, i, j, start, end, num;
    cin&gt;&gt;T;

    vector &lt;vector &lt;int&gt;&gt; snail;

    for(test_case = 1; test_case &lt;= T; ++test_case)
    {
            cin&gt;&gt;N;
            snail.clear();
            snail.resize(N,vector&lt;int&gt;(N,0));
            start=0;
            end=N-1;
            num=1;

            while(start&lt;end){
                for( i=start; i&lt;=end; i++ ){
                    snail[start][i]=num++;
                }   
                for( i=start+1;i&lt;=end;i++){
                    snail[i][end]=num++;
                }
                for( i=end-1;i&gt;=start;i--){
                    snail[end][i]=num++;
                }
                for( i=end-1;i&gt;start;i--){
                    snail[i][start]=num++;
                }
                start++;
                end--;

            }
            if(start==end) snail[start][start]=num++;

            cout&lt;&lt;&quot;#&quot;&lt;&lt;test_case&lt;&lt;&quot;\n&quot;;
            for( i=0;i&lt;N;i++){
                for( j=0;j&lt;N;j++){
                    cout&lt;&lt;snail[i][j]&lt;&lt;&quot; &quot;;
                }
                cout&lt;&lt;&quot;\n&quot;;
            }   

    }
    return 0;
}</code></pre>
<blockquote>
<p><code>snail.clear();</code>를 사용하지 않으면 두 번째 테스트케이스부터 이상한 결과가 나왔다.  </p>
<p>찾아보니, 
C++에서 <code>resize()</code>는 벡터의 크기를 변경하지만, 메모리 용량(capacity)은 그대로 유지할 수 있다고 한다. 즉, 벡터가 이전 테스트케이스에서 할당한 메모리를 그대로 사용하면서 이전 데이터가 남아 있을 수 있다는 의미다.  </p>
<p>크기를 바꾸고 0으로 초기화까지 했는데 왜 그런지 정확히는 모르겠지만..
벡터를 재사용할 때 <code>clear()</code>는 꼭 해 줘야겠다</p>
</blockquote>
<p>다른 풀이를 찾아보니, 방향 <code>d</code>를 설정해 계속 바꿔가며 진행하는 방식과 DFS를 활용한 풀이도 있었다. 내 방법도 좋지만, DFS를 적용할 생각은 전혀 못했다....! 다양하게 생각해보기:)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SWEA/C++] 1859. 백만 장자 프로젝트]]></title>
            <link>https://velog.io/@julia_heo/SWEAC-1859.%EB%B0%B1%EB%A7%8C%EC%9E%A5%EC%9E%90%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@julia_heo/SWEAC-1859.%EB%B0%B1%EB%A7%8C%EC%9E%A5%EC%9E%90%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Tue, 12 Nov 2024 18:04:55 GMT</pubDate>
            <description><![CDATA[<p>SWEA D2 문제이다.<br>D2난이도를 보려고 풀었는데 한 번에 안풀려서 당황했다.. 큼</p>
<hr>
<p>우선, 최대 가격일 때 팔아야 하고, 이후에 또 물건이 남아 있다면 그 중 최대 가격일 때 다시 판매해야 한다.</p>
<p>예를 들어,
가격이 <code>1 1 3 1 2</code>로 주어진다면, 전체에서 최댓값은 <code>3</code>이다.<br>그러면 첫째 날과 둘째 날에 구매한 후, 셋째 날에 판매하면 된다.<br>이렇게 셋째 날까지 총 <code>4</code>의 이익을 얻는다.</p>
<p>그다음, 넷째 날 이후의 가격에서 다시 최댓값을 찾는다.<br>이 경우, 다섯째 날이 최댓값이므로 넷째 날에 구매 후 다섯째 날에 판매하면 된다.<br>이때 <code>1</code>의 이익이 추가된다.</p>
<hr>
<h4 id="생각-1">생각 1</h4>
<p>이런 로직으로 생각해 <code>max_element</code> 함수를 사용해 앞에서부터 진행하며 최대값인 인덱스 전까진 구매하고, 최대일 때 판매한 뒤, 이후 범위에서 다시 최대값을 업데이트했다.<br>하지만 <code>max_element</code>를 너무 자주 쓰는 것 같아 조금 찝찝한 풀이였다.</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
#include &lt;vector&gt;
using namespace std;

int main(int argc, char** argv)
{
    int test_case;
    int T, N, maxIndex, answer;
    vector &lt;int&gt; future;
    vector &lt;int&gt; tmp;

    cin&gt;&gt;T;

    for(test_case = 1; test_case &lt;= T; ++test_case)
    {
        cin&gt;&gt;N;
        future.resize(N,0);
        for(int i=0;i&lt;N;i++){
            cin&gt;&gt;future[i];
        }

        tmp.clear();
        maxIndex=0; answer=0;
        maxIndex=max_element(future.begin(), future.end()) - future.begin();

        for(int i=0;i&lt;N;i++){
            if(i&lt;maxIndex){
                tmp.push_back(future[i]);
            }
            else if(i==maxIndex){
                for(int t:tmp){
                    answer+=future[i]-t;
                }
                tmp.clear();
                maxIndex=max_element(future.begin()+i+1, future.end())- future.begin();
            }
        }
        cout&lt;&lt;&quot;#&quot;&lt;&lt;test_case&lt;&lt;&quot; &quot;&lt;&lt;answer&lt;&lt;endl;


    }
    return 0;
}</code></pre>
<p>시간 초과도 아니고 <em>Fail(오답)</em> 메시지가 떴다..
    <img src="https://velog.velcdn.com/images/julia_heo/post/430ee80b-f1c1-42f0-94ed-d38daf1fa3fd/image.png" alt=""></p>
<h4 id="생각-2">생각 2</h4>
<p>인터넷에서 다른 풀이를 참고해 보았다. 
생각 자체는 비슷했지만, 탐색을 아예 맨 뒤에서부터 시작하면 <code>max</code> 변수만 업데이트하면서 진행할 수 있다는 점을 알았다! 
시간 복잡도를 개선하려면 많은 고민이 필요하구나.. </p>
<p>그런데..
그렇게 작성한 코드도 예상 외로 _Fail(오답)_이 나오는것이다....</p>
<h4 id="생각-3">생각 3</h4>
<p>놀랍게도 원인은 <code>answer</code> 변수의 자료형에 있었다. <code>int</code>가 아닌 <code>long long</code>을 사용해야 했다...</p>
<p><code>long long</code>으로 변경하니 드디어 <strong><em>PASS</em></strong>! </p>
<p>최종 코드는 아래와 같다.</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
#include &lt;vector&gt;
using namespace std;

int main(int argc, char** argv)
{
    int test_case;
    int T, N, max;
    long long answer;
    vector &lt;int&gt; future;

    cin&gt;&gt;T;

    for(test_case = 1; test_case &lt;= T; ++test_case)
    {
        answer=0;max=-1;
        cin&gt;&gt;N;
        future.resize(N,0);
        for(int i=0;i&lt;N;i++){
            cin&gt;&gt;future[i];
        }


        for(int i=N-1;i&gt;=0;i--){
            if(future[i]&gt;max){
                max=future[i];
            }
            else{
                answer+=max-future[i];
            }
        }


        cout&lt;&lt;&quot;#&quot;&lt;&lt;test_case&lt;&lt;&quot; &quot;&lt;&lt;answer&lt;&lt;endl;


    }
    return 0;
}</code></pre>
<p>생각 1의 코드도 <code>answer</code>의 자료형을 <code>long long</code>으로 바꾸니 <strong><em>PASS</em></strong>였다. 
그래도 이건 제한 시간이 길어서 가능한 것!</p>
<p>시간 복잡도에 항상 유의하며 잘 공부하자.</p>
<blockquote>
<p>1,000,000일 동안 매일 10,000원에 가깝다면<br>1,000,000 * 10,000 = 10,000,000,000 (100억)이다...! 워~~
근데 오버플로우라고 안뜨고 오답이라고 뜨다니 너무하다</p>
</blockquote>
<h4 id="long-long-정리"><code>long long</code> 정리</h4>
<ul>
<li><p><strong><code>int</code></strong>  </p>
<ul>
<li>32비트  </li>
<li>범위: -2,147,483,648 ~ 2,147,483,647 (약 21억)</li>
</ul>
</li>
<li><p><strong><code>long long</code></strong>  </p>
<ul>
<li>범위: -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코드트리/C++] 코드트리 메신저]]></title>
            <link>https://velog.io/@julia_heo/%EC%BD%94%EB%93%9C%ED%8A%B8%EB%A6%ACC-%EC%BD%94%EB%93%9C%ED%8A%B8%EB%A6%AC-%EB%A9%94%EC%8B%A0%EC%A0%80</link>
            <guid>https://velog.io/@julia_heo/%EC%BD%94%EB%93%9C%ED%8A%B8%EB%A6%ACC-%EC%BD%94%EB%93%9C%ED%8A%B8%EB%A6%AC-%EB%A9%94%EC%8B%A0%EC%A0%80</guid>
            <pubDate>Sat, 12 Oct 2024 00:02:05 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.codetree.ai/training-field/frequent-problems/problems/codetree-messenger?&amp;utm_source=clipboard&amp;utm_medium=text">코드트리 메신저</a></p>
<p>틀림~</p>
<ol>
<li>power가 짧다고 아랫것도 push안함 (testcase없으면 어떻게 알아낼건데)</li>
<li>OFF한 본인은 아랫것들 알림 받을수가 있었음;;</li>
<li>아예 수신가능 알림 수를 계속 업데이트 해서 사용. 500 호출 시 for문돌리면 시간초과남
근데 dp 로직 자체가 답을 봐도 이해가 안돼서 오답 포기.. 강해져서 돌아오자</li>
</ol>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
#include &lt;vector&gt;
#include &lt;queue&gt;
using namespace std;
int N;


int val[21];
int info[100001][21];


struct node {
    int data;
    int parent;
    int authority;
    int alarm;
};
vector &lt;node&gt; Chat;

void update (int start){
    for(int i=start;i&lt;=N;i++){
        int curr=i;
        int x=Chat[curr].authority;
        while(Chat[curr].parent!=0&amp;&amp;x!=0){
            curr=Chat[curr].parent;
            x--;
            if(x&gt;0) val[curr]++;
            nx[curr][x]++;
        }
    }

}



void alarmOn(int c){
    if(Chat[c].alarm==1)Chat[c].alarm=0;
    else Chat[c].alarm=1;
}
void powerChange(int c, int power){
    Chat[c].authority=power;
}
void changeParents(int c1, int c2){
    int tmp=Chat[c1].parent;
    Chat[c1].parent=Chat[c2].parent;
    Chat[c2].parent=tmp;
}
void checkAlarm(int c){
    int cnt=0;
    queue &lt;pair&lt;int,int&gt;&gt; check;
    check.push(make_pair(c,1));
    while(!check.empty()){
        int front_data=check.front().first;
        int front_length=check.front().second;
        check.pop();
        for(node n:Chat){
            if(n.parent==front_data&amp;&amp;n.alarm==1){
                if(n.authority&gt;=front_length){
                    cnt++;
                }
                check.push(make_pair(n.data,front_length+1));
            }
        }
    }
    cout&lt;&lt;cnt&lt;&lt;endl;

}

int main() {
    int order,Q,value1, value2;
    cin&gt;&gt;N&gt;&gt;Q;
    Chat.resize(1+N);

    // 100 준비 
    cin&gt;&gt;order;
    for(int i=1;i&lt;=N;i++){
        cin&gt;&gt;Chat[i].parent;
        Chat[i].data=i;
        Chat[i].alarm=1;
    }
    for(int i=1;i&lt;=N;i++){
        cin&gt;&gt;Chat[i].authority;
    }

    // 명령 입력
    int num=1;
    while(num++&lt;Q){
        cin&gt;&gt;order;
        if(order==200){
            cin&gt;&gt;value1;
            alarmOn(value1);
        }
        else if (order == 300){
            cin&gt;&gt;value1&gt;&gt;value2;
            powerChange(value1,value2);
        }
        else if (order == 400){
            cin&gt;&gt;value1&gt;&gt;value2;
            changeParents(value1,value2);
        }
        else if (order == 500){
            cin&gt;&gt;value1;
            checkAlarm(value1);
        }
    }




    return 0;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BOJ/C++] 14501 퇴사]]></title>
            <link>https://velog.io/@julia_heo/BOJC-14501-%ED%87%B4%EC%82%AC</link>
            <guid>https://velog.io/@julia_heo/BOJC-14501-%ED%87%B4%EC%82%AC</guid>
            <pubDate>Thu, 10 Oct 2024 02:15:13 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/14501">퇴사</a></p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
#include &lt;vector&gt;
using namespace std;

int main()
{
    int N;
    cin&gt;&gt;N;
    vector &lt;vector&lt;pair&lt;int,int&gt;&gt;&gt; vec(N+1);// i날 끝나는거 모음:시작 날짜, 금액
    vector&lt;int&gt; dp(N+1);

    int t,p;
    for(int i=1;i&lt;=N;i++){
        cin&gt;&gt;t&gt;&gt;p;
        if (i + t - 1 &lt;= N) vec[i+t-1].push_back(make_pair(i,p));
    }

    for(int i=1;i&lt;=N;i++){
        dp[i]=dp[i-1];
        for(pair&lt;int,int&gt; p : vec[i]){
            dp[i]=max(dp[i],dp[p.first-1]+p.second);
        }
    }

    cout&lt;&lt;dp[N];

    return 0;
}</code></pre>
<ol>
<li><code>if (i + t - 1 &lt;= N)</code> 이거 추가 안하고서 왜 코드 안돌아가징.. 이랬다..</li>
<li>dp 식에서 <code>dp[p.first-1]+p.second</code>인데 처음엔 <code>dp[p.first]+p.second</code>로 작성했다,,</li>
<li>식: 현재 <strong>i일</strong>에서 끝나는 모든 상담에 대해 진행
 start: 현재 보고있는 상담이 시작하는 날 / p: 현재 보고있는 상담의 금액
 dp[i]=max(dp[i-1]+dp[start-1]+p)</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코드트리/C++] 왕실의 기사 대결]]></title>
            <link>https://velog.io/@julia_heo/%EC%BD%94%EB%93%9C%ED%8A%B8%EB%A6%ACC-%EC%99%95%EC%8B%A4%EC%9D%98-%EA%B8%B0%EC%82%AC-%EB%8C%80%EA%B2%B0</link>
            <guid>https://velog.io/@julia_heo/%EC%BD%94%EB%93%9C%ED%8A%B8%EB%A6%ACC-%EC%99%95%EC%8B%A4%EC%9D%98-%EA%B8%B0%EC%82%AC-%EB%8C%80%EA%B2%B0</guid>
            <pubDate>Wed, 09 Oct 2024 06:54:26 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.codetree.ai/training-field/frequent-problems/problems/royal-knight-duel?&amp;utm_source=clipboard&amp;utm_medium=text">왕실의 기사 대결</a></p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
#include &lt;vector&gt;
#include &lt;queue&gt;
#include &lt;set&gt;
using namespace std;
int L,N,Q;
int dx[]={-1,0,1,0};
int dy[]={0,1,0,-1};
vector &lt;vector &lt;int&gt;&gt; chess;
vector &lt;vector &lt;int&gt;&gt; knLo;

struct knight {
    int r;
    int c;
    int h;
    int w;
    int k;
    int dm;
};

vector &lt;knight&gt; Knight;

void move(int n,int d,set&lt;int&gt; s){
    for (auto element : s){
        // 기존 위치 다 0으로 설정
        for(int j=0;j&lt;Knight[element].h;j++){
            for(int k=0;k&lt;Knight[element].w;k++){
                knLo[Knight[element].r+j][Knight[element].c+k]=0;
            }
        }
    }
    for (auto element : s){
        // 위치정보 r,c 변경
        Knight[element].r+=dx[d];
        Knight[element].c+=dy[d];

        // 명령 받은애 아니면 데미지 계산, 죽나 확인
        if(element!=n){
            for(int j=0;j&lt;Knight[element].h;j++){
                for(int k=0;k&lt;Knight[element].w;k++){
                    if(chess[Knight[element].r+j][Knight[element].c+k]==1){ // 함정이라면
                        Knight[element].dm++; //데미지 계산
                        Knight[element].k--; // 체력 계산
                        if(Knight[element].k&lt;=0){ // 체력 고갈된 경우 죽음
                            Knight[element].r=-1; 
                            Knight[element].c=-1;
                            Knight[element].h=-1;
                            Knight[element].w=-1;
                            break;
                        }
                    }
                }
                if(Knight[element].r==-1)break;
            }
        }
    }
    for (auto element : s){
        if(Knight[element].r!=-1){ //안죽었으면 다시 배치
            for(int j=0;j&lt;Knight[element].h;j++){
                for(int k=0;k&lt;Knight[element].w;k++){
                    knLo[Knight[element].r+j][Knight[element].c+k]=element;
                }
            }
        }
    }



}

void play(int n,int d){

    int r=Knight[n].r;
    int c=Knight[n].c;
    int h=Knight[n].h;
    int w=Knight[n].w;
    if(r==-1) return; // 마지막에 추가. 없으면 테스트케이스 18번에서 틀림

    queue &lt;int&gt; nextK;
    set &lt;int&gt; tmp;
    tmp.insert(n);
    int can=1;
    for(int j=0;j&lt;h;j++){
        for(int k=0;k&lt;w;k++){
            if(chess[r+j+dx[d]][c+k+dy[d]]==2){
                can=0;
                break;
            }
            int next= knLo[r+j+dx[d]][c+k+dy[d]];
            if(next!=0&amp;&amp;next!=n) {
                nextK.push(next);
                tmp.insert(next);
            }
        }
        if(can==0)break;
    }
    if(can==1){
        while(!nextK.empty()&amp;&amp;can==1){
            int nn=nextK.front();
            nextK.pop();
            for(int j=0;j&lt;Knight[nn].h;j++){
                for(int k=0;k&lt;Knight[nn].w;k++){
                    if(chess[Knight[nn].r+j+dx[d]][Knight[nn].c+k+dy[d]]==2){
                        can=0;
                        break;
                    }
                    int next= knLo[Knight[nn].r+j+dx[d]][Knight[nn].c+k+dy[d]];

                    if(next!=0&amp;&amp;next!=nn) {
                        nextK.push(next);
                        tmp.insert(next);
                    }
                }
                if(can==0)break;
            }
        }
    }
    if(can) move(n,d,tmp);

}



int main() {
    cin&gt;&gt;L&gt;&gt;N&gt;&gt;Q;
    chess.resize(L+2,vector &lt;int&gt;(L+2,2));
    knLo.resize(L+1,vector &lt;int&gt;(L+1,0));
    Knight.resize(N+1); 

    for(int i=1;i&lt;=L;i++){
        for(int j=1;j&lt;=L;j++){
            cin&gt;&gt;chess[i][j];
        }
    }
    int r,c,h,w,k;
    for(int i=1;i&lt;=N;i++){
        cin&gt;&gt;r&gt;&gt;c&gt;&gt;h&gt;&gt;w&gt;&gt;k;
        Knight[i].r=r;
        Knight[i].c=c;
        Knight[i].h=h;
        Knight[i].w=w;
        Knight[i].k=k;
        Knight[i].dm=0;
        for(int j=0;j&lt;h;j++){
            for(int k=0;k&lt;w;k++){
                knLo[r+j][c+k]=i;
            }
        }
    }

    int n,d,rr=0;
    while(rr++&lt;Q){
        cin&gt;&gt;n&gt;&gt;d;
        play(n,d);
        // for(int i=1;i&lt;=L;i++){for(int j=1;j&lt;=L;j++){cout&lt;&lt;knLo[i][j];}cout&lt;&lt;endl;}
    }
    int ans=0;
    for(knight k: Knight){
        if(k.r!=-1) ans+=k.dm;
    }
    cout&lt;&lt;ans;
    return 0;
}</code></pre>
<ol>
<li>처음에 옆에 있는 기사들을 큐에 저장했더니 같은 기사가 여러 번 저장돼서 데미지가 여러번 계산됐다. 중복 안되게 set로 바꿨다.</li>
<li>deque나 map이나 set 써볼때 문법 찾아봤다. 안보고도 쓸줄알기</li>
<li><code>if(r==-1) return;</code> 이거 마지막에 추가했다. 안하면 틀림</li>
</ol>
<p>테스트 케이스 여러개가 아니면 이런 사소한 부분들을 다 안틀리고 챙길 수 있을지 모르겠다🥲</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코드트리/C++] 싸움땅]]></title>
            <link>https://velog.io/@julia_heo/%EC%BD%94%EB%93%9C%ED%8A%B8%EB%A6%AC-%EC%8B%B8%EC%9B%80%EB%95%85</link>
            <guid>https://velog.io/@julia_heo/%EC%BD%94%EB%93%9C%ED%8A%B8%EB%A6%AC-%EC%8B%B8%EC%9B%80%EB%95%85</guid>
            <pubDate>Mon, 07 Oct 2024 20:23:16 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
#include &lt;vector&gt;
using namespace std;
vector &lt;vector&lt;vector&lt;int&gt;&gt;&gt; gun;
vector &lt;vector&lt;int&gt;&gt; people;
vector &lt;vector&lt;int&gt;&gt; info;
int N,M,K;
int dx[]={-1,0,1,0};
int dy[]={0,1,0,-1};

void changeGun(int player,int x, int y){
    if(gun[x][y].size()==0)return;
    if(gun[x][y][0]&gt;info[player][4]){ // 총 바꿔들기
        int tmp = info[player][4];
        info[player][4]=gun[x][y][0];
        gun[x][y][0]=tmp;
        sort(gun[x][y].begin(), gun[x][y].end(),greater&lt;int&gt;());
    }
}
void fight(int p1, int p2, int x, int y){
    // 승자 패자 결정
    int p1power=info[p1][3]+info[p1][4];
    int p2power=info[p2][3]+info[p2][4];
    int winner=p1, looser=p2;
    if(p1power==p2power){
        if(info[p1][3]&lt;info[p2][3]) {
            winner=p2;
            looser=p1;
        }
    } else if(p1power&lt;p2power){
        winner=p2;
        looser=p1;
    }
    int score=(p1power-p2power);
    if (score&lt;0)score*=-1;
    info[winner][5]+=score;
    // cout&lt;&lt;score&lt;&lt;&quot;차이 \n&quot;;

    // 총 내려놓기
    if(info[looser][4]!=0){
        gun[x][y].push_back(info[looser][4]);
        info[looser][4]=0;
        sort(gun[x][y].begin(), gun[x][y].end(),greater&lt;int&gt;());
    }


    // 이긴사람 총 변경
    changeGun(winner,x,y);

    // 진 사람 이동 
    int nx,ny,nd;
    int d=info[looser][2];
    // cout&lt;&lt;looser&lt;&lt;d;
    for(int i=0;i&lt;4;i++){
        nd=(d+i)%4;
        nx=x+dx[nd];
        ny=y+dy[nd];
        if(nx &gt;= 1 &amp;&amp; nx &lt;= N &amp;&amp; ny &gt;= 1 &amp;&amp; ny &lt;= N &amp;&amp; people[nx][ny] == 0){
            info[looser][0]=nx;
            info[looser][1]=ny;
            info[looser][2]=nd;
            people[nx][ny]=looser;
            changeGun(looser,nx,ny);
            break;
        }

    }

    // 이동
    info[winner][0]=x;
    info[winner][1]=y;

    people[x][y]=winner;
}

void move(int player){
    // 이동 좌표 파악
    int d=info[player][2];
    int nx=info[player][0]+dx[d];
    int ny=info[player][1]+dy[d];
    if(nx&lt;1||nx&gt;N||ny&lt;1||ny&gt;N){
        d=(d+2)%4;
        info[player][2]=d;
        nx=info[player][0]+dx[d];
        ny=info[player][1]+dy[d];
    }

    if(people[nx][ny]==0){ // 사람 없는 경우
        // 이동
        people[info[player][0]][info[player][1]]=0;
        people[nx][ny]=player;
        info[player][0]=nx;
        info[player][1]=ny;
        changeGun(player,nx,ny);
    }
    else { // 사람 만난 경우
        people[info[player][0]][info[player][1]]=0;
        fight(player,people[nx][ny],nx,ny);
    }

}

int main() {
    int a;
    cin&gt;&gt;N&gt;&gt;M&gt;&gt;K;
    gun.resize(N+1,vector&lt;vector&lt;int&gt;&gt;(N+1));
    for(int i=1;i&lt;=N;i++){
        for(int j=1;j&lt;=N;j++){
            cin&gt;&gt;a;
            if(a!=0)gun[i][j].push_back(a);
        }
    }
    info.resize(M+1,vector&lt;int&gt;(6,0)); // x좌표 y좌표 방향 초기 총 점수
    people.resize(N+1,vector&lt;int&gt;(N+1));
    for(int i=1;i&lt;=M;i++){
        cin&gt;&gt;info[i][0]&gt;&gt;info[i][1]&gt;&gt;info[i][2]&gt;&gt;info[i][3];
        people[info[i][0]][info[i][1]]=i;
    }
    for(int i=1;i&lt;=K;i++){

        for(int j=1;j&lt;=M;j++){
            move(j);
        }
    }
    for(int i=1;i&lt;=M;i++){
        cout&lt;&lt;info[i][5]&lt;&lt;&quot; &quot;;
    }

    return 0;
}</code></pre>
<ol>
<li>총바꾸는 로직을 changeGun함수로 안빼고 중복된 코드를 계속 작성했었다.</li>
<li>gun을 sort해서 관리할 생각을 못해서 계속 push_back 하고 find해서 erase했었다. 비효율적이다.</li>
<li>⭐️ 싸움 후 진사람 방향 바꿀 때 for문 안에서 새로운 방향을 nd에 저장하는게 아니라 d에 저장해서 계속 업데이트 했다. 그럼 당연히 방향이 제대로 설정이 안된다.... 이거때문에 틀린 부분 몇시간을 찾았다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ONCE] 결제 전 최대 할인 카드를 찾아주는 모델을 구현해보자]]></title>
            <link>https://velog.io/@julia_heo/ONCE</link>
            <guid>https://velog.io/@julia_heo/ONCE</guid>
            <pubDate>Tue, 21 May 2024 14:09:52 GMT</pubDate>
            <description><![CDATA[<p>졸업 프로젝트로 <strong>카드 다보유자를 위한 결제 전 최대 할인 카드를 추천해 주는 AI 챗봇 서비스</strong>, <strong>ONCE</strong>를 구현 중에 있다. </p>
<p>원스는 사용자가 보유한 카드 중 특정 결제처에서 최대 할인을 받을 수 있는 카드를 추천함으로써, 소비자가 모든 카드의 혜택을 최대한 손쉽게 활용할 수 있는 솔루션을 제공한다.</p>
<p>가장 핵심적인 서비스인 &quot;카드 추천&quot;의 구현을 위한 과정과 그 기술들을 정리해보고자 한다. </p>
<h1 id="카드-추천-모델-선택-과정">카드 추천 모델 선택 과정</h1>
<p>최대 혜택을 주는 카드 추천 챗봇을 구현하기 위해 많은 시행착오와 결정이 있었다.<br>
주어진 여러 카드들의 조합에서 최대의 혜택을 주는 카드를 &quot;추천&quot;한다는 점에서 추천 모델 사용을 고려하였지만, 사용자의 선호도 또는 과거의 기록과는 상관없는 단발적인 선택에 해당되기에, <strong>생성형 ai</strong>를 사용하고, 가능하다면 파인튜닝하여 해당 작업에 대한 적합도를 높이기로 하였다.</p>
<h3 id="1-end-to-end-gpt-처리-gpt-vs-gemini">1) End-to-end GPT 처리: GPT vs. Gemini</h3>
<p>보유 카드의 크롤링한 혜택 정보 전부를 GPT와 Gemini에 입력하였다.</p>
<ul>
<li><strong>GPT</strong>
기술 검증을 위해, 임의로 선택된 4개의 카드들의 혜택을 분석하여 20개의 테스트 키워드(결제처)와 그 예상 결과를 표에 작성하였다.
<img src="https://velog.velcdn.com/images/julia_heo/post/aded32d8-ffb4-4fba-9147-3c16f9529dd1/image.png" width=700><br>
GPT 모델에 어떤 행동을 할지 지정하는 system은 다음과 같이 작성하였다.<pre><code>너는 사용자의 결제 금액과 전월실적을 고려해 결제처에 가장 좋은 카드와 해당 혜택을 20글자 이내로 요약해 알려주는 카드 추천 보조야.
혜택 정보에서 각 카드는 \n로 구분되고, 카드의 분야별 혜택은 ###로 구분돼</code></pre>이후 user가 <code>내가 가진 카드 혜택이뭐야?</code> 라고 하면, 챗봇에 해당하는 assistant가 각 카드들의 정보를 나열하였다.<br>
그 결과, 유의미한 결과값을 출력하며 높은 정확도를 보이는 것을 확인할 수 있었다.<img src="https://velog.velcdn.com/images/julia_heo/post/04ced1c2-241a-469f-b54d-ea4bda38685f/image.png" width=700>
하지만, 카드를 여러 장 활용해 test하던 도중 `InvalidRequestError: This model's maximum context length is 16385 tokens.` 에러를 마주치게 된다. GPT로 보낼 수 있는 context의 길이가 **최대 16385 토큰**이라는 것이다.
<img src="https://velog.velcdn.com/images/julia_heo/post/e0163ccf-1fe0-4ae8-95a9-769ea6de5990/image.png" width=700>GPT에 요청을 보낼 수 있는 메시지의 길이는 제한되어 있었고, 원스에서 사용자가 등록하는 카드 수에는 제한이 없고 몇몇 카드는 매우 긴 혜택 정보와 설명들까지 담고있기 때문에, 요약되지 않은 여러 개의 카드 혜택 정보를 한번에 입력하는 것이 불가능하였다.<br><br>



</li>
</ul>
<ul>
<li><strong>Gemini</strong>
생성형 ai를 통해 기술을 검증해보던 것은 2024년 1월이었고, 마침 2023년 12월에 생성형 ai인 Gemini가  출시되어 Gemini를 통해 카드 추천을 시도해보았다.
또한     Gemini는 최대 토큰 수가 <strong>30720 tokens</strong>이기에, 메시지 길이의 제한에서는 어느정도 자유로워질 수 있었다. <br>
<a href="https://ai.google.dev/gemini-api/docs/ai-studio-quickstart?hl=ko#structured_example">Gemini 공식문서</a>를 참고한 결과 <strong>구조화된 프롬프트</strong>가 일관된 출력 형식을 따르도록 하거나 모델이 수행하기를 원하는 작업을 말로 설명하기 어려울 때 유용하다는 점이 적합해 보였다.
<img src="https://velog.velcdn.com/images/julia_heo/post/72e108e7-b194-4903-aa98-1842ae5e17b8/image.png" width=700>동일하게 system prompt를 입력해주고, 예시로 input, output을 2개씩 써주었다.
test해본 결과, CGV를 입력하였지만 &quot;커피10%&quot; 혜택을 출력하거나, 카드들 중 가장 혜택이 적은 카드를 출력하며, 전혀 카드들의 혜택을 이해하지 못하고, 계속해서 관련없는 답변을 반환하였다.<img src="https://velog.velcdn.com/images/julia_heo/post/5d8202c7-554f-4096-b950-a6207664a4ea/image.png" width=700>
system이 추상적이기 때문에 이러한 결과가 도출될 수 있다고 생각하여, system을 더 자세하게 수정하여 다시 기술 검증을 해 보았다. 새로 작성한 system은 다음과 같다.
```
줄바꿈으로 구분되어 입력된 여러 카드들 중 어떤 카드의 어떤 혜택이 결제처에서 최고의 할인을 가져다줄지 골라주세요.\n
결제처, 결제금액, 여러 카드의 정보가 쉼표로 구분되어 입력됩니다. 
입력된 결제처는 브랜드일수도 특정 분야일수도 있으니 카드 헤택 정보에서 관련이 있는 분야와 브랜드를 모두 잘 찾아야 합니다. 
카드 정보는 여러 개의 {카드이름, 카드 고유 번호, 혜택 문자열}로 이루어집니다. 
각 카드들은 줄바꿈으로 구분되어있으니 줄이 바뀐다면 그 다음 카드의 정보를 나타낸다는 것을 기억하세요.
한 카드 안에서 분야별 혜택은 ###로 구분되어 있습니다. 
각각의 카드가 입력된 결제처에 해당되는 혜택을 가지고 있다면 할인 금액을 계산하고, 
여러 카드 중 가장 할인 금액이 큰 카드의 고유번호, 결제처에 해당되는 혜택의 20글자 이내 요약 내용,
해당 혜택 적용 시 받게되는 할인 금액을 쉼표로 구분해 출력하세요. 
할인이 n원이라고 써있으면 n원, n%라고 써있으면 결제금액의 n%를 직접 계산해야 합니다.
```
여러 입력으로 테스트를 해보던 중 gemini가 카드들이 여러장이고, 각각의 혜택이 존재함을 인식하지 못하고 있다는 느낌이 들어, 두가지 카드의 혜택 정보를 순서만 바꾸어 입력해 본 결과 다음과 같은 결과가 출력되었다.<img src="https://velog.velcdn.com/images/julia_heo/post/1914b9fd-94c5-4364-9d28-56a67130a1c8/image.png" width=700>무조건 앞에 있는 카드의 혜택을 찾아 출력한다.<br>

</li>
</ul>
<br>

<p>이러한 과정을 통해 <strong>Gemini는 카드 추천에 적합하지 않다</strong>고 판단하여, GPT를 사용하기로 결정하였다.</p>
<h3 id="2-gpt-35-turbo-vs-gpt-4">2) GPT-3.5 Turbo vs. GPT-4</h3>
<p>GPT를 선택하였다면, 다음으로 구체적으로 GPT의 어떤 모델을 사용할지 결정해야 한다.</p>
<p>GPT-4가 가장 최신 버전으로, GPT-3.5보다 더 높은 정확도와 언어 이해력을 가지고 있지만, 일반적으로 더 느린 응답 시간을 가진다.</p>
<p>실시간 추천 시스템에서 응답 시간은 매우 중요한 요소이기 때문에, 각 모델을 사용했을 때 걸리는 시간을 비교해보았다.
로컬에서 python으로 호출해 걸리는 시간은 실제 서비스와는 다를 수 있기 때문에, 백엔드 서버에서 각 모델을 활용해 호출 했을 때의 응답 시간을 확인하였다.</p>
<img src="https://velog.velcdn.com/images/julia_heo/post/1ae62180-1b70-4b1c-bfd6-16e60d5d8297/image.png" width=700>
두 모델 모두 예상보다 긴 응답시간을 가졌는데, (다행히 지금은 더 빠르다!)
5.15초는 사용자 경험 측면에서 다소 길게 느껴질 수 있고, 불편함을 느낄만하다고 판단하였다.

<p>또한, GPT-4모델과 GPT-3.5모델은 가격 차이도 크다.
<img src="https://velog.velcdn.com/images/julia_heo/post/afe9f2f0-c147-446c-ae26-24c8d829bcf4/image.avif" width=700></p>
<p>따라서 비용과 성능의 균형을 맞춘 <strong>GPT-3.5 Turbo모델</strong>을 선택하였다.</p>
<h3 id="3-카드-혜택-요약">3) 카드 혜택 요약</h3>
<p>Gemini 대신 정확도가 높은 GPT 모델을 사용하되, <strong>토큰이 부족한 문제</strong>를 해결하기 위해 크롤링한 결과를 <strong>요약</strong>하여 카드 추천을 진행하기로 하였다.</p>
<p>카드 혜택 요약 또한 GPT를 이용하였다. 처음 작성한 system은 다음과 같다.</p>
<pre><code>너는 크롤링한 카드의 혜택 정보을 분야별로 정리하는 데이터 추출 전문가야. 
데이터를 다음 키로 JSON 형식의 list로 제공해 줘: 혜택 분야-benefit_field, 혜택 정보-content. 

예를들어, ‘&lt;대중교통(버스, 지하철)&gt;    [대중교통(버스, 지하철)10% 청구할인] 대중교통(버스, 지하철)10% 청구할인 표의 칼럼은 
서비스 구분, 전월 이용실적 구간에 따른 할인률, 전월 이용실적 구간에 따른 월 할인한도, 1구간(40만원이상) 로 이루어져 있습니다.
표의 1번째 행은 대중교통(버스 지하철) 청구할인, 10%, 7천원 로 이루어져 있습니다.대상 : 버스, 지하철택시, 시외버스, 고속버스 제외
버스/지하철 요금할인은 실제 카드 사용일이 아닌 이용대금명세서상 기재된 이용일을 기준으로 서비스 제공
&lt;편의점(GS25, CU)&gt;[편의점(GS25, CU)10% 청구할인] 편의점(GS25, CU)10% 청구할인 표의 칼럼은 서비스 구분, 
전월 이용실적 구간에 따른 할인률, 전월 이용실적 구간에 따른 월 할인한도, 1구간(40만원이상) 로 이루어져 있습니다.
표의 1번째 행은 편의점(GS25 CU) 청구할인, 10%, 5천원 로 이루어져 있습니다.대상 : GS25, CU백화점, 대형쇼핑몰, 
역사(지하철, 철도, KTX 등) 내 입점한 가맹점의 경우, 할인대상에서 제외될 수 있음’은 
‘[{“benefit_field”: “대중교통(버스,지하철)”,“content”: “10%할인, 최대 7천원“},
{“benefit_field”: “편의점(GS25, CU)”,“content”: “10%할인”}]’로 요약해줘 </code></pre><p>하지만 여러 카드를 요약해 보니, 터무니없는 결과를 반환한 카드들이 있어 분석해 보았다.</p>
<br>

<p>아래 사진처럼, 카드의 혜택 정보에 <a href="https://card.kbcard.com/CRD/DVIEW/HCAMCXPRICAC0076?mainCC=a&amp;cooperationcode=09321">표가 존재하는 경우</a>가 있다.
<img src="https://velog.velcdn.com/images/julia_heo/post/13e97211-0b21-42dd-9c34-b5a42ec8529f/image.png" width=500>크롤링 할 때, table은 아래의 코드를 통해 표임을 알리고, &quot;표의 칼럼은 ~ 로 이루어져 있습니다.&quot; &quot;행은 ~ 로 이루어져 있습니다&quot;등의 문구를 붙여 표를 기술하였다.</p>
<pre><code class="language-python">def findtableortext(str): # 테이블 추출
    try:
        thead = str.find(&#39;thead&#39;).findAll(&#39;th&#39;)
        sentence = &quot; 표의 칼럼은 &quot;

        for columnname in thead:
            columnname=columnname.text
            sentence+=columnname.replace(&#39;\n&#39;,&#39;&#39;)+&quot;, &quot;
        sentence=sentence[0:-2]
        sentence += &quot; 로 이루어져 있습니다.&quot;
        j=1
        tbodies = str.find(&#39;tbody&#39;).findAll(&#39;tr&#39;)

        for tbody in tbodies:

            tds = tbody.findAll(&#39;td&#39;)
            sentence += f&quot;표의 {j}번째 행은 &quot;

            for td in tds:
                sentence+=td.text.replace(&quot;,&quot;,&quot;&quot;).replace(&#39;\n&#39;,&#39;&#39;)+&quot;, &quot;
            sentence=sentence[0:-2]
            sentence += &quot; 로 이루어져 있습니다.&quot;
            j+=1

        return sentence
    except:
        sentence=str.text.replace(&#39;\n&#39;, &#39;&#39;)
    return sentence</code></pre>
<p>하지만 해당 표는 셀들이 병합되어 있기 때문에, html을 한줄씩 읽었을 때, 병합된 칸은 한번만 읽히고, 제대로 데이터를 수집할 수 없다.</p>
<p>따라서 다음과 같은 요약 결과가 생성된다.
<img src="https://velog.velcdn.com/images/julia_heo/post/b2cade47-ba0c-4797-b4b2-5175f55a3cbe/image.png" width=700>셀을 병합한 경우를 모두 처리하는 코드를 작성하는 것은 불가능하고, 병합되지 않은 경우에도 gpt가 말로 표현된 표를 잘 이해하지 못하고 있기 때문에, <strong>table은 html을 그대로</strong> 전체 혜택에 포함하도록 크롤링 코드를 수정하였다.
크롤링한 결과는 다음과 같다.</p>
<pre><code>[  청구 할인 서비스 ] &lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tblH mT20&quot;&gt; 
&lt;caption&gt;청구 할인 서비스&lt;/caption&gt; &lt;thead&gt; &lt;tr&gt; &lt;th scope=&quot;col&quot;&gt;구분&lt;/th&gt; &lt;th colspan=&quot;2&quot; scope=&quot;col&quot;&gt;세부 영역&lt;/th&gt; 
&lt;th scope=&quot;col&quot;&gt;할인율&lt;/th&gt; &lt;th scope=&quot;col&quot;&gt;월 할인한도&lt;/th&gt; &lt;th scope=&quot;col&quot;&gt;전월 실적&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; 
&lt;tbody&gt; &lt;tr&gt; &lt;td&gt;대중교통 할인&lt;/td&gt; &lt;td colspan=&quot;2&quot;&gt;버스, 지하철&lt;/td&gt; &lt;td&gt;10%&lt;/td&gt; 

...생략...

합 계&lt;/td&gt; &lt;td&gt;1만5천원&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; 
대중교통 : 택시, 시외버스, 고속버스, 공항버스 제외* 버스/지하철 요금할인은 실제 카드 사용일이 아닌 이용대금명세서 상 기재된 
이용일을 기준으로 서비스 제공 실적 유예 제공 : 최초 카드 사용 등록 후 다음 달 말일까지 전월 실적 조건 미달 시에도 서비스 제공 </code></pre><p>이를 요약하면 이러한 결과가 나온다. 이전과는 다르게, 셀이 병합된 커피·약국·편의점·영화 모두 혜택 정보가 잘 담겨있는 모습이다.
<img src="https://velog.velcdn.com/images/julia_heo/post/477cf175-5992-420f-981e-52861adc4847/image.png" width=700></p>
<p>이에 따라 예시로 표가 들어있던 system 또한 변경하였다. GPT가 요약은 잘 하기에, input예시는 제거하고 output의 예시를 들어 형식을 고정시키기 위해 노력하였다.</p>
<pre><code>입력된 데이터를 key를 가지는 list로 요약하여 제공해 줘 [benefit_field, content]\\
nbenefit_field는 혜택의 분야, content는 혜택 할인율 정보를 핵심만 나타냄.\\
혜택과 관련없는 내용은 지우고, 혜택 분야는 최대한 세분화 할 것\\ 
output 형식의 예시는 다음과 같음     
[{\n  \&quot;benefit_field\&quot;: \&quot;편의점\&quot;,\n  \&quot;content\&quot;: \&quot;4대 편의점 이용 시 10% 적립\&quot;\n },\n
{\n  \&quot;benefit_field\&quot;: \&quot;커피 업종\&quot;,\n  \&quot;content\&quot;: \&quot;10% 적립\&quot;\n },\n 
{\n  \&quot;benefit_field\&quot;: \&quot;해외 이용금액\&quot;,\n  \&quot;content\&quot;: \&quot;1% 적립\&quot;\n },\n   
{\n  \&quot;benefit_field\&quot;: \&quot;디지털 구독(넷플릭스, 쿠팡플레이)\&quot;,\n  \&quot;content\&quot;: \&quot;10% 적립\&quot;\n},\n} ]</code></pre><p>예시를 구체적으로 명시하고 출력 형식을 json으로 지정하니, 카드 혜택 정보가 정형화되고 단순한 혜택 정보를 포함하게 되었다.</p>
<h3 id="4-입·출력-형식-json">4) 입·출력 형식 JSON</h3>
<p>3월 한달에 걸쳐 카드 혜택 정보 요약의 구현을 완료한 후, 다시 카드 추천 구현을 시작하였다.</p>
<ul>
<li><p>첫번째 시도
요약 후 첫번째로 test한 system은 다음과 같다.</p>
<pre><code>결제처, 결제금액, 카드들의 혜택 정보를 Input으로 하여 결제처에서 최적의 혜택을 누릴 수 있는 카드 번호, 혜택 정보, 할인 금액을 알려주어야 함.\n카드들의 혜택 정보에서 각 카드는 ‘/////’ 로 구분되고, 각 카드가 입력된 결제처에 해당되는 혜택을 가지고 있다면 할인 금액을 계산하고, 여러 카드 중 가장 할인 금액이 큰 카드의 고유번호 숫자, 결제처에 해당되는 혜택 정보 요약 텍스트(특수문자 없어야 함), 해당 혜택 적용 시 받게되는 할인 금액 숫자를 쉼표로 구분하여 제공해야 함. \n </code></pre><p>user에 넣는 카드 혜택 정보는 이전과 다르게 요약한 혜택 정보로, 요약된 내용을 ‘/////’로 구분하여 입력하였다.
그 결과, 결제처에 따른 카드 추천 결과가 예상되로 출력되었으며, 전보다 높은 정확도를 확인할 수 있었다.
<img src="https://velog.velcdn.com/images/julia_heo/post/ecb28f40-d46b-4fef-8159-a03f057e8864/image.png" alt=""></p>
</li>
<li><p>JSON 형식 도입
카드 혜택을 요약하는 과정에서 JSON같은 key, value 형식을 지정하므로써 출력 형태를 고정하고 원하는 결과를 얻는데 도움이 된다는 교수님의 조언을 받았었다. 이는 카드 혜택 요약 과정에서 큰 도움이 되었다.<br>
카드 추천 또한 <strong>JSON 형식</strong>을 사용하면 더 높은 정확도와 정형화된 형식을 얻을 수 있겠다는 생각이 들었고, 바로 적용해 보았다. (왜 진작에 json을 사용하지 않았나 아쉽기도 했다:)))<br>
반환값의 json 형식을 지정한 system은 다음과 같다.</p>
<pre><code>결제처, 결제 금액, 카드들의 혜택정보를 Input으로 하여, 결제처에서 최적의 혜택을 누릴 수 있는 카드를 ”카드번호”, 
“혜택 정보”, “할인 금액”을 키로 가지는 json형식으로 반환. output에 ``` 붙이지 말 것.\
각 카드가 입력된 결제처에 해당되는 혜택을 가지고 있다면 할인 금액을 계산하고, 여러 카드 중 가장 할인 금액이 큰 카드를 찾아낼 것\
”카드번호” 는 해당 카드의 &#39;카드 고유 번호&#39;,   “혜택 정보”는 결제처에 해당되는 혜택 정보 요약 텍스트(특수문자 없이 20자 이내)</code></pre><p>결제처와 결제금액, 카드들의 혜택 정보 또한 JSON으로 제공하였다. 기존에 각 카드들의 혜택정보를 독립적으로 이해하지 못하는 경우에 대해, Json의 list로 카드들을 분리하는 것이 큰 도움이 될 것이라 예상되었다.<br><br>
GPT에 전송되는 입력인 user 프롬프트는 다음과 같다.</p>
<pre><code class="language-json">{
  ”결제 금액”: 20000, 
  “결제처”: “CU”, 
  “카드들의 혜택 정보”:[
      {
          ”이름”: “다담카드(비 OTP)”, 
          “카드 고유 번호” : 696, 
          “혜택”: “...생략...”
      }, 
      {
          ”이름”: “My WE:SH 카드”, 
          “카드 고유 번호” : 649 ,  
          “혜택”: “...생략...”
   ]
}</code></pre>
<p>요청을 보낸 결과, json으로 정형화된 형식과 훨씬 정확한 카드 추천 결과가 반환되었다.
<img src="https://velog.velcdn.com/images/julia_heo/post/2b0cf556-b57d-47a8-aa84-b6b64be2b9e0/image.png" alt=""></p>
</li>
</ul>
<h3 id="5-파인튜닝">5) 파인튜닝</h3>
<p>마지막으로, 카드 추천이라는 downstream task에 적합한 모델을 생성하기 위하여 GPT-3.5 Turbo 모델의 파인튜닝을 진행했다.</p>
<ul>
<li><p><strong>데이터셋 구축</strong><br>
총 100개의 데이터셋 중 training dataset, validation dataset, test dataset을 각각 <strong>6:2:2의 비율</strong>로 나누어 사용했다. 데이터가 충분하지 않은 경우, 주로 이러한 비율을 선택한다.<br>
정확도가 높은 파인튜닝 모델을 위해 특정 결제처와 관련된 혜택을 가진 카드들을 비교하여, 가장 많은 할인을 제공하는 카드를 직접 정답으로 선택하는 방식으로 데이터셋을 생성했다.<br>
1인당 신용카드 보유 수는 평균 4.4장이므로, 각 데이터마다 <strong>3-5장의 카드</strong>를 가지도록 데이터셋을 구축했다. 이때 실제 카드 사용자들이 많을 것으로 예상되는 6개 카드사의 인기 카드의 비중을 높게 책정했다.<br>
아래의 이미지는 직접 구축한 데이터셋의 일부이며, 최종 발표 전까지 카드 추천 모델의 정확도 향상을 위해 더 많은 데이터셋을 구축할 계획이다.</p>
<img src="https://velog.velcdn.com/images/julia_heo/post/4590a7e2-e781-4153-a8aa-ad0e4e0b8c0e/image.png" width=700></li>
<li><p><strong>파인튜닝</strong>
위에서 생성한 training dateset과 validation dataset을 이용해 모델 학습을 위한 json 파일을 생성했다.<img src="https://velog.velcdn.com/images/julia_heo/post/a4469580-3ba7-4a7d-a1e3-57d4d8d893f9/image.png" width=700>해당 프롬프트 string를 생성하기 위해, 임의의 api를 만들어 큰 도움을 받았다. 결제처, 결제금액, 사용할 카드들과 함께 예상 답변인 카드 id와 혜택 정보, 할인 금액을 입력하면 이를 프롬프트 형식에 넣어서 반환하게 하였다.<img src="https://velog.velcdn.com/images/julia_heo/post/75148e94-3dab-4b38-8cae-edc8cd842c8e/image.png" width=700>구체적인 파인튜닝 구현 코드는 <a href="https://velog.io/@julia_heo/ONCE#5-gpt-%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D">아래</a>에서 설명하겠다.</p>
</li>
<li><p><strong>파인튜닝 결과 확인</strong>
OpenAI API 사이트의 Playground에서 파인튜닝한 모델의 id를 선택하여, 해당 모델을 테스트해 볼 수 있다. 
<br>다음 이미지는 앞서 생성한 파인튜닝 모델에 ‘이마트24’에서 최대 할인을 받을 수 있는 카드 추천을 요청한 결과이다.<img src="https://velog.velcdn.com/images/julia_heo/post/6c6c3495-2d5b-4660-84fb-c9ee2afd0542/image.png" alt="">보유 중인 ‘신한카드 SOL 트래블 체크’, ‘#MY WAY(샵 마이웨이) 카드’, ‘LIKIT all 체크카드’ 중에 서 ‘이마트24’와 관련된 혜택을 찾은 후, ‘#MY WAY(샵 마이웨이) 카드’를 사용하면 가장 많은 할인을 받을 수 있다는 결과가 반환되었다.</p>
</li>
<li><p><strong>파인튜닝 성능 측정</strong>
구체적으로 20개의 test dataset에 대하여, 파인튜닝된 모델과 파인튜닝 없이 gpt-3.5-turbo-
0125 모델에 프롬프트로 요청을 보내 테스트한 결과를 비교해 보았다.
아래 표에서 빨간색 배경으로 표시한 부분이 잘못된 결과를 응답한 경우를 나타낸다.
<img src="https://velog.velcdn.com/images/julia_heo/post/a0bd9168-4055-42d7-85d3-02309bdf96b1/image.png" alt=""></p>
</li>
</ul>
<p>결과를 분석해 보면, 파인튜닝된 모델은 20개의 test dataset에 대하여 예상과 다른 결과가 3번 출력되었다. 반면, 프롬프트의 경우, 11번의 오류가 발생하였다.</p>
<p>즉, 파인튜닝 없이 프롬프트만을 사용할 경우, 요청을 보낼 때마다 반환되는 결과가 달라지며 모든 카드의 혜택을 정확히 이해하지 못하여 할인 금액이 가장 크지 않은 카드를 추천하는 등 잘못된 응답 결과가 발생하였다. 특히, 대중교통과 관련된 키워드를 입력할 경우에 ‘지하철’, ‘버스’ 등의 단어들을 교통 카테고리와 연관짓지 못하여 전혀 다른 분야의 혜택을 반환하는 것을 발견했다. 또한, 해당 카드가 가지지 않은 혜택 정보를 출력하거나, 올바른 혜택을 찾아냈음에도 할인 금액을 잘못 계산하는 오류도 발견되었다.</p>
<p>즉, 파인튜닝된 모델을 사용할 경우에 카드 추천 결과의 정확도가 높다는 것이 검증되었다.
<br>
따라서 Once의 주요 기능을 개발하기 위해 파인튜닝된 GPT-3.5 Turbo 모델을 최종 선택해 기술을 구현했다.</p>
<h1 id="프로젝트-구현-개요">프로젝트 구현 개요</h1>
<p>위의 과정을 거쳐 선택된 카드 추천 모델을 구현하기 위한 구체적인 기술과 코드들에 대해 설명하겠다.</p>
<ol>
<li>카드 혜택 정보 크롤링</li>
<li>크롤링 자동화</li>
<li>카드 혜택 정보 요약</li>
<li>크롤링 자동화</li>
<li>카드 추천 데이터셋 생성</li>
<li>GPT 파인튜닝</li>
<li>Spring Boot에서 GPT 호출</li>
</ol>
<h1 id="1-카드-혜택-정보-크롤링">1. 카드 혜택 정보 크롤링</h1>
<h3 id="크롤링이란">크롤링이란?</h3>
<p>Web상에 존재하는 Contents를 수집 하는 작업이다. (프로그래밍으로 자동화 가능)</p>
<h3 id="원스의-크롤링">원스의 크롤링</h3>
<p>원스에서는 카드들의 혜택 정보를 직접 크롤링해, 정확한 <strong>최신 버전의 카드 혜택</strong>을 가져온다.<br>
우선적으로 국민 카드, 현대 카드, 삼성 카드, 신한 카드, 롯데 카드, 하나 카드의 카드들을 활용하며, 추후 서비스를 발전시키며 이외의 카드사들도 연결할 예정이다.<br></p>
<p>6개 카드사의 혜택 정보를 3명의 팀원이 2개씩 나눠 크롤링을 진행했으며, 나는 <strong>국민 카드</strong>와 <strong>롯데 카드</strong>의 크롤링을 담당하였다.</p>
<p>html의 구조를 파악하여 원하는 값을 추출하는 과정은 <a href="https://velog.io/@julia_heo/ONCE#%EC%B9%B4%EB%93%9C-%ED%98%9C%ED%83%9D-%EC%A0%95%EB%B3%B4-%ED%81%AC%EB%A1%A4%EB%A7%81">이전 포스트</a>에 있어 생략하고, 추가되거나 발전된 내용을 소개하겠다.</p>
<h3 id="국민-카드-혜택-정보-크롤링">국민 카드 혜택 정보 크롤링</h3>
<h4 id="csv로-데이터-저장">csv로 데이터 저장</h4>
<p>카드들의 정보는 csv로 저장한 뒤 db에 접속해 한번에 저장한다.</p>
<p>csv의 열이 될 정보들의 리스트를 선언한다</p>
<pre><code class="language-python">name = []
img_url = []
benefits = []
created_at = []</code></pre>
<p>국민카드의 경우 전체 카드를 카테고리에 따라 분류해 제공하는데, 카테고리마다 중복되는 카드들이 존재한다. 이는 name 리스트 안의 중복여부에 따라 크롤링 진행 여부를 결정하여 해결한다.
<img src="https://velog.velcdn.com/images/julia_heo/post/40aaf9ed-8f29-4fae-b1f7-707661ac8fc2/image.png" width=400></p>
<pre><code class="language-python">        cardName=card_bs.find(&#39;h1&#39;,{&#39;class&#39;,&#39;tit&#39;}).text      # 카드 이름 추출

        if cardName not in name:     # 카드 존재 여부 확인 
            name.append(cardName)
            print(datetime.now().strftime(&quot;%Y-%m-%d %H:%M:%S&quot;)+&quot; [&quot;+cardName+&quot;] --- 웹 페이지에 접속 중... &quot;)
        else:                         # 이미 존재하는 경우 continue(크롤링 진행하지 않음)
            print(datetime.now().strftime(&quot;%Y-%m-%d %H:%M:%S&quot;)+&quot; [&quot;+cardName+&quot;] --- 이미 존재하는 카드입니다 &quot;)
            continue</code></pre>
<p>카드별로 크롤링 완료할때마다 위에 정의한 list에 append한다.</p>
<p>모든 카드들의 크롤링이 완료되면, 데이터들을 모아 csv로 저장한다.</p>
<pre><code class="language-python">data = {&quot;card_company_id&quot;:card_company_id, &quot;name&quot; : name, &quot;img_url&quot; : img_url, &quot;benefits&quot;: benefits, &quot;created_at&quot;: created_at,&quot;type&quot;:type}
df = pd.DataFrame(data)

df.to_csv(&quot;./credit_benefit.csv&quot;, encoding = &quot;utf-8-sig&quot;, index=False)</code></pre>
<p>저장된 csv는 다음과 같다.
<img src="https://velog.velcdn.com/images/julia_heo/post/21c85289-96c6-43d2-ab42-cfc2230b948e/image.png" width=400></p>
<h3 id="롯데-카드-혜택-정보-크롤링">롯데 카드 혜택 정보 크롤링</h3>
<h4 id="s3-이미지-저장">S3 이미지 저장</h4>
<p>다른 카드 사이트의 경우 일관되게 가로로 누운 카드 모양이거나, 세로로 긴 카드 모양을 가지고 있어 카드사 종류에 따라 처리하면 되었다.</p>
<p>하지만 롯데 카드 사이트는 카드들 이미지의 가로/세로 비율이 무작위로 가로되어있다는 문제점이 존재했다.</p>
<p>이에 따라 크롤링 과정에서 이미지가 세로인 경우에만 aws S3에 변환해 저장한 뒤 해당 url을 카드 이미지로 사용하였다.</p>
<ul>
<li><p>S3 연결</p>
<pre><code class="language-python">config = configparser.ConfigParser()
config.read(&#39;/crawling/config.ini&#39;)
AWS_S3_ACCESSKEY = config[&#39;s3&#39;][&#39;AWS_S3_ACCESSKEY&#39;]
AWS_S3_SECRETKEY = config[&#39;s3&#39;][&#39;AWS_S3_SECRETKEY&#39;]
AWS_S3_BUCKET = config[&#39;s3&#39;][&#39;AWS_S3_BUCKET&#39;]
AWS_S3_REGION = config[&#39;s3&#39;][&#39;AWS_S3_REGION&#39;]</code></pre>
<p>S3의 ACCESSKEY, SECRETKEY등의 정보는 인터넷 상에 공개되어서는 안되기 때문에, <code>config.ini</code>파일로 분리하였다.</p>
<pre><code class="language-python">def s3_connection():
  try:
      s3 = boto3.client(
          service_name=&quot;s3&quot;,
          region_name=&quot;ap-northeast-2&quot;,
          aws_access_key_id=&quot;{AWS_S3_ACCESSKEY}&quot;,
          aws_secret_access_key=&quot;{AWS_S3_SECRETKEY}&quot;,
      )
  except Exception as e:
      print(e)
  else:
      print(&quot;s3 연결 성공&quot;)
      return s3</code></pre>
<ul>
<li>이미지 저장 함수 호출
크롤링을 통해 얻은 카드 이미지의 url을 매개변수로 넣어 s3_put_object함수를 호출하였다.<pre><code class="language-python">cardImg= &quot;https:&quot; + card.find(&#39;img&#39;).get(&#39;src&#39;)
img_url.append(s3_put_object(cardImg,cardNo))</code></pre>
</li>
<li>s3_put_object 함수
받은 이미지 url을 직접 이미지 객체로 변환한 뒤, 이미지의 가로폭이 세로폭보다 짧은 경우에만 90° 회전한 뒤 바이너리 스트림에 저장하였다.이미지를 AWS S3에 업로드한 뒤, 해당 url을 반환하였다.
이미지가 가로로 되어있다면, 그대로 받은 이미지 url을 반환하였다.<pre><code class="language-python">from io import BytesIO
from PIL import Image
import boto3
</code></pre>
</li>
</ul>
<p>def s3_put_object(cardImg,cardNo):
  try:</p>
<pre><code>  data = urlopen(cardImg).read()
  img = Image.open(BytesIO(data))
  w, h = img.size
  if w &lt; h : # 이미지가 세로인 경우
      img = img.rotate(90, expand=True)            # 회전

      image_fileobj = BytesIO()
      img.save(image_fileobj, format=&#39;PNG&#39;)
      image_fileobj.seek(0)

      # S3에 업로드
      s3.upload_fileobj(image_fileobj, &quot;{AWS_S3_BUCKET}&quot;, &quot;lottecard/&quot;+cardNo+&quot;.png&quot;,ExtraArgs={&quot;ContentType&quot;: &quot;image/jpg&quot;, &quot;ACL&quot;: &quot;public-read&quot;})
      return &quot;https://{AWS_S3_BUCKET}.s3.{AWS_S3_REGION}.amazonaws.com/lottecard/&quot;+cardNo+&quot;.png&quot;
  else:
      return cardImg</code></pre><p>  except Exception as e:</p>
<pre><code>  return False</code></pre><pre><code></code></pre></li>
</ul>
<h3 id="csv데이터-rds에-업로드">csv데이터 RDS에 업로드</h3>
<p>csv에 저장된 데이터는 일괄적으로 RDS에 업로드하였다. 코드의 재사용성을 높이기 위해, 카드 회사 이름과 신용카드/체크카드를 명령줄인수로 받아 사용하였다. 즉, 서로 다른 코드로 모인 각 카드사의 카드 혜택 정보는 모두 이 코드를 통해 rds에 저장된다.</p>
<ul>
<li><p>rds 연결</p>
<pre><code class="language-python">config = configparser.ConfigParser()
config.read(&#39;./crawling/config.ini&#39;)

db_host = config[&#39;database&#39;][&#39;host&#39;]
db_user = config[&#39;database&#39;][&#39;user&#39;]
db_password = config[&#39;database&#39;][&#39;password&#39;]
db_database = config[&#39;database&#39;][&#39;database&#39;]
db_charset = config[&#39;database&#39;][&#39;charset&#39;]</code></pre>
<p>rds또한 s3와 마찬가지로, <code>config.ini</code>파일을 통해 계정정보를 보호한다.</p>
<pre><code class="language-python">connection = pymysql.connect(
  host=db_host,
  user=db_user,
  password=db_password,
  database=db_database,
  charset=db_charset,
  cursorclass=pymysql.cursors.DictCursor
)</code></pre>
</li>
<li><p>csv 읽기</p>
<pre><code class="language-python">with open(f&#39;./{csv_file}_benefit.csv&#39;, &#39;r&#39;, encoding=&#39;utf-8&#39;) as csvfile:
   csvreader = csv.reader(csvfile)
   next(csvreader)
   for row in csvreader:
  card_company_id, name, img_url, benefits, created_at, type = row</code></pre>
<p>  for문으로 모든 row에 접근중이며, card_company_id, name, img_url, benefits, created_at, type변수에 현재 row의 값들이 들어있다.</p>
</li>
<li><p>rds에 삽입</p>
<pre><code class="language-python">insert_query = &quot;&quot;&quot;
  INSERT INTO card
  (card_company_id, name, img_url, benefits, created_at, type)
  VALUES (%s, %s, %s, %s, %s, %s)
  &quot;&quot;&quot;
  cursor.execute(insert_query, (card_company_id, name, img_url, benefits, created_at, type))</code></pre>
<p>원하는 동작의 mysql문을 작성한 뒤, <code>cursor.execute()</code>로 실행할 수 있다.</p>
</li>
</ul>
<br>


<p>크롤링 <a href="https://github.com/EWHA-LUX/ONCE-BE/tree/develop/src/main/resources/crawling">최종 코드</a>는 깃허브에서 확인 가능하다.</p>
<h1 id="2-크롤링-자동화">2. 크롤링 자동화</h1>
<p>카드들의 부가서비스는 간혹 변경되기도 하고, 카드 자체가 단종되기도 한다.
이러한 데이터의 변화를 원스 서비스의 데이터 베이스에 즉각적으로 반영하는것이 중요하다.</p>
<p>따라서 <strong>주기적으로 카드 혜택 업데이트</strong>를 진행한다.</p>
<p>각 카드들의 혜택 정보 이외에 카드사별 이벤트성 혜택 또한 크롤링 하여 사용한다. 
카드의 혜택 정보는 주 1회, 이벤트성 혜택은 매일 업데이트를 진행한다.</p>
<p>가장 핵심인 카드 혜택 업데이트를 설명하겠다.</p>
<h3 id="스케쥴링">스케쥴링</h3>
<p>Spring Boot에서는 <strong>@Scheduled</strong> 어노테이션을 이용하여 스케쥴러를 구현할 수 있다.</p>
<p>먼저, @Scheduled를 사용하기 위해 Application 클래스에 <strong>@EnableScheduling</strong>을 추가해준다.</p>
<pre><code class="language-java">@SpringBootApplication
@EnableScheduling
public class OnceApplication {

    public static void main(String[] args) {
        SpringApplication.run(OnceApplication.class, args);
    }

}</code></pre>
<p>스케쥴러는 스프링 빈에 등록되어야 한다. @Service 애노테이션을 이용해서 빈에 등록하였다.</p>
<pre><code class="language-java">@Service
@Slf4j
@RequiredArgsConstructor
public class CrawlingService {

    // 매주 월요일 00:00 카드 혜택 크롤링
    @Scheduled(cron = &quot;0 0 0 ? * 1&quot;)
    public void cardCrawling() throws CustomException {
        String[] cardCompanyList = {&quot;Kookmin&quot;, &quot;Hana&quot;, &quot;Samsung&quot;, &quot;Shinhan&quot;, &quot;Lotte&quot;, &quot;Hyundai&quot;};
        for (String cardCompany : cardCompanyList){
            crawling(cardCompany);
        }
    }
   }</code></pre>
<p>@Scheduled 속성에는 크게 fixedDelay, fixedRate, initDelay, cron 등이 있는데, 
난 Scheduling의 정규 표현식인 cron 표현식을 사용하였다.</p>
<p><strong>cron 표현식</strong>
cron은 아래와 같이 총 6개의 필드로 구성된다.
<img src="https://velog.velcdn.com/images/julia_heo/post/6678c59a-22c0-48eb-adba-68c8329736b4/image.png" width=700></p>
<table>
<thead>
<tr>
<th>필드 명</th>
<th>값의 허용 범위</th>
<th>허용된 특수문자</th>
</tr>
</thead>
<tbody><tr>
<td>초 (Seconds)</td>
<td>0 ~ 59</td>
<td>, - * /</td>
</tr>
<tr>
<td>분 (Minutes)</td>
<td>0 ~ 59</td>
<td>, - * /</td>
</tr>
<tr>
<td>시 (Hours)</td>
<td>0 ~ 23</td>
<td>, - * /</td>
</tr>
<tr>
<td>일 (Day)</td>
<td>1 ~ 31</td>
<td>, - * ? / L W</td>
</tr>
<tr>
<td>월 (Month)</td>
<td>1 ~ 12 or JAN ~ DEC</td>
<td>, - * /</td>
</tr>
<tr>
<td>요일(Week)</td>
<td>0 ~ 6 or SUN ~ SAT</td>
<td>, - * ? / L #</td>
</tr>
</tbody></table>
<p>특수 문자</p>
<table>
<thead>
<tr>
<th>특수 문자</th>
<th>설명</th>
<th>예제</th>
</tr>
</thead>
<tbody><tr>
<td>*</td>
<td>모든 값 의미</td>
<td></td>
</tr>
<tr>
<td>?</td>
<td>특정한 값이 없음을 의미</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>범위를 나타낼 때 사용</td>
<td>월요일부터 수요일까지 -&gt; MON-WED</td>
</tr>
<tr>
<td>,</td>
<td>특정 값을 여러 개 나열할 때 사용</td>
<td>월,수,금 -&gt; MON,WED,FRI</td>
</tr>
<tr>
<td>/</td>
<td>시작 시간 / 단위</td>
<td>0분부터 매 5분 -&gt; 0/5</td>
</tr>
<tr>
<td>L</td>
<td>일에서 사용하면 마지막 일,<br>요일에서 사용하면 마지막 요일(토요일)</td>
<td></td>
</tr>
<tr>
<td>W</td>
<td>가장 가까운 평일</td>
<td>15W -&gt; 15일에서 가장 가까운 평일</td>
</tr>
<tr>
<td>#</td>
<td>몇째 주의 무슨 요일을 표현</td>
<td>3#2 -&gt; 2번째주 수요일</td>
</tr>
</tbody></table>
<br>
cron표현식에 따라, 매주 월요일 00:00 cardCrawling()함수가 실행된다.
cardCrawling함수에서는 6개 카드사들 차례대로 crawling()함수를 실행한다

<pre><code class="language-java">    private static void crawling(String cardCompany) throws CustomException{
        LOG.info(cardCompany+&quot; 크롤링 시작&quot;);
        executeFile(cardCompany+&quot;/credit.py&quot;);
        executeInsertData(cardCompany,&quot;Credit&quot;);
        executeFile(cardCompany+&quot;/debit.py&quot;);
        executeInsertData(cardCompany,&quot;Debit&quot;);
    }</code></pre>
<p>crawling함수에서는 크롤링 시작을 알리는 로그를 찍은 뒤, 
<code>신용카드 크롤링 코드 실행-&gt;DB에 반영-&gt;체크카드 크롤링 코드 실행-&gt;DB에 반영</code>의 과정을 거친다.</p>
<h3 id="spring-boot에서-python-실행">Spring Boot에서 python 실행</h3>
<p>스프링 부트는 Java 프레임워크이기 때문에, python을 실행하기 위해 몇가지 설정이 필요하다.</p>
<p>우선, 크롤링 코드(python)는 <code>src/main/resources/crawling/{카드사 이름}/</code>경로 안에 위치한다.
<img src="https://velog.velcdn.com/images/julia_heo/post/ca1a08dc-34a4-4434-a37d-c383eabda70d/image.png" width=200></p>
<p><strong>1) 도커 설정</strong></p>
<p>스프링부트로 구현된 백엔드 서버는 도커를 통해 컨테이너화하고, 깃허브 액션을 통해 CI/CD 파이프라인을 구축하여 배포를 자동화하고 있다.
즉, EC2 서버에 SSH로 접속했을 때 애플리케이션 파일이 서버 파일 시스템에 직접 보이지 않고 도커 컨테이너 내부에 애플리케이션 파일이 존재한다.</p>
<p>스프링 부트 JAR 파일 내에서 Python 파일을 직접 실행하는 것은 지원이 되지 않기 때문에, jar파일이 아닌 도커 환경에서 python을 실행해야 한다. 이를 위해 python 관련 라이브러리 설치, 크롤링 코드 도커 안에 복사의 과정이 필요하다.</p>
<p>이 작업은 <a href="https://github.com/EWHA-LUX/ONCE-BE/blob/develop/Dockerfile">도커파일</a>에서 진행된다. python 실행과 관련된 코드들만 가져와 설명하겠다.</p>
<pre><code>RUN apt-get update &amp;&amp; apt-get install -y python3 python3-pip wget unzip curl</code></pre><p>파이썬을 설치한다.</p>
<pre><code>RUN wget https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_114.0.5735.198-1_amd64.deb &amp;&amp; \
    apt -y install ./google-chrome-stable_114.0.5735.198-1_amd64.deb

RUN wget -O /tmp/chromedriver.zip https://chromedriver.storage.googleapis.com/114.0.5735.90/chromedriver_linux64.zip &amp;&amp; \
     unzip /tmp/chromedriver.zip -d /usr/bin &amp;&amp; \
     chmod +x /usr/bin/chromedriver</code></pre><p>동적 크롤링을 위해 크롬을 설치해야 한다. 
chromedriver과 google-chrome 모두 다운로드 해준다.</p>
<pre><code>COPY ./requirements.txt .
RUN pip install --no-cache-dir --upgrade pip &amp;&amp; \
    pip install -r requirements.txt</code></pre><p>크롤링을 위해 필요한 python의 라이브러리가 정리된 requirements.txt파일을 가져와 모두 install 해준다.</p>
<pre><code>COPY ./src/main/resources/crawling /crawling</code></pre><p>깃허브의 크롤링 코드들을 도커 안에 그대로 copy해준다.</p>
<p>이제 도커 안에서 python 파일을 실행할 준비가 완료됐다.<br><br></p>
<p><strong>2) 크롤링 코드 수정</strong></p>
<p>크롤링 코드들은 로컬에서 돌릴때와는 파일의 위치가 달라졌다. 이에 따라 python 코드 안에서 csv를 저장하고, 가져오기 위해 명시된 경로 또한 변경해 주어야 한다
<code>./credit_benefit.csv</code> -&gt; <code>/app/src/main/resources/crawling/Kookmin/credit_benefit.csv</code>
그냥 바로 가져다 쓰던 파일들 앞에 모두 <code>/app/src/main/resources/crawling/</code>라는 도커 안에서의 절대 경로를 적어준다.</p>
<p>웹 드라이버를 사용하기 위해 로컬에서는 다음과 같이 코드를 작성하였다.</p>
<pre><code class="language-python">chrome_options = Options()
chrome_options.add_argument(&#39;--headless&#39;)
chrome_options.add_argument(&#39;--disable-web-security&#39;)

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)</code></pre>
<p>하지만 도커에서 돌리기 위해서는 다음과 같이 option을 추가하고, Service를 위해 실행 경로를 명시해 주어야 한다.</p>
<pre><code class="language-python">chrome_options = Options()
chrome_options.add_argument(&#39;--headless&#39;)
chrome_options.add_argument(&#39;--no-sandbox&#39;)
chrome_options.add_argument(&quot;--disable-dev-shm-usage&quot;)

service = Service(executable_path=r&#39;/usr/bin/chromedriver&#39;)
driver = webdriver.Chrome(service=service,options=chrome_options)</code></pre>
<br>

<p><strong>3) spring boot에서 python 실행</strong></p>
<p>Java의 ProcessBuilder를 활용하면 다른 외부 프로세스를 실행시키거나 컨트롤 할 수 있다.</p>
<pre><code class="language-java">ProcessBuilder pb = new ProcessBuilder(&quot;python3&quot;, &quot;-u&quot;, &quot;/crawling/&quot;+path);
pb.redirectErrorStream(true);
Process p = pb.start();
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));

String line;
while ((line = br.readLine()) != null) {
    // 실행 결과 처리
    LOG.info(line);
}

p.waitFor();</code></pre>
<p>이 작업을 완료하면, 월요일 00:00이후 EC2 서버에 들어가 <code>sudo docker logs web</code>명령어를 입력해 다음과 같이 크롤링 자동화가 진행되고 있는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/julia_heo/post/c331caba-ea14-4e40-8339-f94e58c71bf1/image.png" alt=""></p>
<h3 id="트러블-슈팅">트러블 슈팅</h3>
<p>크롤링 자동화는 전체 개발 과정중에서도 가장 큰 이슈 중 하나였다.
로컬에서는 어렵지 않게 구현하였지만, 서버에서 실행하는 것은 차원이 다른 문제였다.
수많은 오류를 겪었지만 큰 에러들을 정리해보겠다</p>
<p>1) csv 파일 위치
서버에서 돌아갈 때 깃허브에 있는 파일이 자꾸 없다고 오류가 떠서 당황스러웠던 기억이 있다.
<code>docker exec -it web sh</code>명령어를 통해 직접 도커 컨테이너의 쉘로 접속하고,
<code>jar tf app.jar</code>로 파일이 존재하는지 확인했다.
결과적으로는 jar 안이 아닌 도커 컨테이너 안에서 python을 실행해 해결할 수 있었고, 그러기 위해 도커에 crawling파일들을 복사하였다.</p>
<p>2) webdriver
동적 크롤링인 국민 카드의 크롤링까진 돌아가는 것을 확인했는데, 웹드라이버를 사용하기 위해 수많은 수정과 커밋과 커밋의 reset과(^^) 시도를 했다.</p>
<ul>
<li><p><code>DevToolsActivePort file doesn&#39;t exist</code>
<code>chrome_options.add_argument(&#39;--headless&#39;)</code> <code>chrome_options.add_argument(&#39;--no-sandbox&#39;)</code> 옵션 추가</p>
</li>
<li><p><code>WebDriverError: unknown error: session deleted because of page crash</code>
<code>chrome_options.add_argument(&quot;--disable-dev-shm-usage&quot;)</code>옵션 추가</p>
</li>
<li><p><code>chrome not reachable</code> <code>selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 114</code>
chromedriver와 google-chrome의 버전이 맞지 않아 진행이 되지 않았다.
<code>chromedriver --version</code> <code>google-chrome --version</code> 명령어로 버전을 확인하였고, 안정적인 버전을 찾아 <code>114.0.5735</code> 버전으로 다운로드했다.</p>
</li>
<li><p><code>session not created</code>
<code>service = Service(executable_path=r&#39;/usr/bin/chromedriver&#39;)</code>로 웹 드라이버 절대경로로 호출</p>
</li>
</ul>
<p>3) python
모든 설정이 된 것 같은데, python이 실행되지 않았다.
<code>ProcessBuilder pb = new ProcessBuilder(&quot;python&quot;, &quot;-u&quot;, &quot;/crawling/&quot;+path);</code> 코드를 
<code>ProcessBuilder pb = new ProcessBuilder(&quot;python3&quot;, &quot;-u&quot;, &quot;/crawling/&quot;+path);</code>로 수정해 해결되었다. 
도커 이미지에서 Python 3이 설치된 경우, 인터프리터는 &quot;python3&quot;로 접근해야 할 수 있다.</p>
<h1 id="3-카드-혜택-정보-요약">3. 카드 혜택 정보 요약</h1>
<p>월요일 00:00에 카드 혜택 정보 업데이트를 진행한 뒤, 매주 월요일 6:00에 크롤링한 헤택 정보의 요약 작업이 진행된다.</p>
<p>해당 service코드는 다음과 같다.</p>
<pre><code class="language-java">    @Scheduled(cron = &quot;0 0 6 ? * 1&quot;)
    public void updateBenefitSummary() throws CustomException, JsonProcessingException {

        List&lt;Card&gt; cardList = cardRepository.findAll();

        int index = 1;
        for (Card card : cardList) {
            // 기존의 BenefitSummary 삭제
            List&lt;BenefitSummary&gt; existingSummaries = benefitSummaryRepository.findByCard(card);
            benefitSummaryRepository.deleteAll(existingSummaries);

            log.info(&quot;[&quot; + card.getName() + &quot;] - 카드 혜택 요약 중... (&quot; + index + &quot;/&quot; + cardList.size() + &quot;)&quot;);
            BenefitDto[] benefitJson = openaiService.gptBenefitSummary(card.getBenefits());

            for (BenefitDto benefit : benefitJson) {
                BenefitSummary benefitSummary = BenefitSummary.builder()
                        .benefitField(benefit.getBenefit_field())
                        .benefitContents(benefit.getContent())
                        .card(card)
                        .build();

                benefitSummaryRepository.save(benefitSummary);
            }
            index++;
        }
        log.info(&quot;전체 카드 혜택 요약 완료&quot;);
    }</code></pre>
<p>모든 카드들에 대해 for문을 돌며, 기존의 요약된 혜택들 삭제하고, openaiService의 gptBenefitSummary함수를 호출해 요약을 진행한 뒤 요약된 혜택을 BenefitSummary로 저장한다.</p>
<p>GPT와 연결하기 위한 openaiService는 다음과 같다.</p>
<pre><code class="language-java">@Service
@Slf4j
@RequiredArgsConstructor
public class OpenaiService {

    @Qualifier(&quot;openaiRestTemplate&quot;)
    @Autowired
    private RestTemplate restTemplate;

    @Value(&quot;${openai.model}&quot;)
    private String model;

    @Value(&quot;${openai.api.url}&quot;)
    private String apiUrl;
}</code></pre>
<p>test를 위해 gpt와 gemini모두 백엔드 서버와 연결되어 있어 여러 개의 RestTemplate 빈이 사용된다.
@Qualifier(&quot;openaiRestTemplate&quot;)를 사용하여 &quot;openaiRestTemplate&quot;이라는 이름의 빈을 주입한다.</p>
<p>RestTemplate은 Spring에서 HTTP 요청을 보내고 응답을 받는 데 사용되는 클래스이다.</p>
<p>openai에 사용할 model과 apiUrl는 환경변수로 설정하였다.</p>
<br>

<p>gptBenefitSummary함수는 다음과 같다. </p>
<pre><code class="language-java">    public BenefitDto[] gptBenefitSummary(String benefits) throws CustomException, JsonProcessingException {

        String prompt = &quot;입력된 데이터를 [] 사이에 주어진 key를 가지는 JSON 형식의 list로 요약하여 제공해 줘 [benefit_field, content]\\nbenefit_field는 혜택의 분야, content는 혜택 할인율 정보를 핵심만 나타냄. \\ output 형식은 다음과 같음.     [{\n     \&quot;benefit_field\&quot;: \&quot;편의점\&quot;,\n \&quot;content\&quot;: \&quot;4대 편의점 이용 시 10% 적립\&quot;\n&quot; +  },\n   {\n&quot;        \&quot;benefit_field\&quot;: \&quot;커피 업종\&quot;,\n&quot;   \&quot;content\&quot;: \&quot;커피 업종 이용 시 10% 적립\&quot;\n   },\n  {\n  \&quot;benefit_field\&quot;: \&quot;해외 이용금액\&quot;,\n\&quot;content\&quot;: \&quot;해외 이용금액 1% 적립\&quot;\n&quot;   },\n {\n  \&quot;benefit_field\&quot;: \&quot;디지털 구독\&quot;,\n  \&quot;content\&quot;: \&quot;디지털 구독 영역 이용 시 10% 적립\&quot;\n },\n {\n  \&quot;benefit_field\&quot;: \&quot;One Pick 쇼핑몰\&quot;,\n \&quot;content\&quot;: \&quot;One Pick 온라인 쇼핑몰 가맹점 최대 3천 포인트 적립\&quot;\n&quot;  } ]&quot;;

        // gpt 요청 보내는 부분
        OpenaiChatRequest request = new OpenaiChatRequest(model, prompt, benefits);
        OpenaiChatResponse response = restTemplate.postForObject(apiUrl, request, OpenaiChatResponse.class);

        if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) {
            throw new CustomException(ResponseCode.FAILED_TO_OPENAI);
        }

        String result = response.getChoices().get(0).getMessage().getContent();

        ObjectMapper objectMapper = new ObjectMapper();
        BenefitDto[] benefitJson = objectMapper.readValue(result, BenefitDto[].class);

        return benefitJson;
    }</code></pre>
<p>시스템 프롬프트에 해당하는 prompt와 유저 프롬프트에 해당하는 benefits, GPT 모델을 OpenaiChatRequest에 넣어 GPT에 전달할 요청 객체를 생성한다.</p>
<p>RestTemplate을 사용하여 GPT에 POST 요청을 보낸다. 이 요청은 apiUrl로 지정된 엔드포인트에 요청을 전송하고, 요청 본문으로는 OpenaiChatRequest 객체를 사용한다. 응답으로는 OpenaiChatResponse 객체가 반환된다.</p>
<p>요청에 대한 응답을 처리하고 예외가 발생하지 않았다면 결과를 가공하고, ObjectMapper를 사용하여 JSON 형식의 문자열을 BenefitDto 배열로 변환한다.</p>
<p>최종적으로 요약된 혜택 데이터를 나타내는 BenefitDto 배열을 반환한다.</p>
<h1 id="4-카드-추천-데이터셋-생성">4. 카드 추천 데이터셋 생성</h1>
<p>카드 추천은   <code>결제처</code>  / <code>결제금액</code> / <code>보유카드목록</code>을 입력으로 가지고 
<code>최대혜택카드id</code> / <code>해당혜택요약</code> / <code>할인금액</code>을 출력한다.</p>
<p>현재 데이터의 양과 기술로는 카드 추천 모델의 training을 위한 데이터를 직접 만드는 수밖에 없다.</p>
<p>각 카드사의 인기 카드를 중심으로 데이터 베이스에서 요약된 카드 혜택 정보들을 직접 읽어보며 데이터를 채워 나간다.</p>
<p>수작업이라 속도가 느린 만큼, 데이터들은 정확해야 한다. 추후 서비스를 통해 모이는 데이터를 활용할 수 있으면 좋겠다..</p>
<p>나는 데이터 작성을 위해, 키워드별로 해당하는 혜택을 가진 카드들을 나열하는 작업을 진행하였다. 위에 언급한대로, 각 카드사별 인기 카드의 비중을 높게 하였다.
<img src="https://velog.velcdn.com/images/julia_heo/post/86de9100-123d-4523-bfe8-e193d12002eb/image.png" width=700></p>
<p>편의점과 관련한 데이터들을 만들어보자. &quot;편의점&quot; 키워드의 혜택을 가진 카드들을 무작위로 배치한다. 중간중간 관련 혜택을 가지지 않은 카드들도 섞어서 값을 채워준다. </p>
<p>카드들의 혜택을 비교하고, 정답에 해당되는 <code>최대혜택카드id</code> / <code>해당혜택요약</code> / <code>할인금액</code>을 도출해낸다.
<img src="https://velog.velcdn.com/images/julia_heo/post/14b4c9b1-006e-477b-abc5-70e9b3b996ad/image.png" width=700></p>
<p>ai 모델인만큼 training 데이터가 많아야 성능을 크게 높일 수 있고, test를 통해 얻게되는 정확도는 원스 서비스의 신뢰도와 직결되기 때문에, 계속해서 더 많은 데이터를 생성하는 것이 중요하다.</p>
<h1 id="5-gpt-파인튜닝">5. GPT 파인튜닝</h1>
<p>만든 데이터 셋을 통해 파인튜닝 모델을 생성할 차례이다.
100개 중 60개의 training data를 <code>ex_tran.jsonl</code>에,
20개의 validation data를 <code>ex_val.jsonl</code>에 저장해 두었다.</p>
<h3 id="세팅">세팅</h3>
<ul>
<li>import
파이썬에서 사용되는 여러 가지 모듈과 라이브러리를 임포트해준다.<pre><code class="language-python">import json
import openai
import os
import pandas as pd
from pprint import pprint</code></pre>
</li>
<li>OPENAI  인증키
<a href="https://platform.openai.com/docs/overview">OPENAI API 사이트</a>에서 API KEY를 만들 수있다.<img src="https://velog.velcdn.com/images/julia_heo/post/415f08de-92f4-4944-acdf-af0cf2073aad/image.png" width=500>
생성한 뒤 이를 복사해, 아래 {오픈AI의 API 키} 자리에 넣어주자.
<code>openai.api_key=&quot;{오픈AI의 API 키}&quot;</code></li>
</ul>
<h3 id="데이터-파일-업로드">데이터 파일 업로드</h3>
<p>GPT 모델을 파인튜닝(fine-tuning)하기 위해 <code>ex_val.jsonl</code>와 <code>ex_tran.jsonl</code>을 OpenAI에 업로드한다</p>
<pre><code class="language-python">validation_file_name = &quot;ex_val.jsonl&quot;
training_file_name=&quot;ex_tran.jsonl&quot;
training_response = openai.File.create(
    file=open(training_file_name, &quot;rb&quot;), purpose=&quot;fine-tune&quot;
)
training_file_id = training_response[&quot;id&quot;]
validation_response = openai.File.create(
    file=open(validation_file_name, &quot;rb&quot;), purpose=&quot;fine-tune&quot;
)
validation_file_id = validation_response[&quot;id&quot;]
</code></pre>
<p>OpenAI API 홈페이지의 storage에서 업로드된 파일을 확인하고 관리할 수 있다.<img src="https://velog.velcdn.com/images/julia_heo/post/1de7ad1f-a14a-40a1-86a6-c28cadfbb059/image.png" width=500></p>
<h3 id="파인튜닝-작업-생성">파인튜닝 작업 생성</h3>
<pre><code class="language-python">response = openai.FineTuningJob.create(
    training_file=training_file_id,
    validation_file=validation_file_id,
    model=&quot;gpt-3.5-turbo&quot;,
    suffix=&quot;recipe-ner&quot;,
)
job_id = response[&quot;id&quot;]</code></pre>
<p><code>openai.FineTuningJob.create()</code> 메서드로 GPT 모델을 파인튜닝하기 위한 작업을 생성한다. 
이를 위해 훈련 데이터 파일과 검증 데이터 파일의 ID를 지정하고, 사용할 GPT 모델의 버전과 작업 이름에 접미사를 추가로 지정해준다.</p>
<h3 id="파인튜닝-작업-상태정보-조회">파인튜닝 작업 상태/정보 조회</h3>
<ul>
<li><p>파인튜닝 작업의 진행 상태, 훈련 진행 상황을 조회한다.</p>
<pre><code class="language-python">response = openai.FineTuningJob.retrieve(&quot;ftjob-iSlcJEn3kB5VOUhVDkhznTSR&quot;)

print(&quot;Job ID:&quot;, response[&quot;id&quot;])
print(&quot;Status:&quot;, response[&quot;status&quot;])
print(&quot;Trained Tokens:&quot;, response[&quot;trained_tokens&quot;])</code></pre>
<p><code>Job ID</code>는 작업을 식별하고, <code>Status</code>는 작업의 진행 상태를 나타내며, <code>Trained Tokens</code>는 작업의 훈련 진행 상황을 알려준다. job의 status가 succeeded가 되었을 때 다음 작업을 진행하면 된다.
<img src="https://velog.velcdn.com/images/julia_heo/post/2f3617e3-5163-437c-afa3-c335b0695f22/image.png" alt=""></p>
</li>
<li><p>파인튜닝 작업에 대한 이벤트 조회,역순으로 출력</p>
<pre><code class="language-python">response = openai.FineTuningJob.list_events(id=job_id, limit=50)
</code></pre>
</li>
</ul>
<p>events = response[&quot;data&quot;]
events.reverse()</p>
<p>for event in events:
    print(event[&quot;message&quot;])</p>
<pre><code>반드시 필요한 부분은 아니다.작업의 상태 변화나 진행 상황을 더 자세히 추적하고 싶을 때 유용하다.

- 파인튜닝된 모델의 ID 확인
 ```python
response = openai.FineTuningJob.retrieve(job_id)
  fine_tuned_model_id = response[&quot;fine_tuned_model&quot;]

  if fine_tuned_model_id is None: 
    raise RuntimeError(&quot;Fine-tuned model ID not found. Your job has likely not been completed yet.&quot;)

  print(&quot;Fine-tuned model ID:&quot;, fine_tuned_model_id)</code></pre><p>OpenAI API 사이트에 접속하여 파인튜닝 현황 및 생성된 모델 결과를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/julia_heo/post/124139a1-c907-4f9b-b764-ace1c072562c/image.png" alt=""></p>
<h3 id="챗봇-대화-생성">챗봇 대화 생성</h3>
<p>파인튜닝된 모델을 사용하여 챗봇 대화를 생성하고, 생성된 대화의 응답을 출력해보자.</p>
<pre><code class="language-python">responseTest = openai.ChatCompletion.create(
    model = fine_tuned_model_id,
    messages=[
        {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;:  system},
        {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: testuser},
    ],
    temperature=0
)

print(responseTest[&quot;choices&quot;][0][&quot;message&quot;][&quot;content&quot;])</code></pre>
<p>model에 파인튜닝한 gpt모델의 id를 써주면 된다. 파인튜닝한 gpt 모델의 아이디는 <code>ft:</code>로 시작한다.
결과는 다음과 같다.
<img src="https://velog.velcdn.com/images/julia_heo/post/b21b8267-c6c3-46e4-8f36-32c9b58319f5/image.png" width=700></p>
<h1 id="6-백엔드에서-파인튜닝한-gpt-호출">6. 백엔드에서 파인튜닝한 GPT 호출</h1>
<p>이제 파인튜닝한 gpt 모델을 백엔드단에서 사용하면, 결제 전 최대 할인 카드를 찾아주는 모델은 완성이다!</p>
<p>이 과정은 카드 혜택 요약 과정에서 사용한 코드와 크게 다르지 않다.</p>
<p>메인 화면에서 사용자가 결제처와 결제 금액을 입력하면, 현재 로그인한 사용자 <code>nowUser</code>와 입력한 정보 <code>keyword</code>, <code>paymentAmount</code>를 매개변수로 넣어 cardRecommend()를 호출한다.</p>
<p><code>String response = openaiService.cardRecommend(nowUser, keyword, paymentAmount);</code></p>
<p>cardRecommend는 다음과 같다.</p>
<pre><code class="language-java">@Service
@Slf4j
@RequiredArgsConstructor
public class OpenaiService {

    @Qualifier(&quot;openaiRestTemplate&quot;)
    @Autowired
    private RestTemplate restTemplate;
    private final BenefitSummaryRepository benefitSummaryRepository;
    private final OwnedCardRepository ownedCardRepository;
    private final CardRepository cardRepository;

    @Value(&quot;${openai.model}&quot;)
    private String model;

    @Value(&quot;${openai.api.url}&quot;)
    private String apiUrl;


    // 결제할 카드 추천
    public String cardRecommend(Users nowUser, String keyword, int paymentAmount) throws CustomException {
        String prompt = &quot;결제 금액, 결제처, 카드들의 혜택 정보를 입력으로 받아, 각 카드별로 결제처에 해당하는 혜택이 있다면 할인 금액을 계산합니다. 가장 큰 할인을 받을 수 있는 카드의 ”카드번호”, “혜택 정보”, “할인 금액”을 JSON 형식으로 반환합니다.\\\\```를 붙이지 않습니다. 결제처에 해당하는 카드의 혜택이 없거나, 결제처가 분야·브랜드명이 아니라면, 모든 value에 0을 넣어 반환합니다.\\\\”카드번호” 는 해당 카드의 &#39;카드 고유 번호&#39;,  “혜택 정보”는 결제처에 해당되는 혜택 정보 요약 텍스트(특수문자 없이 20자 이내)를 의미합니다.&quot;;

        List&lt;OwnedCard&gt; ownedCards = ownedCardRepository.findOwnedCardByUsers(nowUser);

        String userInput = &quot;{”결제 금액”: &quot; + paymentAmount + &quot;, “결제처”: “&quot; + keyword + &quot;“, “카드들의 혜택 정보“: [&quot;;
        for (OwnedCard ownedCard : ownedCards) {
            String name = ownedCard.getCard().getName();
            String id = ownedCard.getCard().getId().toString();
            Card card = ownedCard.getCard();
            userInput = userInput +&quot;{”이름”: ”&quot;+ name + &quot;”, &quot; + &quot;”카드 고유 번호” : &quot; + id + &quot;, “혜택“: [ &quot;;
            List&lt;BenefitSummary&gt; beneList = benefitSummaryRepository.findByCard(card);
            for( BenefitSummary benefit : beneList){
                userInput += &quot;“&quot;+benefit.getBenefitField()+&quot;  &quot;+benefit.getBenefitContents()+&quot;“,&quot;;
            }
            userInput = userInput.substring(0, userInput.length() - 1);
            userInput += &quot;” },&quot;;
        }
        userInput = userInput.substring(0, userInput.length() - 1);
        userInput += &quot;]}&quot;;

        // gpt 요청 보내는 부분
        OpenaiChatRequest request = new OpenaiChatRequest(model, prompt, userInput);
        OpenaiChatResponse response = restTemplate.postForObject(apiUrl, request, OpenaiChatResponse.class);

        if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) {
            throw new CustomException(ResponseCode.FAILED_TO_OPENAI);
        }

        String result = response.getChoices().get(0).getMessage().getContent();

        log.info(result);

        return result;
    }
}</code></pre>
<p>for문을 돌며 사용자의 ownedCards들의 혜택 정보를 json에 삽입해 Input을 완성하고,
OpenaiChatRequest에 넣은 뒤, RestTemplate을 사용하여 GPT에 POST 요청을 보낸다.</p>
<p>model에 파인튜닝한 모델의 id인 <code>ft:gpt-3.5-turbo-0125:personal:recipe-ner:9Op9RHfV</code>만 넣어주면 된다. 이는 스프링 부트의 application.properties에, 깃허브의 레포지토리 시크릿에 저장하고 있기 때문에, 파인튜닝을 추후 진행하더라도 코드의 수정 없이, 환경 변수만 다시 설정해주면 된다!</p>
<h1 id="회고">회고</h1>
<p>기술 검증을 해가며 AI 모델을 선택하는 것부터, 구현하는 것까지 많은 것을 새롭게 시도하고 배운 것 같다. 수많은 오류들을 만나고 밤을 새웠었는데, 그 모든 내용들을 이렇게 글로 정리하니 스스로 대견하게 느껴지기도 한다. 감도 잡히지 않던 카드 추천 챗봇이 정상적으로 카드를 추천해 주고 있고, 끝나지 않을 것 같던 졸업 프로젝트가 끝이 보인다...! 남은 기간에 더 많은 데이터 셋을 구축해 완벽한 카드 추천을 구현 완료해야겠다. 원스 짱 루스 짱</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SAA] 9 AWS 기초: RDS + Aurora + ElastiCache]]></title>
            <link>https://velog.io/@julia_heo/SAA-9-AWS-%EA%B8%B0%EC%B4%88-RDS-Aurora-ElastiCache</link>
            <guid>https://velog.io/@julia_heo/SAA-9-AWS-%EA%B8%B0%EC%B4%88-RDS-Aurora-ElastiCache</guid>
            <pubDate>Wed, 20 Dec 2023 06:34:21 GMT</pubDate>
            <description><![CDATA[<h1 id="rds-relational-database-service">RDS (Relational Database Service)</h1>
<p>DB 엔진의 유형: PostgreSQL, MySQL, MariaDB, Oracle Microsoft SQL Server, Aurora</p>
<p>관리형 서비스→데이터베이스 프로비저닝, 기본 운영체제 패치 완전 자동화</p>
<p>한 가지 단점- RDS 인스턴스에 SSH 액세스 불가</p>
<h2 id="⭐️storage-auto-scaling">⭐️Storage Auto Scaling</h2>
<p>활성화→RDS가 db공간 부족 감지해서 자동으로 스토리지를 확장 (db다운 x)</p>
<p>최대 스토리지 임곗값 설정 후 10% 미만 남으면 자동 수정</p>
<h2 id="rds-read-replica">RDS Read Replica</h2>
<p>요청이 너무 많아서 읽기를 스케일링</p>
<p>read replica 15개까지 생성 가능<img src="https://velog.velcdn.com/images/julia_heo/post/cbd2dff7-1b80-47d2-a575-900605dee16f/image.png" alt="">주 데이터베이스 인스턴스와 두 읽기 전용 복제본 사이에 <strong>비동기식 복제</strong>가 발생 (읽기 일관적으로 유지)</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; SELECT문만 가능</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 복제본 중 하나 승격시켜 db로 사용할수도있음 → 복제메커니즘 탈피, 자체적 life cycle 가짐</p>
<p>UseCase:
현재 내 데이터 기반으로 보고와 분석 실시할 예정, 보고 애플리케이션을 메인 rds에 연결하면 오버로드가 발생하고 생산 애플리케이션의 속도가 느려짐  → 이 workload를 위한 read replica 생성</p>
<p>Read Replica는 관리형 서비스로, 다른 AZ로 이동할 때 같은 region내라면 비용발생X
리전을 넘나들때는 네트워크 복제 비용 O</p>
<h2 id="rds-multi-az">RDS Multi AZ</h2>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/1434d17b-a07d-4aa5-a3c2-f50da45d6c83/image.png" alt="">
주로 재해 복구에 사용 (가용성 👆)</p>
<p>AZ A의 마스터 데이터베이스 인스턴스를 <strong>동기식</strong>으로 이를 AZ B에 스탠바이 인스턴스로 복제</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 애플리케이션의 마스터에 쓰이는 변경 사항이 대기 인스턴스에도 그대로 복제</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 하나의 DNS 이름을 갖고 통신하며 마스터에 문제가 발생시 스탠바이 데이터베이스에 자동으로 새로운 마스터</p>
<p>스탠바이 db는 대기목적 하나. 읽거나 쓰지x(scalingX)</p>
<h2 id="⭐️rds-읽기-전용-복제본-vs-다중-az">⭐️RDS 읽기 전용 복제본 VS 다중 AZ:</h2>
<p>⭐️ 재해 복구를 대비해서 읽기 전용 복제본을 다중 AZ로 설정 가능</p>
<p>⭐️단일 AZ에서 다중 AZ로 RDS 데이터베이스 전환이 가능, zero downtime operation(=단일 AZ에서 다중 AZ로 전환할 때 db 중지X)<img src="https://velog.velcdn.com/images/julia_heo/post/bd5f0ac2-bf91-42b9-8d2d-f4454e07559a/image.png" alt="">
&nbsp; &nbsp; &nbsp; &nbsp; 기본 데이터베이스의 RDS가 자동으로 스냅샷을 생성, 새로운 스탠바이 데이터베이스에 복원</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 두 데이터베이스 간 동기화 설정되어 스탠바이 데이터베이스가 메인 RDS 데이터베이스 내용을 모두 수용, 다중 AZ 설정 상태가 됨</p>
<p>실습에서 쓰는 SQL Electron (처음보는데 편해보임)</p>
<p>** 아는데…! 아는데! 자꾸 헷..갈리고 동기식/비동기식 “복제”는 어떤건지 궁금해져서</p>
<ul>
<li>동기식 복제와 비동기식 복제동기 복제 와 비동기 복제 의 주요 차이점은 데이터가 복제본에 기록되는 방식입니다. 대부분의 <strong>동기식 복제</strong> 제품은 기본 스토리지와 복제본에 동시에 데이터를 씁니다. 따라서 기본 복사본과 복제본은 항상 동기화된 상태를 유지해야 합니다.</li>
<li>대조적으로, <strong>비동기식 복제</strong> 제품은 먼저 기본 스토리지에 데이터를 쓴 다음 복제본에 데이터를 복사합니다. 복제 프로세스가 거의 실시간으로 발생할 수 있지만 복제가 예약된 방식으로 발생하는 것이 더 일반적입니다. 예를 들어, 쓰기 작업은 주기적으로(예: 5분마다) 일괄적으로 복제본에 전송될 수 있습니다.</li>
</ul>
<h2 id="rds-custom">RDS Custom</h2>
<p>RDS에서는 기저 운영 체제나 사용자 지정 기능에 액세스 불가 / RDS Custom에선 가능</p>
<ul>
<li>Oracle</li>
<li>Microsoft SQL Server</li>
</ul>
<p>Custom: 기본 데이터베이스 및 OS에 액세스하여 
• 설정 구성
• 패치 설치
• 기본 기능 사용
• SSH 또는 SSM Session Manager를 사용하여 기본 EC2 인스턴스 액세스</p>
<p>커스터마이징 하려면 Automation mode 비활성화 (ec2 인스턴스에 액세스가 가능해지면 문제발생 쉬우므로 db스냅샷 만들어두는게 좋음</p>
<h2 id="rds-vs-rds-custom">RDS vs RDS Custom</h2>
<p>RDS: 전체 데이터베이스 및 OS를 AWS가 관리</p>
<p>RDS Custom: 기본 OS 및 데이터베이스에 대한 전체 관리자(admin) 권한을 가짐</p>
<h1 id="⭐️amazon-aurora">⭐️Amazon Aurora</h1>
<p>AWS 고유의 기술( 오픈 소스X )</p>
<p>Postgres 및 MySQL과 호환</p>
<p>클라우드에 최적화되어 있고 여러 가지 똑똑한 최적화→ RDS의 MySQL보다 5배, Psostgres보다는 3배 높은 성능</p>
<p>스토리지 자동 확장 (10GB에서 시작, 12TB까지 커짐)</p>
<p> 15개의 Read Replica 가능 (MySQL에서는 5개), 복제속도 빠름</p>
<h2 id="aurora-high-availability-and-read-scaling">Aurora High Availability and Read Scaling</h2>
<p>AZ 3개에 걸쳐 기록할때마다 6개의 copies</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; write에 사본 중 4개만 있으면 됨(AZ하나 작동 안해도 ㄱㅊ)</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; read에는 사본 중 3개만 있으면 됨(읽기 가용성 높음)</p>
<p>마스터가 작동안하면 평균 30초 이내로 장애조치</p>
<p>백엔드 peer-to-peer replication으로 self healing</p>
<p>수백개의 볼륨 사용(단일볼륨에 의존x)<img src="https://velog.velcdn.com/images/julia_heo/post/d2e033fe-fe12-400e-9653-2fc10769befa/image.png" alt="">마스터가 하나, 복제본들은 리전 간 복제 가능 , 작은 블록 단위로 자가 복구·확장</p>
<h2 id="aurora-db-cluster">Aurora DB Cluster</h2>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/c329a1ea-e9bd-4175-b1d7-94eab31f5aa5/image.png" alt="">마스터만 write가능- ⭐️<strong>라이터(Writer) 엔드포인트</strong> 제공 = DNS 이름 (마스터 가리킴)</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 클라이언트가 라이터 엔드포인트와 상호작용, 올바른 인스턴스로 자동 리다이렉트</p>
<p>read replica 15개까지 자동 스케일링</p>
<p>자동스케일링 켜있으면 우리의 앱이 복제본이 어디있고 URL이 무엇이고 어떻게 연력하는지 파악하기 어려울수도</p>
<p>→ ⭐️<strong>리더(Reader) 엔드포인트</strong></p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 라이터 엔드포인트와 정확히 같은 기능</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 연결 로드 밸런싱에 도움</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 모든 읽기 전용 복제본과 자동으로 연결</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 클라이언트가 리더 엔드포인트에 연결될 때마다 읽기 전용 복제본 중 하나로 연결되며 로드 밸런싱을 도와줌</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 로드 밸런싱 <strong>statement 레벨이 아닌 connection 레벨 **</strong></p>
<p>** 클러스터:</p>
<p>컴퓨터 클러스터란 여러 대의 컴퓨터들이 연결되어서 하나의 시스템처럼 동작하는 컴퓨터들의 집합. 여러개의 객체를 하나로 모은다는 개념</p>
<p>DB 클러스터도 똑같은 맥락! DB 서버를 여러개 둔다고 생각하면 된다. 이에 대한 기본적인 장점은 서버 한 대가 죽어도 대비가 가능하다는 점이다.</p>
<h2 id="aurora-replicas---auto-scaling">Aurora Replicas - Auto Scaling</h2>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/ad7d69f2-0164-4cf9-9f02-4b84998d12c1/image.png" alt=""></p>
<h2 id="custom-endpoints">Custom Endpoints</h2>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/2401b8da-369f-4ebb-93d9-06c5c183f74f/image.png" alt="">Aurora 인스턴스의 하위 집합을 Custom Endpoint로 정의</p>
<p>사용자 지정 엔드포인트가 있는 경우, 일반적으로 리더 엔드포인트는 사용x</p>
<h2 id="aurora-serverless">Aurora Serverless</h2>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/9c845380-b3d7-40e4-98bf-b4066264556e/image.png" alt="">사용량에 따라 자동화된 데이터베이스 인스턴스화 및 오토 스케일링을 제공</p>
<p>워크로드가 드물거나 간헐적이거나 예측할 수 없는 경우에 유용</p>
<p>용량 계획을 세울 필요X</p>
<p>클라이언트는 Aurora에서 관리하는 프록시 플릿과 소통</p>
<h2 id="aurora-multi-master">Aurora Multi-Master</h2>
<p>continuous write availability를 원하는 경우</p>
<p>모든 aurora 인스턴스가 writer 노드</p>
<p>클라이언트가 Multiple writer endpoints로 연결 유지 가능
<img src="https://velog.velcdn.com/images/julia_heo/post/a41209dd-f19a-48f6-a919-5e1ce9f69ffd/image.png" alt=""></p>
<p>** endpoint가 뭔가</p>
<p>엔드포인트는 서비스를 사용 가능하도록 하는 서비스에서 제공하는 커뮤니케이션 채널의 한쪽 끝</p>
<p>→ 엔드포인트는 AWS 웹 서비스를 위한 진입점의 URL</p>
<h2 id="global-aurora">Global Aurora</h2>
<p>Aurora Cross Region Read Replicas → 재해 복구에 유용</p>
<p>Aurora Global Database → 최대 5개의 보조 읽기 전용 리전(복제delay 1초 미만), 중단되면 다른 리전을 활성화(1분 미만) (읽기전용이 읽고쓰게 승격)</p>
<p>⭐️ &quot;Aurora 글로벌 데이터베이스의 데이터를 리전 간에 복제하는 데 평균 1초 미만이 소요됩니다&quot;</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; → 이 문장이 시험에 나온다면 글로벌 Aurora를 사용하라는 힌트</p>
<h2 id="aurora-machine-learning">Aurora Machine Learning</h2>
<p>SQL 인터페이스로 애플리케이션에 머신러닝 기반 예측을 적용 가능</p>
<ul>
<li>SageMaker : 백엔드에서 모든 종류의 머신러닝 모델을 사용할 수 있게 도움</li>
<li>Amazon Comprehend : 감정 분석</li>
</ul>
<p>비스 간의 간단하고 최적화된 안전한 통합입니다</p>
<p>두 가지 서비스에서 지원되는데요, SageMaker는 백엔드에서 모든 종류의 머신러닝 모델을 사용할 수 있게 해줍니다, 그리고 Amazon Comprehend가 있죠</p>
<h1 id="rds--aurora--백업과-모니터링">RDS &amp; Aurora- 백업과 모니터링</h1>
<h2 id="rds-backups">RDS Backups</h2>
<p>Automated backups</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; RDS 서비스가 자동으로 매일 데이터베이스의 전체 백업을 수행</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 5분마다 트랜잭션 로그가 백업 (언제라도 5분 전으로 복원 가능), 자동 백업 보존 기간은 1~35일 </p>
<p>Manual DB Snapshots</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 수동 DB 스냅샷은 원하는 기간 동안 보관 가능</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; ⭐️ex. 비용 절감하고싶을 때, 수냅샷 만들고 데이터 베이스 중지하여 사용할때만 스냅샷 복원</p>
<h2 id="aurora-backups">Aurora Backups</h2>
<p>Automated backups</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 1~35일 (비활성화 불가능)</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; point-in-time recovery(시점 복구기능) - 어느 시점으로든 복구 가능</p>
<p>Manual DB Snapshots</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 사용자가 수동으로 트리거, 원하는 기간동안 유지</p>
<h2 id="restore-options">Restore options</h2>
<ul>
<li>RDS / Aurora backup or a snapshot을 복원할 때 새 데이터베이스 생성</li>
<li>S3에서 MySQL RDS database 복구 (db 백업만 있으면 됨)</li>
<li>S3에서 MySQL Aurora cluster 복구(Percona XtraBackup으로 백업한 다음, S3에서 Aurora DB 클러스터로 백업)</li>
</ul>
<h2 id="aurora-database-cloning">Aurora Database Cloning</h2>
<p>기존 데이터베이스 클러스터에서 새로운 Aurora 데이터베이스 클러스터 생성 가능</p>
<p>ex. 프로덕션 데이터베이스 존재, 테스트 실행하기 위해db 복제→스테이징 Aurora 데이터베이스</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; (스냅샷 찍고 복원보다 빠름</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; → 복제는 copy-on-write 프로토콜**</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 처음 복제본 만들때 원래 데이터베이스 클러스터와 동일한 볼륨 사용, 데이터 복사 안하니까 빠르고 효율적임</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 프로덕션 Aurora 데이터베이스 ⇒ 스테이징 Aurora 데이터베이스에 업데이트 : 새로운 추가 스토리지가 할당되고, 데이터 복사 </p>
<p>Database Cloning은 빠르고 비용 효율적, 프로덕션 db에 영향 X,  db 복제에 유용 (스냅샷, 복원 기능 필요X)</p>
<p>** <a href="https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraUserGuide/Aurora.Managing.Clone.html">https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraUserGuide/Aurora.Managing.Clone.html</a></p>
<h2 id="rds--aurora-security">RDS &amp; Aurora Security</h2>
<p><strong>At-rest encryption:</strong> AWS KMS를 사용해 마스터와 모든 replica 암호화 - 데이터가 볼륨에 암호화됨</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 마스터 암호화 안했으면 read replica 암호화 불가</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 암호화 안했던거 암호화하려면 스냅샷생성 → 암호화된 형태로 스냅샷 복원</p>
<p><strong>In-flightencryption:</strong> 클라이언트, 데이터베이스 간의 전송중 데이터 암호화</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; TLS default → 클라이언트가 TLS 루트 인증서 사용해야 함</p>
<p><strong>IAM Authentication:</strong> IAM role 사용해 db에 접속</p>
<p><strong>Security Groups:</strong> 특정 포트, IP, 보안 그룹 허용/차단</p>
<p><strong>No SSH available:</strong> ssh액세스 없음</p>
<p><strong>Audit Logs:</strong> 쿼리 기록, 데이터베이스 확인, CloudWatch Logs로 보내서 장기보관</p>
<h3 id="amazon-rds-proxy">Amazon RDS Proxy</h3>
<p><strong>바로 액세스 안하고 프록시하는 이유:</strong></p>
<ul>
<li><p>애플리케이션이 db내에서 연결 풀을 형성하고 공유 가능
⭐️애플리케이션을 rds인스턴스에 일일이 연결하는 대신 프록시에 연결→ 프록시가 하나의 pool에 연결을 모음 → <strong>rbs 인스턴스로의 연결 감소</strong> → CPU, RAM등 db리소스의 부담을 줄여 효율성 향상, db의 open connection과 timeout 최소화</p>
</li>
<li><p>rds프록시는 완전한 서버리스 - autoscaling, highly available (multi-AZ)
⭐️장애나면 대기 인스턴스로 실행되어 <strong>장애조치시간(failover time) 66% 감소</strong>
&nbsp; &nbsp; &nbsp; &nbsp; 메인 rds db instance에 애플리케이션 연결하고 각자 장애조치하는 대신, 장애조치와 무관한 RDS프록시에 연결 : RDS프록시가 장애 조치 발생한 RDS데이터베이스 인스턴스 처리해서 장애조치 시간 개선됨</p>
</li>
<li><p>애플리케이션 코드 변경 없이 rds프록시에 연결하면 됨 (rds db인스턴스, 오로라인스턴스 대신에)</p>
</li>
<li><p>데이터베이스에 <strong>IAM 인증 강제</strong>하여 IAM인증을 통해서만 rds db instance에 연결하게 함
자격증명은 AWS Secret Manager서비스에 안전하게 저장됨</p>
</li>
<li><p>RDS는 public 액세스가 불가능.(VPC 내에서만 액세스 가능) → 보안 훌륭</p>
</li>
<li><p>Lambda 함수와 사용하면 유용
람다함수는 빠르게 생성되고 사라져 연결많아지고 시간초과 발생 가능 → rds 프록시로 람다함수의 connection pool을 생성하면 람다함수가 rds프록시를 오버로드, 인스턴스 연결 감소</p>
</li>
</ul>
<p>** 프록시란?</p>
<p>&quot;대리&quot;의 의미</p>
<p>특히 내부 네트워크에서 인터넷 접속을 할 때에, 빠른 액세스나 안전한 통신등을 확보하기 위한 중계서버를 &quot;프록시 서버” 라고함</p>
<p>클라이언트와 Web서버의 중간에 위치하고 있어, 대신 통신을 받아 주는 것이 프록시 서버</p>
<h1 id="amazon-elasticache">Amazon ElastiCache</h1>
<p>캐싱 기술인 Redis 또는 Memcached를 관리하는 것을 도움
(캐시: 높은 성능 짧은 delay의 인메모리(in-memory) 데이터베이스/ 읽기 집약적 워크로드의 db로드 줄여줌</p>
<p>** in-memory 컴퓨터의 주 메모리에 모든 조직 또는 개인의 데이터를 저장</p>
<p>일반적 쿼리는 캐시에 저장, 캐시만 사용해도 쿼리 결과 검색 가능</p>
<p>애플리케이션의 상태를 Amazon ElastiCache에 저장해 애플리케이션이 상태 비저장형(stateless)하게 도와줌</p>
<p>애플리케이션의 <strong>코드를 많이 바꿔야함**</strong></p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 캐시를 쿼리하는 애플리케이션을 변경해야 함</p>
<p>ex. Amazon , RDS 데이터베이스, 애플리케이션</p>
<p>애플리케이션이 ElastiCache를 쿼리 → 이미 그 결과가 발생하여 저장하되어 있으면 Cache hit</p>
<p>⇒ 쿼리 수행 위해 RDS로 이동하는 시간 절약</p>
<p>캐시 무효화 전략 (invalidation strategy) - 최신 데이터만 사용해야 함
<img src="https://velog.velcdn.com/images/julia_heo/post/dba427b1-6c66-4d11-9e94-22f597900b72/image.png" alt=""></p>
<p>ex. 애플리케이션을 stateless로 만들기 위해 사용자 세션을 저장</p>
<p>사용자가 application에 로그인하면 세션 데이터를 ElastiCache에 씀</p>
<p>세션 캐시를 ElastiCache에서 검색가능하면 계속 로그인 상태인 것</p>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/03d9818d-1b67-4ba0-b3ca-f7c567f11ce1/image.png" alt=""></p>
<p><strong>Redis vs Memcache</strong></p>
<p>Redis: 가용성 내구성이 뛰어난 복제된 캐시, 고가용성, 백업, 읽기 복제본을 위해 존재</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; Multi AZ → Auto-Failover(자동 장애 조치 기능)
&nbsp; &nbsp; &nbsp; &nbsp; Read Replicas → ( scale reads, high availability)
&nbsp; &nbsp; &nbsp; &nbsp; AOF persistence → Data Durability(내구성)
&nbsp; &nbsp; &nbsp; &nbsp; Backup,  restore 
&nbsp; &nbsp; &nbsp; &nbsp; ⭐️Supports Sets,  ⭐️Sorted Sets</p>
<p>Memcache: 분산되어있는 순수한 캐시. 데이터 손실되어도 괜찮음</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; <strong>Multi-node</strong> for partitioning of data (=sharding)
&nbsp; &nbsp; &nbsp; &nbsp; No high availability (replication X)
&nbsp; &nbsp; &nbsp; &nbsp; Non persistent 영구 캐시 아님
&nbsp; &nbsp; &nbsp; &nbsp; No backup and restore
&nbsp; &nbsp; &nbsp; &nbsp; Multi-threaded architecture</p>
<p>** <a href="https://docs.aws.amazon.com/ko_kr/AmazonElastiCache/latest/mem-ug/SelectEngine.html">https://docs.aws.amazon.com/ko_kr/AmazonElastiCache/latest/mem-ug/SelectEngine.html</a></p>
<p>Redis랑 Memcached는 Amazon ElastiCache에서 지원하는 캐시엔진</p>
<br>

<p><strong>Cache Security</strong></p>
<p>Redis만 IAM 인증 지원 (나머진 id pw)</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; IAM 정책 정의→AWS API-level security만 사용</p>
<p>Redis AUTH _ EC2인스턴스와 클라이언트가 있는 경우 Redis AUTH로 Redis 클러스터에 연결</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; redis클러스터 만들때 “password/token” 설정</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 캐시에 추가 보안 수준 제공 (보안그룹+α로)</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; SSL 전송중 암호화 지원</p>
<p>Memcached는 SASL 기반 승인 제공</p>
<br>

<p><strong>ElastiCache에 데이터 로드하는 패턴</strong></p>
<p>Lazy Loading(지연로딩) : 모든 데이터 캐시</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 캐시 히트 없는 경우에만 데이터를 ElastiCache에 로드</p>
<p>Write Through : 데이터가 기록될 떄마다 캐시에 데이터 추가, 엡데이트 (시간지연 X)</p>
<p>Session Store: ElastiCache에 세션 저장</p>
<br>

<p><strong>Redis Use Case</strong></p>
<p>⭐️게이밍 리더보드 만들기:</p>
<p>게임하는 어떤 순간이든 누가 1등? 2등? 3등?</p>
<p>⇒Redis Sorted sets은 고유성(uniqueness), 요소 순서(element ordering) 모두 보장</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;  요소가 추가될 때마다, 실시간으로 순위를 매긴 다음 올바른 순서로 추가</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; ⇒ 애플리케이션에서 기능 프로그래밍할 필요 없이 실시간 리더보드에 액세스 가능</p>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/f9c07794-9847-4aa9-b46c-1738c2572b74/image.png" alt=""></p>
<p>⭐️“중요한 포트”와 “RDS 데이터베이스 포트”를 구분할 줄만 알면 됨
<strong>중요한 포트:</strong></p>
<p>FTP: 21</p>
<p>SSH: 22</p>
<p>SFTP: 22 (SSH와 같음)</p>
<p>HTTP: 80</p>
<p>HTTPS: 443
<br></p>
<p><strong>RDS 데이터베이스 포트:</strong></p>
<p>PostgreSQL: 5432</p>
<p>MySQL: 3306</p>
<p>Oracle RDS: 1521</p>
<p>MSSQL Server: 1433</p>
<p>MariaDB: 3306 (MySQL과 같음)</p>
<p>Aurora: 5432 (PostgreSQL와 호환될 경우) 또는 3306 (MySQL과 호환될 경우)</p>
<h3 id="용어-정리">용어 정리</h3>
<p>RDS</p>
<ul>
<li><p>Storage Auto Scaling</p>
</li>
<li><p>Read Replica
15개</p>
</li>
<li><p>Multi AZ
다른az에 standby</p>
</li>
<li><p>Custom</p>
</li>
<li><p>백업
매일 전체백업, 5분마나 트랜잭션로그 백업 + Manual DB Snapshots</p>
</li>
<li><p>Proxy
Amazon Aurora</p>
</li>
<li><p>High Availability and Read Scaling
3개 AZ에 6개의 copies</p>
</li>
<li><p>Aurora DB Cluster
라이터(Writer) 엔드포인트
리더(Reader) 엔드포인트</p>
</li>
<li><p>Aurora Replicas - Auto Scaling</p>
</li>
<li><p>Custom Endpoints</p>
</li>
<li><p>Aurora Serverless</p>
</li>
<li><p>Aurora Multi-Master
Multiple writer endpoints</p>
</li>
<li><p>Global Aurora
최대 5개 read replica region, 중단되면 메인으로 승격 (복제 1초미만)</p>
</li>
<li><p>Aurora Machine Learning</p>
</li>
<li><p>백업
자동백업 비활성화불가 + Manual DB Snapshots
Aurora Database Cloning
&nbsp; &nbsp; &nbsp; &nbsp; 크기같은거 만들고나서 내용 복사. 빠르고 비용효율적</p>
</li>
</ul>
<p>ElastiCache</p>
<ul>
<li><p>Redis</p>
</li>
<li><p>Memcached
쿼리결과캐싱, stateless 애플리케이션, Supports Sets,  Sorted Sets</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ACC] Storage]]></title>
            <link>https://velog.io/@julia_heo/ASC-Storage</link>
            <guid>https://velog.io/@julia_heo/ASC-Storage</guid>
            <pubDate>Mon, 27 Nov 2023 17:52:07 GMT</pubDate>
            <description><![CDATA[<h1 id="storage-service">Storage Service</h1>
<p>: 데이터 저장, 엑세스, 보호 및 분석 기능을 갖춘 Amazon의 서비스</p>
<ul>
<li><p><a href="https://aws.amazon.com/ko/what-is/block-storage/">Block Storage</a>
• 데이터를 일정 크기의 블록으로 나누어 저장
• 각 블록은 독립적인 주소를 가지며 이를 통해 랜덤 엑세스 가능 
• Storage Area Network(SAN): 고성능의 전용 네트워크. 서버와 데이터 스토리지 시스템을 연결
• 빠른 입출력(I/O) 속도가 필요한 데이터베이스에 적합
• 높은 IOPS(초당 입출력 작업 수)와 낮은 지연 시간이 중요한 경우에 선택
• 운영 체제 또는 중요한 애플리케이션을 실행하는 데 사용됨</p>
</li>
<li><p><a href="https://aws.amazon.com/ko/what-is/cloud-file-storage/">File Storage</a>
• 데이터를 파일로 저장, 파일들은 폴더나 디렉토리에 조직
• 계층적인 파일 시스템 구조를 가짐
• Network Attached Storage(NAS): 네트워크에 연결돼 여러 사용자와 장치가 파일 기반의 데이터에 접근할 수 있게 해주는 전용 파일 스토리지
• 여러 서버나 사용자가 동일한 파일 시스템에 동시에 액세스해야 할 때 적합
• 파일과 디렉토리를 통해 데이터를 관리하고 싶을 때 유용
• 네트워크를 통한 파일 공유가 필요한 경우에 적합</p>
</li>
</ul>
<ul>
<li><a href="https://aws.amazon.com/ko/what-is/object-storage/">Object Storage</a>
• REST 기반의 API 호출을 통해 데이터에 접근 
• HTTP 프로토콜
• 대용량 미디어 파일, 이미지, 백업 등을 저장할 때 적합
• 정적 웹 컨텐츠(예: 이미지, 비디오, HTML 페이지)를 호스팅할 때 유용 
• 저렴한 비용으로 데이터를 오래 보관해야 할 때 선택
• 전 세계 여러 데이터 센터에 걸쳐 데이터를 분산시켜야 할 때 효과적
<img src="https://velog.velcdn.com/images/julia_heo/post/fcfd8946-5f65-4754-bbcb-c1486f943ee7/image.png" alt=""></li>
</ul>
<h1 id="amazon-s3">Amazon S3</h1>
<p>usecase: 백업 및 스토리지, 재해 복구, 아카이브, 하이브리드 클라우드 스토리지, 애플리케이션 스토리지, 미디어 호스팅, 데이터 레이크 및 대규모 데이터 분석, 소프트웨어 배포, 정적 웹사이트 호스팅</p>
<h3 id="특징">특징</h3>
<p><strong>Access 제어</strong>
객체 별 ACL을 통해 데이터에 접근 가능한 사용자 특정이 가능
(ACL: Access Control List)</p>
<p><strong>데이터 저장</strong>
데이터는 객체 단위로 저장</p>
<p><strong>내구성</strong>
99.999999999%의 내구성</p>
<p><strong>데이터 저장 용량</strong>
Standard의 경우 객체 하나의 크기는 최대 5TB까지</p>
<h3 id="구성">구성</h3>
<p><strong>Bucket</strong>
- S3에 객체를 저장하게 해주는 시스템
- AWS 전역에서 단 한개만 존재: 리전과 관계 없이 전역적으로 Unique한 이름이 필요
- 리전 수준에서 정의: 데이터 저장 위치와 관련된 법률 및 규정 준수에 영향</p>
<p><strong>Object</strong>
- 객체는 보통 파일을 의미 (img, html,.. )
- 객체는 키를 가짐 (키는 파일의 전체 경로)
- 객체 최대 크기는 5TB
- 한번에 5GB이상 업로드 불가능
<img src="https://velog.velcdn.com/images/julia_heo/post/89841880-2a4f-413a-bdf8-e4bf470a04ae/image.png" alt=""></p>
<h3 id="s3-versioning">S3 Versioning</h3>
<p>버킷의 버전 관리를 활성화하여 여러 버전의 객체 보관 가능
실수로 삭제되거나 덮어써진 객체 복원 가능
한번 활성화하면 비활성화 불가. 대신 중단 상태로 전환 가능</p>
<h3 id="s3-replication">S3 Replication</h3>
<ul>
<li>버킷 내의 객체를 자동으로 다른 버킷으로 복사하는 기능. 비동기적</li>
<li><strong>CRR</strong>(Cross Region Replication) - 재해 복구, 사용자에게 더 빠른 액세스 제공, 규정 요구사항 충족을 위해</li>
<li><strong>SRR</strong>(Same Region Repplication) - 엑세스 제어, 실수로 인한 삭제 손상 방지, 효율적 데이터 관리</li>
</ul>
<h3 id="s3-encryption---정적암호화">S3 Encryption - 정적암호화</h3>
<p><strong>서버 측 암호화(SSE)</strong></p>
<ul>
<li><p>SSE-S3 (Server-Side Encryption with Amazon S3- Managed Key) : 
각 객체마다 고유한 키를 사용하고, 추가 보안을 위해 이 키를 다른 마스터 키로 정기적으로 암호화. 
사용자는 키 관리에 대해 걱정할 필요 없이 S3 에 의해 자동으로 처리됨. (default)<img src="https://velog.velcdn.com/images/julia_heo/post/cf720061-e9b1-42d5-a1cb-02493bfca015/image.png" alt=""></p>
</li>
<li><p>SSE-KMS (Server-Side Encryption with AWS Key Management Service) : 
사용자가 키의 사용을 제어하고 접근을 감사할 수 있음. 또한, 사용자 정의 키 관리 및 암호화 키의 로테이션을 지원.
<em>KMS-api 사용하므로 KMS 제한 사항에 영향을 받을 수 있음</em><img src="https://velog.velcdn.com/images/julia_heo/post/b794cb02-724b-4cbc-9e8f-c00a25025b6c/image.png" alt=""></p>
</li>
<li><p>SSE-C (Server-Side Encryption with Customer- Provided Keys) : 
데이터를 업로드하거나 다운로드할 때마다 사용자는 자신의 암호화 키를 제공해야 함. 이 방법은 키 관리의 책임이 사용자에게 있음.
<em>AWS로 키를 보내기 때문에 서버측 암호화 HTTP 요청마다 키를 헤더 제공해야하고, HTTPS 사용 필수</em><img src="https://velog.velcdn.com/images/julia_heo/post/329dd55f-d6a7-467c-bd57-4d9aa005d675/image.png" alt=""></p>
</li>
</ul>
<p><strong>클라이언트 측 암호화 (CSE)</strong></p>
<ul>
<li>CSE (Client-Side Encryption)
클라이언트 측에서 암호화하고, 암호화 된 데이터를 s3에 저장
암호화 키와 암호화 프로세스는 전적으로 사용자 측에서 관리
이는 보안을 높이지만, 키 관리 및 암호화 구현의 복잡성이 증가
(Amazon S3 Client-Side Encryption Library와 같은 클라이언트 라이브러리 사용)<img src="https://velog.velcdn.com/images/julia_heo/post/c2bc7b13-90a5-423f-b09f-2443939a8380/image.png" alt=""></li>
</ul>
<h3 id="s3-encryption---동적암호화">S3 Encryption - 동적암호화</h3>
<ul>
<li>전송 중 암호화는 SSL 또는 TLS라고 함 </li>
<li>Amazon S3는 두 개의 엔드포인트가 있음<pre><code>  - **HTTP** 엔드포인트: 암호화되지 않음
  - **HTTPS** 엔드포인트: 전송 중 암호화</code></pre></li>
<li>HTTPS가 권장됨</li>
<li>SSE-C의 경우 HTTPS 프로토콜을 꼭 사용해야 함 대부분의 클라이언트는 기본적으로 HTTPS 엔드포인트를 사용함</li>
</ul>
<h3 id="s3-security">S3 Security</h3>
<ul>
<li>IAM</li>
<li>버킷 정책</li>
<li>접근 제어 목록 ACL
<img src="https://velog.velcdn.com/images/julia_heo/post/cd09de41-7e0e-4f1a-b386-82fbace65448/image.png" alt=""></li>
</ul>
<h3 id="s3-hosting">S3 Hosting</h3>
<p>S3 버킷에 있는 파일을 이용해 정적 웹사이트 호스팅 가능</p>
<h3 id="s3-cors">S3 CORS</h3>
<p><strong>CORS</strong>
요청 프로토콜의 일부로 다른 웹사이트에 요청을 보내야할 때 다른 오리진이 <strong>CORS Headers</strong>를 사용해서 요청을 허용하지 않는한 해당 요청은 이행되지 않음 (ex. <strong>Access-Control-Allow-Origin</strong>)
<img src="https://velog.velcdn.com/images/julia_heo/post/aad8c012-29df-42d7-9e16-5bc1d0300940/image.png" alt="">
<strong>S3 CORS</strong></p>
<ul>
<li>만약 클라이언트가 S3 버킷에서 교차 출처 요청 (cross-origin request)을 보내는 경우, 정확한 CORS 헤더를 활성화해야 한다</li>
<li>CORS 헤더를 설정함으로써 특정 출처를 허용하거나 * (모든 출처)를 허용할 수 있음
<img src="https://velog.velcdn.com/images/julia_heo/post/10c3a1a3-5226-4905-9598-69ff9b82d742/image.png" alt=""></li>
</ul>
<h3 id="s3-pre-signed-urls">S3 Pre-Signed URLS</h3>
<ul>
<li>S3 콘솔, AWS CLI 또는 SDK를 사용하여 생성할 수 있는 URL</li>
<li>미리 서명된 URL을 받은 사용자는 GET / PUT에 대한 권한을 URL을 생성한 사용자로 부터 상속받음<img src="https://velog.velcdn.com/images/julia_heo/post/a7622c94-613f-4fa4-b319-340e208327f0/image.png" alt=""></li>
</ul>
<h3 id="s3-classes">S3 Classes</h3>
<p>S3에는 다양한 클래스가 존재 -&gt; 내구성은 동일하지만 가용성이 클래스마다 다름
<img src="https://velog.velcdn.com/images/julia_heo/post/1f7f91b7-9f7b-4cde-a760-37bbae99b735/image.png" alt=""></p>
<h3 id="s3-수명-주기-규칙">S3 수명 주기 규칙</h3>
<p>수명 주기 규칙은 크게 전환(Transition), 만료(Expiration) 작업으로 구성 (기간 설정도 가능)<img src="https://velog.velcdn.com/images/julia_heo/post/3f542b1e-72b5-44a2-bdaa-b5d603d0b6da/image.png" alt=""></p>
<h1 id="amazon-cloudfront">Amazon CloudFront</h1>
<h3 id="cdn">CDN</h3>
<p>Content Delivery Network
지리적 제약 없이 전세계 사용자에게 빠르고 안전하게 콘텐츠를 전송할 수 있는 기술 <img src="https://velog.velcdn.com/images/julia_heo/post/a18df716-df3a-4576-8dd0-86facaca906d/image.png" alt=""></p>
<h3 id="edge-location">Edge Location</h3>
<p> 사용자에게 더 가까운 지리적 위치에 콘텐츠를 저장하는 데이터 센터
(Cloudfront에는 300개 이상의 엣지 로케이션)<img src="https://velog.velcdn.com/images/julia_heo/post/7afde32a-27d7-4425-9cd6-bc13a0f8ce66/image.png" alt=""></p>
<h3 id="cloudfront-edge-location">Cloudfront Edge Location</h3>
<p><strong>Regional Edge Caches (REC)</strong>
- 오리진과 엣지 로케이션 사이에 존재하는 캐시 계층
- 글로벌하게 배포되어있는 CloudFront 위치 
- 리전 엣지 캐시는 표준 에지 로케이션에서 캐시하지 않는 콘텐츠를 저장
- 에지 로케이션에서 캐시 미스(cache miss)가 발생했을 때, 오리진 서버 대신 리전 엣지 캐시로부터 콘텐츠를 검색하여 더 빠른 응답 시간을 제공</p>
<p><strong>Origin Shield (2020.10 릴리즈)</strong>
- 2020년 10월에 발표된 새로운 기능으로, CloudFront에서 캐싱 계층을 하나 더 추가하여 사용자(클라이언트)와 엣지 서버간의 거리를 줄이는 기능.
- 캐시 적중률을 높이고 오리진 서버의 부하를 줄여주어 로드 속도를 향상시키는 효과가 있다.</p>
<h3 id="amazon-cloudfront-1"><a href="https://aws.amazon.com/ko/cloudfront/">Amazon Cloudfront</a></h3>
<p>- AWS에서 제공하는 CDN (글로벌) 서비스
- Client의 콘텐츠 요청으로 서버에서 받아온 콘텐츠를 캐싱하고 이후 같은 요청이 왔을 때, 그 캐싱해 둔 것을 제공하는 서비스
- 성능 향상, 데이터 전송 속도 빨라짐, 보안성 향상, 안정성 및 가용성 향상
- Origin에서 HTTPS를 지원하지 않아도, CloudFront 내에서 HTTPS 통신 지원 가능
- Shield, AWS Web Application Firewall을 제공함으로써 DDoS로부터 보호 제공
- S3와 연동 시 =&gt; 캐싱을 지원하기 때문에 s3에 저장된 컨텐츠를 직접 접근하지 않아도 되므로 s3의 비용이 감소하며, 더 빠른 응답 지원</p>
<h3 id="amazon-cloudfront-동작-순서">Amazon Cloudfront 동작 순서</h3>
<ol>
<li>사용자가 어플리케이션에 요청을 한다.</li>
<li><strong>DNS</strong>는 사용자에게 적합한 <strong>Edge Location</strong>으로 라우팅 한다.</li>
<li>Edge Location에서 <strong>캐시</strong>를 확인하고 있으면 이것을 사용자에게 반환한다.</li>
<li>없으면 가장 가까운 <strong>REC</strong>로 캐시가 있는지 요청한다.</li>
<li>없으면 <strong>CloudFront</strong>는 <strong>오리진</strong>으로 요청을 전달한다.</li>
<li>오리진은 &#39;오리진 &gt; REC &gt; Edge Location &gt; CloudFront가 사용자에게 전달&#39; 수순을 밟는다. (캐시도 추가된다)</li>
<li>REC에 캐시가 있다면 REC는 콘텐츠를 요청한 Edge Location으로 반환한다.</li>
<li>REC로부터 콘텐츠의 첫 번째 바이트가 도착하는 즉시 Edge Location은 이를 사용자에게 반환한다.</li>
<li>Edge Location은 나중을 위해 이 콘텐츠 <strong>캐시</strong>를 저장한다.<img src="https://velog.velcdn.com/images/julia_heo/post/2c518d95-2df0-4534-b36e-be4260dd786a/image.png" alt=""></li>
</ol>
<h3 id="amazon-cloudfront-origin">Amazon Cloudfront Origin</h3>
<p><strong>콘텐츠는 TTL 값 동안 Edge Location에 캐싱되어 낮은 지연시간으로 콘텐츠를 요청</strong></p>
<ul>
<li>정적 콘텐츠
  서버(EC2)가 필요하지 않음 이미지 등</li>
<li>동적 콘텐츠
  서버가 필요한 콘텐츠들 (로그인 자료, 실시간으로 새롭게 추가되는 게시판 등)
  이를 정적 캐싱하면 사용자는 TTL 시간동안 새롭게 추가 or 수정된 데이터를 볼 수 없게됨<img src="https://velog.velcdn.com/images/julia_heo/post/9a2019e8-e9b7-4f10-803a-ce393785fc80/image.png" alt=""></li>
</ul>
<h3 id="amazon-cloudfront-캐시-정책">Amazon Cloudfront 캐시 정책</h3>
<p>- 목적: 
캐시 정책은 CloudFront가 콘텐츠를 얼마나 오랫동안 캐시할 것인지, 어떤 HTTP 헤더, 쿼리 문자열 및 쿠키를 캐시에 포함할 것인지를 결정.</p>
<p>- 구성 요소: 캐시 정책 설정은 TTL(Time-To-Live) 값, 캐시할 헤더, 쿼리 문자열, 쿠키 등을 포함할 수 있음.<img src="https://velog.velcdn.com/images/julia_heo/post/37a684e2-803d-4127-b24e-44095bcfb8e4/image.png" alt=""></p>
<h3 id="amazon-cloudfront-oai">Amazon Cloudfront OAI</h3>
<p>OAI (Origin Access Identity):</p>
<p>CloudFront가 S3에 저장된 _Private 객체_에 액세스 할 수 있도록 하는 특별한 식별자
외부 사용자가 S3 Endpoint를 통한 직접적인 요청이 아닌 CF의 배포를 통해 접근하도록 구성하고 싶다면 OAI를 고려할 수 있음
S3 Origin 보안 강화를 위해 요청을 식별하는 데 사용되는 S3만을 위한 CloudFront 서비스 (S3의 ‘Principal’을 통해 참조)</p>
<h3 id="amazon-cloudfront-oac">Amazon Cloudfront OAC</h3>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/c18dd0a2-47ad-40eb-8450-131e0648a85d/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/5ff4ff82-e710-4b3c-b992-755b8b905cac/image.png" alt=""></p>
<h3 id="amazon-cloudfront-응답-헤더-정책">Amazon Cloudfront 응답 헤더 정책</h3>
<p>CloudFront가 사용자에게 콘텐츠를 전달할 때 HTTP 응답에 추가할 헤더를 정의하는 기능</p>
<ul>
<li>보안 헤더 추가: 보안 관련 HTTP 헤더를 응답에 자동으로 추가가능</li>
<li>사용자 정의 헤더: 특정 사용자 정의 헤더를 추가하여 응답을 더 유용하게 만들 수 있음 (애플리케이션 버전, 캐시 상태 등)</li>
<li>캐싱 동작 제어: Cache-Control 헤더를 통해 캐시 서버와 브라우저가 콘텐츠를 캐싱하는 방식을 제어할 수 있음 - CORS(Cross-Origin Resource Sharing) 설정: 다른 도메인의 웹 페이지에서 리소스를 요청할 수 있도록 허용하는 Access-Control-Allow-Origin과 같은 CORS 관련 헤더를 설정 가능</li>
<li>성능 최적화: 성능과 관련된 헤더를 추가하여 브라우저의 렌더링 성능을 개선할 수 있음 
(Vary 헤더를 사용하여 다양한 장치 유형에 따라 적절한 콘텐츠를 제공)</li>
</ul>
<h3 id="amazon-cloudfront-보안-access-구성-및-제한">Amazon Cloudfront 보안 Access 구성 및 제한</h3>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/f7e0d34c-16ea-4e5c-93e2-56ddc45a575b/image.png" alt="">
<strong>Signed Url</strong>
개별 파일에 대한 access 제공 (파일 하나당 하나의 url) 만료시간, IP주소 범위 등 지정
ex. 쿠키 구운 구독자에게만 웹툰을 제공</p>
<p><strong>Signed Cookie</strong>
다수의 파일에 대한 access 제공 (다수의 파일에 하나의 signed cookie)
만료시간, IP주소 범위 등 지정
ex. 로그인 한 유료회원에게 콘텐츠를 제공</p>
<h3 id="amazon-cloudfront-pricing">Amazon Cloudfront Pricing</h3>
<ul>
<li>서버 Edge Location마다 데이터 전송 비용이 다름</li>
<li>다양한 Price Class 존재
Price Class All
Price Class 200 
Price Class 100</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SAA] 8 고가용성 및 스케일링성: ELB 및 ASG]]></title>
            <link>https://velog.io/@julia_heo/SAA-8-%EA%B3%A0%EA%B0%80%EC%9A%A9%EC%84%B1-%EB%B0%8F-%EC%8A%A4%EC%BC%80%EC%9D%BC%EB%A7%81%EC%84%B1-ELB-%EB%B0%8F-ASG</link>
            <guid>https://velog.io/@julia_heo/SAA-8-%EA%B3%A0%EA%B0%80%EC%9A%A9%EC%84%B1-%EB%B0%8F-%EC%8A%A4%EC%BC%80%EC%9D%BC%EB%A7%81%EC%84%B1-ELB-%EB%B0%8F-ASG</guid>
            <pubDate>Sat, 25 Nov 2023 23:57:54 GMT</pubDate>
            <description><![CDATA[<h2 id="scalability확장성-수직-확장성-수평확장성">Scalability(확장성): 수직 확장성, 수평확장성</h2>
<p>수직확장성: 인스턴스 크기 확장</p>
<p>수평확장성: 인스턴스 개수 확장+분배시스템 - scale out/in</p>
<h2 id="availability고가용성">Availability(고가용성)</h2>
<p>애플리케이션 또는 시스템을 둘 이상의 데이터센터에서 가동중</p>
<p>→ 고가용성으로 데이터 손실 없이 계속해서 작동하도록</p>
<h1 id="elastic-load-balancing">Elastic Load Balancing</h1>
<p>: 트래픽을 서버들로 전달: 부하 분산, 장애x</p>
<p>aws가 업그레이드, 유지관리, 고가용성 책임짐</p>
<ul>
<li><p>Health Check: 로드밸런서가 ec2인스턴스 작동 올바르게 되는지 확인</p>
<p>  포트와 라우터에서 진행, 200응답 아니면 트래픽 안보냄</p>
<p>  대상 그룹 level</p>
</li>
<li><p>ec2보안그룹을 로드밸런서 그룹으로 연결</p>
<p>  ec2 인스턴스가 로드 밸런서에서 온 트래픽만을 허용</p>
<p>  ‼️이거 우리가 전에 어려워했던거!! 이렇게 쓰인다고 이해하니까 완전 알겠음</p>
</li>
<li><p>애플리케이션 서버는 클라이언트의 IP를 직접 못봄</p>
</li>
<li><p>종류</p>
<ul>
<li><p>Classic Load Balancer</p>
</li>
<li><p>Application Load Balancer</p>
<ul>
<li><p>HTTP 전용 로드밸런서 (7Layer)</p>
</li>
<li><p>HTTP에서 HTTPS로 트래픽을 자동 리다이렉트 가능</p>
</li>
<li><p>다른 대상그룹에 라우팅 가능(path, hostname, query string, header)</p>
<p>  ex. 각 대상그룹마다 path에 따라 검색만. 조회만.. 다른 작업 처리 가능</p>
</li>
<li><p>대상그룹: ec2 instance, ecs task, Lambda function, (사설)IP addr</p>
</li>
<li><p>도커, ECS에 적합 - 포트 매핑 기능</p>
</li>
<li><p>실습: 
두 인스턴스 대상그룹에 넣고 ALB만들어 새로고침 계속하면 두 ec2인스턴스로 부하 분산
하나 끄면 다른데로만 연결됨
ALB &gt; Listener에서 요청별로 (다른 대상그룹으로 연결/다른 url로 연결/바꿔서 다른 결과 반환) 가능 : 규칙 순서대로 우선순위</p>
</li>
</ul>
</li>
<li><p>Network Load Balancer</p>
<ul>
<li>TCP와 UDP 트래픽 (4Layer)</li>
<li>가용 영역별로 하나의 고정 IP
⭐️1~3개의 IP로만 액세스할 수 있는 애플리케이션을 만들라 →NLB</li>
<li>대상그룹: ec2 instance, private IP addr, ALB</li>
<li>Health Check - TCP, HTTP, HTTPs</li>
</ul>
</li>
<li><p>Gateway Load Balancer</p>
<ul>
<li>네트워크 계층 3Layer</li>
<li>배포 및 확장과 AWS의 타사 네트워크 가상 어플라이언스의 플릿 관리에 사용</li>
<li>애플리케이션에 도달하기 전에 모든 트래픽 검사 - 방화벽, 침입탐지에 강함</li>
<li>기능: 투명 네트워크 게이트웨이 / 로드밸런서</li>
<li>⭐️6081번 포트의 GENEVE 프로토콜을 사용 =GWLB</li>
<li>대상그룹: EC2 instance, private IP addr
  <img src="https://velog.velcdn.com/images/julia_heo/post/904a723d-2f4c-4a93-ad1e-cb46f1224ec0/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="⭐️sticky-sessions-session-affinity">⭐️Sticky Sessions (Session Affinity)</h3>
<p>특정 세션의 요청을 처음 처리한 서버로만 전송하는 것- 쿠키 사용</p>
<p>CLB, ALB 대상그룹에 설정하는 옵션</p>
<p>쿠키: </p>
<p>&nbsp; &nbsp; &nbsp; Application-based Cookies</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 대상으로 생성된 사용자 정의 쿠키 (애플리케이션에 필요한 모든 사용자 정의 속성을 포함)</p>
<p>&nbsp; &nbsp; &nbsp; Duration-based Cookies</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 로드밸런서에서 생성한 기간 기반으로 만료됨</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ALB에서 이름 - AWSALB</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CLB에서 이름 - AWSELB</p>
<h1 id="cross-zone-load-balancing">Cross-Zone Load Balancing</h1>
<p>가용영역에 상관없이 모든 인스턴스에 부하를 고르게 분배</p>
<p>아니라면) az에게 고르게 분배, 각 az안에서 인스턴스들에 분배</p>
<p>CLB, ALB에서 default 활성화, 데이터를 다른 az로 옮길때 비용X</p>
<p>NLB, NWLB는 비활성화, 비용O</p>
<h1 id="ssltls-암호화-기반-인터넷-보안-프로토콜">SSL/TLS 암호화 기반 인터넷 보안 프로토콜</h1>
<p>SSL(보안 소켓 계층): 클라이언트와 로드 밸런서 사이에서 트래픽이 이동하는 동안 암호화(=in-flight 암호화),  송신자와 수신자 측에서만 복호화 </p>
<p>TLS(전송 소켓 계층):새로운 버전의 SSL - 요즘 씀 걍 SSL이라부름</p>
<p>Public SSL 인증서는 인증기관CA에서 발급.  주기적으로 갱신.</p>
<p>HTTPs의 s가 SSL 인증서 써서 암호화해 안전하다는 의미</p>
<p>ACM - AWS 인증서 관리자</p>
<ul>
<li><p>Server Name Indication (SNI) 서버 이름 표시</p>
<p>  여러 개의 SSL 인증서를 하나의 웹 서버에 로드해 하나의 웹 서버가 여러 개의 웹 사이트 지원하도록 함</p>
<p>  즉, 단일 IP주소에서 여러 SSL 인증서를 호스팅할수있도록 도와줌</p>
<p>  클라이언트가 대상 서버의 호스트 이름을 지정 → 서버가 어떤 인증서 로드할지 알게됨</p>
<p>  ALB, NLB만</p>
</li>
</ul>
<h1 id="⭐️connection-draining-deregistration-delay">⭐️Connection Draining (Deregistration Delay)</h1>
<p>인스턴스가 등록 취소 또는 비정상적인 상태일 때 인스턴스에 어느 정도의 시간을 주어 in-flight 요청을 완료할 수 있도록 만들어 주는 기능</p>
<p>연결이 draining 되면, 즉 인스턴스가 draining 되면 ELB는 등록 취소 중인 인스턴스로는 새로운 요청을 보내지 않게 된다.(새로운 요청은 shut down)</p>
<p>업로드와 같이 오래 걸리는 작업인 경우 파라미터를 높게 설정해 인스턴스 장애 복구 등의 작업을 진행</p>
<h1 id="auto-scaling-group">Auto Scaling Group</h1>
<p>ASG를 통해 scale-out/scale-in</p>
<p>로드밸런서와 패어링하면 자동으로 모든 ec2인스턴스가 로드밸런서에 연결</p>
<p>인스턴스 비정상이면 종료하고 대체할 ec2인스턴스 생성</p>
<p>Min 용량/Max 용량/desired 용량, Lanch Template 설정</p>
<h3 id="cloudwatch가-auto-scaling-alarms">CloudWatch가 Auto Scaling Alarms</h3>
<h4 id="scaling-policies">Scaling Policies</h4>
<ul>
<li>Target Tracking Scaling - ex. 평균 CPU 설정</li>
<li>Simple / Step Scaling  -CloudWatch 알림 생성할때 인스턴스 생성 제거 조건 설정</li>
<li>Scheduled Actions - 시간별 사용패턴 기준으로 scaling 여부 예측</li>
<li>Predictive Scaling- 지속적으로 부하를 예측하고 이를 통해 다음 스케일링을 예측</li>
</ul>
<h4 id="스케일링의-지표">스케일링의 지표</h4>
<ul>
<li>CPUUtilization - 평균 CPU 사용량</li>
<li>RequestCountPerTarget - 인스턴스 당 요청의 수</li>
<li>Average Network In / Out</li>
<li>커스텀 지표</li>
</ul>
<h4 id="⭐️scaling-cooldowns">⭐️Scaling Cooldowns</h4>
<ul>
<li>스케일링 발생 후 휴지 기간 가짐</li>
<li>휴지기간동안 추가 인스턴스 실행/종료 불가</li>
</ul>
<hr>
<h3 id="용어-정리">용어 정리</h3>
<p>** Elastic Load Balancing</p>
<p>종류</p>
<ul>
<li>Classic Load Balancer</li>
<li>Application Load Balancer</li>
<li>Network Load Balancer</li>
<li>Gateway Load Balancer</li>
</ul>
<p>설정</p>
<p>Sticky Sessions (Session Affinity)</p>
<ul>
<li>쿠키</li>
</ul>
<p>Cross-Zone Load Balancing</p>
<p>Connection Draining (Deregistration Delay)</p>
<p>SSL/TLS</p>
<ul>
<li>SNI 서버 이름 표시</li>
</ul>
<blockquote>
<p>로드밸런서랑 SSL이랑 뭔상관!</p>
</blockquote>
<p>로드밸런서로 SSL 적용</p>
<blockquote>
</blockquote>
<p>SSL이 적용된 로드밸런서에서의 호스트 기반 라우팅 동작 방식</p>
<blockquote>
</blockquote>
<p>Network Load Balancer, 이제 서버 이름 표시(SNI)를 사용하여 여러 TLS 인증서 지원</p>
<p>Auto Scaling Group에 로드밸런서를 연결</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SAA] 7 EC2 Instance Storage]]></title>
            <link>https://velog.io/@julia_heo/SAA-7-EC2-Instance-Storage</link>
            <guid>https://velog.io/@julia_heo/SAA-7-EC2-Instance-Storage</guid>
            <pubDate>Sat, 25 Nov 2023 23:47:45 GMT</pubDate>
            <description><![CDATA[<h2 id="ebs-volume">EBS Volume</h2>
<p>AZ에 속하는 network drive</p>
<p>Snapshots (AZ상관x)</p>
<p><strong>Encryption: 스냅샷 →스냅샷암호화→그걸로 EBS생성 →EBS암호화</strong></p>
<ul>
<li>General Purpose SSD</li>
<li>Provisioned IOPS (PIOPS) SSD</li>
<li>Hard Disk Drives (HDD)</li>
</ul>
<p><strong>EBS Multi-Attach – io1/io2 family</strong></p>
<p>하나의 볼륨에 여러ec2를 연결
최대 16개의 EC2 Instances까지</p>
<h2 id="amiamazon-machine-image-customization-of-an-ec2-instance">AMI(Amazon Machine Image) :customization of an EC2 instance</h2>
<p>region에 속함</p>
<br>

<blockquote>
<p>** 난 이 상황에 AMI가 왜나오는지 이해가 안됐다!!<br>
<a href="https://docs.aws.amazon.com/ko_kr/prescriptive-guidance/latest/backup-recovery/new-ebs-volume-backups.html">AMI와 EBS 스냅샷을 사용하여 EBS 볼륨 백업 생성 - AWS규범적 지침</a> <br>
AMI에는 이미지가 생성될 때 인스턴스에 연결된 EBS 볼륨의 루트 볼륨과 스냅샷이 포함</p>
</blockquote>
<h2 id="ec2-instance-store">EC2 Instance Store</h2>
<p>고성능 하드웨어 디스크</p>
<p>I/O performance 좋음</p>
<h2 id="efs--elastic-file-system">EFS – Elastic File System</h2>
<p>AZ상관x</p>
<p>EBS보다 3배 큼</p>
<p>하나의 EFS에 여러 EC2연결 가능</p>
<p>⭐️ 언제 EFS를 사용해야 하는지, 네트워크 파일 시스템에 어떤 옵션을 설정해야 하는지
Use cases: content management, web serving, data sharing,Wordpress</p>
<h3 id="efs-vs-ebs">EFS vs EBS</h3>
<p><strong>EFS</strong></p>
<p>네트워크 파일 시스템
여러 az의 수백개의 instance에 연결</p>
<p><strong>EBS</strong></p>
<p>한번에 하나의 인스턴스에 attach</p>
<p>AZ안에 snapshot을 통해 다른 AZ로 복제해 이동</p>
<p>인스턴스 root ebs는 인스턴스 종료되면 종료
<br></p>
<p><strong>인스턴스 스토어</strong></p>
<p>물리적으로 EC2에 연결, instance 종료→ 데이터x</p>
<br>

<br>

<p>+퀴즈에 영역 문제가 많다‼️</p>
<p>EBS - az</p>
<p>ami - region</p>
<p>EFS- 상관x</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SAA] 6 솔루션스 아키텍트 어소시에이트 레벨]]></title>
            <link>https://velog.io/@julia_heo/SAA-6-%EC%86%94%EB%A3%A8%EC%85%98%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%ED%8A%B8-%EC%96%B4%EC%86%8C%EC%8B%9C%EC%97%90%EC%9D%B4%ED%8A%B8-%EB%A0%88%EB%B2%A8</link>
            <guid>https://velog.io/@julia_heo/SAA-6-%EC%86%94%EB%A3%A8%EC%85%98%EC%8A%A4-%EC%95%84%ED%82%A4%ED%85%8D%ED%8A%B8-%EC%96%B4%EC%86%8C%EC%8B%9C%EC%97%90%EC%9D%B4%ED%8A%B8-%EB%A0%88%EB%B2%A8</guid>
            <pubDate>Sat, 25 Nov 2023 23:41:35 GMT</pubDate>
            <description><![CDATA[<h1 id="classic-ports-to-know"><strong>Classic Ports to know</strong></h1>
<p>22 = SSH (Secure Shell) - log into a Linux instance</p>
<p>21 = FTP (File Transfer Protocol) – upload files into a file share</p>
<p>22 = SFTP (Secure File Transfer Protocol) – upload files using SSH</p>
<p>80 = HTTP – access unsecured websites</p>
<p>443 = HTTPS – access secured websites</p>
<p>3389 = RDP (Remote Desktop Protocol) – log into a Windows instance</p>
<p>(EC2 Instances Purchasing Options)</p>
<h1 id="dedicated-instances-vs-dedicated-hosts">dedicated instances vs dedicated hosts</h1>
<p><strong>EC2 전용 인스턴스</strong></p>
<p>그 인스턴스가 할당된 물리적 서버는 같은 AWS 계정(account)의 인스턴스만 할당되어 사용하는 옵션</p>
<p><strong>전용 인스턴스</strong> 방식은 비어있는 호텔을 골랐을 때만 가능합니다. 룸이 10개라 하더라도 1개만 내가 먼저 예약하면 나머지 9개 룸은 누구도 예약할 수 없습니다. 즉 나만의 호텔을 잠깐 동안이지만 소유할 수 있습니다. 나만 온전히 사용할 수 있지만 비용은 On-Demand보다 훨씬 비싼 것은 어쩔 수 없습니다. 사용이 완료되면 사용 기간만큼 10개 룸의 사용 요금을 내고 퇴실합니다.</p>
<p>필요한 인스턴스만 빌림</p>
<p><strong>EC2 전용 호스트</strong></p>
<p>EC2 인스턴스가 있는 물리적 서버를 구매하여 전용 용량, 전담 호스트를 사용하는 방식 (물리적 서버를 점유)</p>
<p><strong>전용 호스트</strong> 방식은 처음부터 이 호텔을 내가 작정하고 짓고 나만 소유하는 방식입니다. 호텔 룸이 비어있다고 하더라도 다른 누구도 사용할 수 없습니다. 오롯이 나만 소유할 수 있는 변경 없는 옵션이며 사용이 끝나면 모든 비용 처리를 해야 합니다. 가장 비싼 옵션입니다.</p>
<p>⇒ 더 조사!!</p>
<p>회사 차리면 필요한 물리적 hw 전체를 예약해버림</p>
<h1 id="ec2-spot-instances"><strong>EC2 Spot Instances</strong></h1>
<p>Not suitable for critical jobs or databases</p>
<h2 id="종료하는법">종료하는법</h2>
<p>spot 요청 취소 후 인스턴스 종료 (내가 요청을 취소 안하면 aws에서 또 새로운 인스턴스를 만든다)</p>
<h1 id="spot-fleets">Spot Fleets</h1>
<p><a href="https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/spot-fleet.html">https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/spot-fleet.html</a></p>
<p>스팟 인스턴스 및 선택적으로 온 디맨드 인스턴스의 모음</p>
<p>Spot Fleet는 Spot Fleet 요청에서 지정한 목표 용량을 충족하기 위해 스팟 인스턴스 및 온디맨드 인스턴스 수를 시작</p>
<p>사용 가능한 용량이 있고 요청에서 지정한 최대 가격이 현재 스팟 가격을 초과하면 스팟 인스턴스에 대한 요청이 이행</p>
<p>Spot Fleet은 또한 스팟 인스턴스가 중단된 경우 대상 용량 집합을 유지하려고 시도</p>
<p>Spot Fleet는 목표 용량을 가격 제한과 일치시키려고 시도함</p>
<h1 id="place-group">Place Group</h1>
<h3 id="parition">Parition</h3>
<p>동일한 리전의 여러 가용 영역에서 파티션을 가짐</p>
<p><strong>파티션 1</strong>, <strong>파티션 2</strong> 및 <strong>파티션 3</strong>이 있는 파티션 배치 그룹에 배치된 인스턴스를 보여줍니다. 각 파티션은 여러 인스턴스로 구성됩니다. 각 파티션에 있는 인스턴스는 다른 파티션에 있는 인스턴스와 랙을 공유하지 않기 때문에 단일 하드웨어 장애의 영향을 관련 파티션으로만 국한할 수 있습니다.<img src="https://velog.velcdn.com/images/julia_heo/post/d8d7a791-2417-475b-9d24-7d1b38c6a5a0/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/ec1b2239-2327-4586-8119-3fb66c625657/image.png" alt=""></p>
<h3 id="spread">Spread</h3>
<p>각각 고유한 하드웨어에 배치된 인스턴스 그룹</p>
<p>7개의 인스턴스가 7개의 서로 다른 랙에 배치되며, 랙마다 자체 네트워크 및 전원이 있습니다.
각 인스턴스는 고유한 랙에 배치. 각 랙에는 인스턴스가 최대 1개 <img src="https://velog.velcdn.com/images/julia_heo/post/c17918a2-56d0-409f-bc4d-eb4f8d643dcf/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SAA] 5 EC2 기초]]></title>
            <link>https://velog.io/@julia_heo/SAA-5-EC2-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@julia_heo/SAA-5-EC2-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Sat, 25 Nov 2023 23:37:31 GMT</pubDate>
            <description><![CDATA[<h1 id="amazon-ec2">Amazon EC2</h1>
<p>EC2 = Elastic Compute Cloud = Infrastructure as a Service (IaaS)</p>
<p>구성:</p>
<p>Renting virtual machines (EC2)- ec2인스턴스</p>
<p>Storing data on virtual drives (EBS)</p>
<p>Distributing load across machines (ELB)</p>
<p>Scaling the services using an auto-scaling group (ASG)</p>
<h1 id="ec2-sizing--configuration-options"><strong>EC2 sizing &amp; configuration options</strong></h1>
<p>Operating System (OS): Linux / Windows / Mac OS</p>
<p>compute power &amp; cores (CPU)</p>
<p>random-access memory (RAM)</p>
<p>storage space:</p>
<p>Network-attached (EBS &amp; EFS)
hardware (EC2 Instance Store)</p>
<p>Network card: speed of the card, Public IP address</p>
<p>Firewall rules: security group</p>
<p>Bootstrap script (configure at first launch): EC2 User Data</p>
<hr>
<h1 id="ec2-user-data"><strong>EC2 User Data</strong></h1>
<p>EC2 User data script: EC2 사용자 데이터 스크립트를 사용해 인스턴스 부트 스트래핑 가능</p>
<p>bootstrapping: 머신이 작동될때 명령을 시작하는 것</p>
<p>처음 시작할때만 스크립트 실행</p>
<p>부팅 작업 자동화:</p>
<p>업데이트 설치
sw 설치
인터넷에서 common files 다운 ..</p>
<p>사용자 데이터 스크립트에 작업 추가할수록 부팅 시 인스턴스가 할일이 늘어남</p>
<h1 id="ec2-instancetypes">EC2 InstanceTypes</h1>
<p>명명규칙<img src="https://velog.velcdn.com/images/julia_heo/post/cb030745-697f-402a-8428-e25cb70e39a9/image.png" alt="">m: instance class
5: generation (AWS improves them over time)
2xlarge: size within the instance class</p>
<h3 id="general-purpose"><strong>General Purpose</strong></h3>
<p>Balance between: Compute / Memory / Networking
(t로 시작)</p>
<h3 id="compute-optimized"><strong>Compute Optimized</strong></h3>
<p>high performance 필요로 하는 compute-intensive tasks</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;Batch processing workloads
&nbsp;&nbsp;&nbsp;&nbsp;Media transcoding
&nbsp;&nbsp;&nbsp;&nbsp;High performance web servers
&nbsp;&nbsp;&nbsp;&nbsp;High performance computing (HPC)
&nbsp;&nbsp;&nbsp;&nbsp;Scientific modeling &amp; machine learning
&nbsp;&nbsp;&nbsp;&nbsp;Dedicated gaming servers</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;(c로 시작)</p>
<h3 id="memory-optimized"><strong>Memory Optimized</strong></h3>
<p>Fast performance for workloads that process large data sets in memory</p>
<p>(메모리에서 빠른 성능)</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;High performance, relational/non-relational databases
&nbsp;&nbsp;&nbsp;&nbsp;Distributed web scale cache stores
&nbsp;&nbsp;&nbsp;&nbsp;In-memory databases optimized for BI (business intelligence)
&nbsp;&nbsp;&nbsp;&nbsp;Applications performing real-time processing of big unstructured data</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;R로 시작</p>
<h3 id="storage-optimized"><strong>Storage Optimized</strong></h3>
<p>high, sequential read and write access to large data sets on local storage필요한 storage-intensive tasks</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;High frequency online transaction processing (OLTP) systems
&nbsp;&nbsp;&nbsp;&nbsp;Relational &amp; NoSQL databases
&nbsp;&nbsp;&nbsp;&nbsp;Cache for in-memory databases (for example, Redis)
&nbsp;&nbsp;&nbsp;&nbsp;Data warehousing applications
&nbsp;&nbsp;&nbsp;&nbsp;Distributed file systems (분산파일 시스템)</p>
<h1 id="security-groups">Security Groups</h1>
<p>control how traffic is allowed into or out of our EC2 Instances</p>
<p>only contain rules</p>
<p>reference by IP / by security group<img src="https://velog.velcdn.com/images/julia_heo/post/e4c480d2-ede4-403b-b08b-d07bd0513070/image.png" alt="">
They regulate:
• Access to Ports
• Authorised IP ranges – IPv4 and IPv6
• Control of inbound network (from other to the instance)
• Control of outbound network (from the instance to other)</p>
<p>인바운드는 허용하는 ip를 작성, 아웃바운드는 default로 모두 허용<img src="https://velog.velcdn.com/images/julia_heo/post/91a9beb4-1c7a-4230-9348-ccfa2fcfed99/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SAA] 4 Hands-on]]></title>
            <link>https://velog.io/@julia_heo/SAA-4-Hands-on</link>
            <guid>https://velog.io/@julia_heo/SAA-4-Hands-on</guid>
            <pubDate>Sat, 25 Nov 2023 23:33:48 GMT</pubDate>
            <description><![CDATA[<h1 id="iam-user만들기">IAM user만들기</h1>
<h2 id="iam">IAM</h2>
<p>오른쪽 상단에 아이디만 뜨면 루트 계정인것</p>
<h3 id="사용자-생성">사용자 생성</h3>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/3e5bb186-9e64-4a2c-bc1a-00fdd4b259b5/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/fcc65ec5-adb6-4700-8335-f1bf552f1fd9/image.png" alt="">그룹을 만들거나 직접(인라인 정책) 권한 부여해야함<img src="https://velog.velcdn.com/images/julia_heo/post/2f8b13d8-06a5-4b43-a510-2dae252288b6/image.png" alt="">
그룹 만들어서 진행해보자<img src="https://velog.velcdn.com/images/julia_heo/post/dfcc94a1-0d9a-4daa-ad6a-104d9a947be0/image.png" alt="">aws의 거의 모든 리소스엔 항상 태그가 존재(optional) <img src="https://velog.velcdn.com/images/julia_heo/post/6347067b-bdcc-48f4-ac1a-8ff109233d13/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/b5ba604a-abff-44ae-8323-05f1c9b84fff/image.png" alt="">생성 완료!<img src="https://velog.velcdn.com/images/julia_heo/post/f50d08b7-0029-4c46-9547-6d008f97f3f9/image.png" alt=""></p>
<h4 id="-루트-사용자-계정-별칭-생성">+ 루트 사용자 계정 별칭 생성</h4>
<p>계정 별칭: 더 빠르게 로그인하기 위해 설정할수 있는 기능<img src="https://velog.velcdn.com/images/julia_heo/post/78fda260-3f8e-4537-b53a-740fb73ecc46/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/04381587-0a99-4b7d-b34c-fe29a66d441e/image.png" alt=""></p>
<p>이어서..
stephan으로 로그인해보자<img src="https://velog.velcdn.com/images/julia_heo/post/975a7dd7-e568-492a-8fa3-d15113e26c6c/image.png" alt="">url접근- 시크릿창 or 다른 웹 브라우저<img src="https://velog.velcdn.com/images/julia_heo/post/72d3bdca-515d-4fe7-9e13-51cf333f97ba/image.png" alt="">콘솔에 IAM user로 로그인됨
<br>
루트 사용자 vs IAM user<img src="https://velog.velcdn.com/images/julia_heo/post/4711fce1-d780-4e9f-ae28-3fc223310a4a/image.png" alt=""></p>
<hr>
<h1 id="정책-변화주고-확인정책-작동방법">정책 변화주고 확인정책 작동방법</h1>
<p>권한 변화주고 확인하기
지금 iam user도 콘솔 잘 보임(왼쪽 루트사용자, 오른쪽 stephan)<img src="https://velog.velcdn.com/images/julia_heo/post/e7ee81d6-909a-493f-89c8-8bd36166045a/image.png" alt="">관리자 그룹에서 stephan 삭제<img src="https://velog.velcdn.com/images/julia_heo/post/08a09307-af7e-4f6d-9c0c-0ab5bb89e569/image.png" alt="">권한 없어져서 안보임<img src="https://velog.velcdn.com/images/julia_heo/post/eafe3563-4bdf-4096-8c06-6155476da7e8/image.png" alt="">다시 root account에서 사용자에 들어가서 </p>
<ol>
<li>직접 권한 연결해주면 원래대로 돌아감<img src="https://velog.velcdn.com/images/julia_heo/post/212202e8-9f2b-44ae-aae9-48079064aa91/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/e97b139b-1320-4e1f-9fe7-274f54bf8dd6/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/38175fce-b050-4065-bd85-6b003c530d08/image.png" alt="">=&gt;<img src="https://velog.velcdn.com/images/julia_heo/post/ef268d30-f131-4212-897d-0e8e3ef0ac01/image.png" alt="">그룹 생성의 권한은 없음<img src="https://velog.velcdn.com/images/julia_heo/post/81b53305-e674-4d78-9167-c2678d9ec6a2/image.png" alt=""></li>
<li>그룹 생성해 stephan 추가<img src="https://velog.velcdn.com/images/julia_heo/post/2a4e1fca-b980-4a10-9d0f-2840f686f620/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/b91984b5-3ed3-479f-8182-cde0e99e0e15/image.png" alt="">⇒ 두 그룹에 속함</li>
</ol>
<h1 id="정책-작동방법">정책 작동방법</h1>
<p>정책 작동 방법:<img src="https://velog.velcdn.com/images/julia_heo/post/cec4a2a8-adb6-462f-8ebe-616801ad2d46/image.png" alt="">정책 생성도 가능:<img src="https://velog.velcdn.com/images/julia_heo/post/c2161ed9-cbf8-474a-a055-87665f7789cf/image.png" alt="">visual editor에서 클릭하고 json으로 가면 추가되어있음</p>
<hr>
<h1 id="mfa">MFA</h1>
<p>비밀번호 규칙 변경 가능
<img src="https://velog.velcdn.com/images/julia_heo/post/fd1e734f-eefe-4a07-9661-b27dcb483edd/image.png" alt=""></p>
<p>루트계정에 대한 멀티팩터 인증 설정(루트계정으로 로그인했을 때만 접근 가능)<img src="https://velog.velcdn.com/images/julia_heo/post/67a7c3bd-c862-4494-9725-5243e59ffef7/image.png" alt=""> <a href="https://velog.io/@julia_heo/Hands-on-0926-MFA-%EC%84%A4%EC%A0%95">이전에 했던 실습이다</a></p>
<hr>
<h1 id="cli터미널-cloudshell">CLI(터미널, CloudShell)</h1>
<h3 id="터미널">터미널</h3>
<p><a href="https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html">https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html</a>
<img src="https://velog.velcdn.com/images/julia_heo/post/9e1b508a-3610-4dde-ab21-cc07b756cfd1/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/7721ea08-404a-409c-a63c-e9710716a5e8/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/dc12ec9c-96b7-43e0-833b-d973387ba97e/image.png" alt="">액세스 키 확인 가능한 유일한 시간</p>
<p><code>aws configure</code></p>
<p><code>aws iam list-users</code></p>
<p>⇒ 모든 사용자 나열</p>
<p>관련 권한 지우면 터미널창에서 이 명령어 수행도 안됨</p>
<p>CLI권한은 IAM콘솔에서 얻는 권한과 완전히 같다
<br></p>
<p>터미널 대안:</p>
<h3 id="aws-cloudshell-리전-가용성">AWS CloudShell: 리전 가용성</h3>
<p>AWS CloudShell는 다음의 AWS 리전에서 사용할 수 있습니다.</p>
<ul>
<li>미국 동부(오하이오)</li>
<li>미국 동부(버지니아 북부)</li>
<li>미국 서부(오리곤)</li>
<li>아시아 태평양(뭄바이)</li>
<li>아시아 태평양(시드니)</li>
<li>아시아 태평양(도쿄)</li>
<li>유럽(프랑크푸르트)</li>
<li>유럽(아일랜드)</li>
</ul>
<p>CloudShell은 AWS 클라우드에서 무료로 사용 가능한 터미널같은 개념<img src="https://velog.velcdn.com/images/julia_heo/post/d5cfdf02-f50f-4610-a081-c50334e62d1c/image.png" alt="">
<code>--region</code>: API 호출을 할 리전 지정(default 지금 로그인한 계정</p>
<p>CloudShell 전체 저장소</p>
<p>여기서 파일 생성하면 새로고침해도 그대로 존재함</p>
<p>글씨크기 테마 등 구성 가능<img src="https://velog.velcdn.com/images/julia_heo/post/ec6ba815-75bf-4c30-bbbd-948287a57569/image.png" alt="">
작업&gt;경로 쳐서 파일 다운로드 가능</p>
<hr>
<h1 id="iam-role-생성">IAM Role 생성</h1>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/386f03e6-f5b0-4203-a06f-130936cd1ec6/image.png" alt="">사용할 서비스 선택<img src="https://velog.velcdn.com/images/julia_heo/post/7d123f10-e504-4178-9fee-372d0d76f75b/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/13c4643e-da18-4ac3-a7bf-3accbde74d33/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/8629d91f-4508-4605-b782-454437cb3fef/image.png" alt="">나중에 사용할것임</p>
<hr>
<h1 id="iam-security-tools-생성-확인">IAM Security Tools 생성, 확인</h1>
<p>자격 증명 보고서 만들자<img src="https://velog.velcdn.com/images/julia_heo/post/ebc478d2-1008-45eb-bc4e-888412036cdc/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/fad00dc2-067b-41d8-9661-66754dfcca89/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/23ce2369-d30a-4800-a90f-3f5098b00b9c/image.png" alt="">최근 4시간만 추적
한번도 접속 안했다거나 하는 기록이 권한 변경의 근거가될수있음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SAA] 4 IAM 및 AWS CLI]]></title>
            <link>https://velog.io/@julia_heo/SAA-4-IAM-%EB%B0%8F-AWS-CLI</link>
            <guid>https://velog.io/@julia_heo/SAA-4-IAM-%EB%B0%8F-AWS-CLI</guid>
            <pubDate>Sat, 25 Nov 2023 23:13:06 GMT</pubDate>
            <description><![CDATA[<h1 id="users--groups"><strong>Users &amp; Groups</strong></h1>
<p>IAM: Identity and Access Management</p>
<p>Global service</p>
<p><strong>Root account</strong>: 회원가입하면 default로 생김</p>
<p><strong>Users</strong>: 조직에 있는 사람. 0개 이상의 그룹에 속한다</p>
<p><strong>Groups</strong>: User을 포함. 다른 그룹을 포함X</p>
<p><strong>Permissions</strong>: User나 Group에 할당하는 JSON 문서인 policy가 정의하는 권한</p>
<p>최소권한 원칙(least privilege principle)</p>
<h1 id="iam-policies-structure">IAM Policies Structure</h1>
<p>⭐️구성⭐️</p>
<ul>
<li>Version: policy language version. 항상 “2012-10-17” 포함</li>
<li>Id: 정책의 식별자 (optional)</li>
<li>Statement (required) 1개 이상의 statements<ul>
<li>Sid: statement의 식별자 (optional)</li>
<li>Effect: 허용할지 거부할지 (Allow / Deny)</li>
<li>Principal: 적용할 account/user/role</li>
<li>Action: 허용or거부할 action의 list</li>
<li>Resource: action적용할 리소스 list</li>
<li>Condition: 언제 영향줄지 (optional)
<img src="https://velog.velcdn.com/images/julia_heo/post/9f9db3ac-c411-47e2-b069-b6b1df3f7bc9/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<p>그룹과 사용자 보호</p>
<p>1st:</p>
<h1 id="iam--password-policy"><strong>IAM – Password Policy</strong></h1>
<p>Strong passwords = 높은 보안</p>
<p>직접 pw policy 설정 가능:</p>
<ul>
<li>minimum password length</li>
<li>Require specific character types<ul>
<li>including uppercase letters</li>
<li>lowercase letters</li>
<li>numbers</li>
<li>non-alphanumeric character</li>
</ul>
</li>
<li>IAM user가 password 바꾸게 허용</li>
<li>특정 기간마다 꼭 바꾸도록 (password expiration)</li>
<li>password  re-use 방지</li>
</ul>
<p>2nd:</p>
<h1 id="multi-factor-authentication---mfa"><strong>Multi Factor Authentication - MFA</strong></h1>
<p>MFA = password <em>you know</em> + security device <em>you own</em>
<strong>→ 비밀번호 알아도 계정 보호됨</strong></p>
<p>⭐️<strong>MFA devices options</strong>⭐️</p>
<ul>
<li>Virtual MFA device <img src="https://velog.velcdn.com/images/julia_heo/post/8baff6b3-4b84-4610-9453-72a412825daa/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/9d9e30bf-9a4d-4525-bd59-182cc9ffa0c2/image.png" alt=""></li>
</ul>
<hr>
<h1 id="aws에-접근">AWS에 접근</h1>
<ol>
<li>AWS Management Console (password + MFA)</li>
<li>AWS Command Line Interface (CLI): access keys. 컴퓨터 터미널에서 접속</li>
<li>AWS Software Developer Kit (SDK):  for code, access keys. 애플리케이션 코드 내부</li>
</ol>
<p>Access Key ID ~= username 
Secret Access Key ~= password</p>
<p>절대공유X</p>
<h1 id="aws-cli">AWS CLI</h1>
<p>셸에서 명령어 사용해 AWS 서비스들과 상호작용하게 해주는 도구 (command-line shell)</p>
<p>모든 명령어 aws로 시작</p>
<p>aws 서비스의 공용API로 직접 access </p>
<p>리소스 관리하는 스크립트 개발해 일부 작업 자동화</p>
<p>open-source</p>
<h1 id="aws-sdk">AWS SDK</h1>
<p>AWS Software Development Kit </p>
<p>특정 언어로 된 라이브러리의 집합(언어마다 개별 SDK존재)</p>
<p>코딩을 통해 어플리케이션에 심어둠 (Embedded)</p>
<h1 id="iam-roles-for-services"><strong>IAM Roles for Services</strong></h1>
<p>policy와 같지만 사용자가 아닌 AWS 서비스가 사용</p>
<h1 id="iam-security-tools">IAM Security Tools</h1>
<p>IAM Credentials Report (account-level): 자격중명 보고서</p>
<p>계정의 사용자와 다양한 자격 증명 상태 포함</p>
<p>IAM Access Advisor (user-level)</p>
<p>사용자에게 부여된 서비스와 그 서비스에 마지막으로 엑세스한 시간 조회 가능</p>
<p>최소 권한 원칙에 유용</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SAA] 3 AWS 시작하기]]></title>
            <link>https://velog.io/@julia_heo/SAA-3-AWS-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@julia_heo/SAA-3-AWS-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 25 Nov 2023 23:07:21 GMT</pubDate>
            <description><![CDATA[<h1 id="aws">AWS</h1>
<p>Cloud Provider
use on demand(사용한 만큼 지불), scale easily(쉽게 규모 조절)<img src="https://velog.velcdn.com/images/julia_heo/post/f4433372-c8ba-4489-82fb-dfd016a3d3c9/image.png" alt="">
use case: build sophisticated, scalable applications
• Enterprise IT, Backup &amp; Storage, Big Data analytics
• Website hosting, Mobile &amp; Social Apps
• Gaming</p>
<AWS Global Infrastructure>

<h1 id="aws-regions">AWS Regions</h1>
<p>cluster of data centers
대부분 서비스가 region범위
ex. us-east-1, eu-west-3</p>
<p>선택 기준</p>
<ul>
<li>Compliance with data governance and legal requirements(데이터 통제 및 법적 요구사항 준수)</li>
<li>Proximity to customers(근접) : latency 낮도록</li>
<li>Available services within a Region: 모든 지역이 모든 서비스를 지원X</li>
<li>Pricing: 지역에 따라 가격 책정 다름</li>
</ul>
<h1 id="aws-availability-zones"><strong>AWS Availability Zones</strong></h1>
<p>지역 안에 가용영역들 존재 (3, 3~6개)</p>
<p>1개 이상의 별도의 데이터 센터들로 분리 → 재난에 안전</p>
<p>각 데이터 센터는 high bandwidth, ultra-low latency networking로 연결
<img src="https://velog.velcdn.com/images/julia_heo/post/6c733b7e-2589-4291-ad3f-d7c7463b7786/image.png" alt=""></p>
<h1 id="aws-points-of-presence-edge-locations"><strong>AWS Points of Presence (Edge Locations)</strong></h1>
<p>전세계에 펼쳐져 있고 end user에게 가장 적은 latency로 전달</p>
<p>(나중에 다룸)</p>
<p>Global ex. IAM, Route53(DNS service), CloudFront, WAF
Region-scoped ex. EC2, Elastic Beanstalk, Lambda, Rekognition</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ONCE: 사용 기술 정리]]></title>
            <link>https://velog.io/@julia_heo/ONCE-1</link>
            <guid>https://velog.io/@julia_heo/ONCE-1</guid>
            <pubDate>Fri, 24 Nov 2023 14:36:20 GMT</pubDate>
            <description><![CDATA[<h1 id="once-소개">ONCE 소개</h1>
<p>ONCE는 카드 결제 전 보유한 카드 중 결제처에서 최적의 혜택을 받을 수 있는 카드를 미리 추천해주는 AI 모바일 어플리케이션이다.
<img src="https://velog.velcdn.com/images/julia_heo/post/d4e6ae42-0a47-498b-b3b2-f82df7dc1e8c/image.png" alt=""></p>
<p>[ 매년 사라지는 카드 포인트 1000억원, 이대로 괜찮을까? ]</p>
<p>우리나라 1인당 신용카드 보유 개수는 3.6장이며, 개인의 신용카드 보유율은 80.2%에 달한다. 하지만 전체 발급 카드 중 1년 동안 사용 실적이 없는 휴면카드가 1300만장에 육박하며, 매년 사라지는 카드 포인트 1000억원이 소비자가 아닌 카드사의 부가 수입으로 들어가는 이유에 의문을 가지고 프로젝트를 시작하게 되었다.
<img src="https://velog.velcdn.com/images/julia_heo/post/1be7e1f3-50fd-4040-97d8-7c049e8dddfa/image.png" alt=""></p>
<p>필요 기술 1 : 스크래핑 기반 금융 API를 이용한 사용자 정보 조회 (*스크래핑 : 각 카드사와 통신하여 실제 데이터를 제공)</p>
<p>→ CODEF API를 이용하여 실제 카드 보유 목록, 혜택 정보 및 실적을 불러올 수 있다.
<br></p>
<p>필요 기술 2 : 크롤링을 이용한 카드사 별 카드 혜택 정보 실시간 수집</p>
<p>→ 파이썬 크롤링 라이브러리 BeautifulSoup, Selenium, Requests 등을 이용하여 카드별 혜택의 상세 정보를 불러올 수 있다.
<br>
필요 기술 3 : 자연어 처리 사전 훈련 언어 모델을 활용한 카드 혜택 학습</p>
<p>→ 사용자가 이용 중인 카드가 어떤 혜택을 제공하는지 분석하기 위해 수집한 상세 카드 혜택 정보를 BERT 모델을 활용하여 이해 및 학습시킨다.
<br>
필요 기술 4 : 트랜스포머 모델을 활용한 사용처에 맞는 최적의 카드 추천</p>
<p>→ 자연어 처리 기술을 활용하여 아이템을 벡터로 표현한 후, 사용자가 입력한 결제처와의 유사도를 계산하여 사용처에서 최적의 혜택을 제공하는 카드를 추천한다.
<br>
필요 기술 5 : 챗봇을 통한 사용자와의 상호작용</p>
<p>→ ChatGPT API를 활용하여 메인화면에서 사용자와의 주요 상호작용을 처리할 수 있도록 한다.<img src="https://velog.velcdn.com/images/julia_heo/post/6aa4ce38-18a6-473a-ba60-ac4ac39f9779/image.png" alt=""></p>
<br>

<hr>
<br>


<h1 id="codef-api를-이용해-금융정보-가져오기">CODEF API를 이용해 금융정보 가져오기</h1>
<ol>
<li><p><strong>API란</strong>?</p>
<ul>
<li><p>응용 프로그램에서 사용할 수 있도록, 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스</p>
</li>
<li><p>어떠한 응용프로그램에서 데이터를 주고 받기 위한 방법<img src="https://velog.velcdn.com/images/julia_heo/post/0682bc3e-7af0-4e54-a8bf-fe339e408ba5/image.png" alt=""></p>
</li>
</ul>
</li>
</ol>
<pre><code>**오픈 API**:

오픈API란 누구나 사용할 수 있도록 공개된 API

데이터를 표준화하고 프로그래밍해 외부 소프트웨어 개발자나 사용자가 바로 개발(어프리케이션)에 활용할 수 있는 형태의 개방 형식

업데이트가 빈번하고 활용도가 높은 대용량의 데이터를 연계 개발할 때, 날씨나 교통 정보 등 실시간 업데이트되는 데이터를 연계가 필요할 때 유용 </code></pre><br>

<ol start="2">
<li><p>ONCE에서의 필요</p>
<p> ONCE는 사용자에게 맞춤화된 서비스를 제공하기 위해서, 사용자의 카드 관련 정보들이 필요하다. </p>
<p> 기본적으로 사용자가 <strong>보유하고 있는 카드</strong>의 종류를 파악하고 있어야 하고, <strong>전월 실적</strong>을 파악해 혜택의 조건을 고려할 때 활용해야 한다. 또한 이번 달의 <strong>받은 혜택 정보</strong>와 <strong>실적</strong>이 얼마나 채워졌는지 등의 정보를 실시간 업데이트 하여 저장할 수 있어야 한다. </p>
<p> ONCE는 금융, 카드 관련 서비스로 보안 또한 중요한 지점이기 때문에, 이 정보들을 믿을 수 있는 방식으로 활용 가능한 <strong>금융 API</strong>를 활용하기로 결정하였다.</p>
</li>
</ol>
<br>

<ol start="3">
<li>CODEF API 선정 이유<br>
카드 관련 api를 제공하는 여러 사이트가 존재하고, 우리가 대학생 창업 프로젝트의 과정에서 사용할 수 있는 서비스를 조사하였다. <br>
해당 정보들이 민감한 개인정보인 만큼 <a href="https://developers.kftc.or.kr/dev">금융결제원 오픈API</a>과 <a href="https://dev.barobill.co.kr/services/card?ACE_REF=adwords_g&amp;ACE_KW=%EC%B9%B4%EB%93%9C%20%EC%82%AC%EC%9A%A9%EB%82%B4%EC%97%AD%20api&amp;gclid=CjwKCAjwu4WoBhBkEiwAojNdXjM87bQFbnzKOGtuTwFIDbbNJ6YlZuRegB79YDZg4yjHYAfnJiHWtBoC4TUQAvD_BwE">바로빌 카드 정보 조회 API</a>는 실제로 사업자 등록을 한 경우에만 회원가입이 되었고, API를 활용하기 위해서는 기업간 계약 체결을 진행해야 했기에, 현재 우리의 단계에는 적합하지 않았다.<br>
<a href="https://openapi.shinhan.com/">신한 Open API</a>에서 원하는 API를 제공 중인 것을 보았지만, 신한 카드의 정보만으로는 우리의 서비스를 완성시킬 수 없고, 다른 카드 회사들의 API를 모두 따로따로 불러오기는 어렵다는 결론을 내렸다.<br>
<a href="https://docs.tosspayments.com/common/apis/card-benefits">토스 페이먼츠 개발자센터</a>에서는 &quot;카드사 혜택 조회&quot; API를 제공중인데, 이는 하나하나 카드 상품의 혜택 정보를 원하는 우리 서비스의 의도와 맞지 않았다.<br>
이에 따라 현재 대학생 신분에 사업자 등록 없이도 돈을 지불한다면 활용이 가능하고, 실제 창업 전에 DEVELOPER 모드로 기술 검증을 해볼 수 있으며 &quot;보유 카드 목록 조회&quot;, &quot;승인 내역 조회&quot;, &quot;실적 조회&quot;가 가능한 <a href="https://codef.io/">CODEF API</a>를 활용하기로 결정하였다.</li>
</ol>
<br>

<ol start="4">
<li>API 활용 과정
CODEF에서 정보를 제공해주는 과정은 다음과 같다.<img src="https://velog.velcdn.com/images/julia_heo/post/56b75996-2d48-41f1-bf2d-c7c0fc3b5ed8/image.png" alt="">CODEF의 모든 api를 사용하기 위해서는 헤더에 accessToken이, body에 connectedID가 필요하다. 
connectedID는 CODEF에서 사용하는 개념으로, 한개의 커넥티드 아이디로 엔드 유저가 사용하는 N개의 기관에 등록이 가능하다. 제공하는 여러 API들은 커넥티드 아이디 계정 등록으로 받은 accessToken과 connectedID, 필수 파라미터들을 담아 요청하여 원하는 정보들을 반환받을 수 있다.</li>
</ol>
<br>

<ol start="5">
<li>구현
 해당 실습은 API 요청이 제대로 실행되는지 확인하는 test용으로, 실제 서비스에서는 서버에서 자동으로 처리되는 로직을 가진다. <br>
 Django를 이용해 진행하였다. Django는 urls.py에 써있는 url에 접속하면 해당하는 views.py의 함수가 실행된다.</li>
</ol>
<p>1) <a href="https://developer.codef.io/common-guide/connected-id/register">accessToken, connectedID 발급</a></p>
<p>urls.py<br><code>path(&#39;tocken/&#39;,tockenView),</code></p>
<p>views.py</p>
<pre><code class="language-python">    @api_view([&#39;GET&#39;])
    def tockenView(request):
    client_id=&quot;[키 관리&gt;client_id]&quot;
    client_secret=&quot;[키 관리&gt;client_secret]&quot;
    url=&#39;https://oauth.codef.io/oauth/token&#39; 
    authHeader = stringToBase64(client_id + &#39;:&#39; + client_secret).decode(&quot;utf-8&quot;)
    headers = {
        &#39;Acceppt&#39;: &#39;application/json&#39;,
        &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded&#39;,
        &#39;Authorization&#39;: &#39;Basic &#39; + authHeader
        }
    response = requests.post(url, headers = headers, data = &#39;grant_type=client_credentials&amp;scope=read&#39;)
    print(response.status_code)
    response_text=response.text
    json_data = json.loads(response_text)
    access_token = json_data.get(&#39;access_token&#39;)</code></pre>
<p>필요한 정보(client_id,client_secret)를 담아 <code>https://oauth.codef.io/oauth/token</code>에 요청을 보내 <code>access_token</code>을 받는다.</p>
<p>공동인증서를 이용한 로그인도 가능하지만, 서비스 이용 비용이 있어 <strong>카드사별 계정</strong>으로 직접 로그인하는 방식을 사용한다.</p>
<pre><code class="language-python">    bank_code=&#39;0306&#39;              # 신한 코드
    user_id=&#39;[신한 카드 홈페이지 아이디]&#39;          
    user_password=&#39;[신한 카드 홈페이지 패스워드]&#39;
    pubkey=&quot;[키 관리&gt;public_key]&quot;
    codef_account_create_url = &#39;https://development.codef.io/v1/account/create&#39;
    codef_account_create_body = {
            &#39;accountList&#39;:[
                {
                    &#39;countryCode&#39;:&#39;KR&#39;,                     # KOREA
                    &#39;businessType&#39;:&#39;CD&#39;,                    # CARD
                    &#39;clientType&#39;:&#39;P&#39;,                       # PERSONAL
                    &#39;organization&#39;:bank_code,
                    &#39;loginType&#39;:&#39;1&#39;,                        # 아이디 패스워드로 로그인
                    &#39;id&#39;:user_id,
                    &#39;password&#39;:publicEncRSA(pubKey, user_password)    # 퍼블릭 키로 패스워드 암호화
                }
            ]
        }

    response_account_create = http_sender(codef_account_create_url, access_token, codef_account_create_body)                
    decoded_response_text = urllib.parse.unquote(response_account_create.text) # URL 디코딩</code></pre>
<p>필요한 파라미터들을 담아 데이터를 만들고, 해당 url에 요청한다. 이때 CODEF에서 제공해주는 <a href="https://developer.codef.io/common-guide/connected-id/register">API 명세서</a>를 잘 참고해야 한다.<img src="https://velog.velcdn.com/images/julia_heo/post/dba4d5ff-aff3-4227-8410-33c456132812/image.png" alt=""></p>
<pre><code class="language-python">    try:
        response_data = json.loads(decoded_response_text)
        connected_id = response_data[&#39;data&#39;][&#39;connectedId&#39;]
        return render(request,&#39;home.html&#39;,{&#39;tocken&#39;:access_token,&#39;connectedID&#39;:connected_id})
    # 예외처리</code></pre>
<p>완료되어 얻은 access_token과 connected_id를 담아 home.html를 호출한다.</p>
<p>home.html은 access_token과 connected_id를 화면에 보여주고 세가지 api를 호출하는 버튼으로 구성하였다.<img src="https://velog.velcdn.com/images/julia_heo/post/280f9d52-a044-4310-8ccc-40b69cf7c3ba/image.png" alt=""></p>
<p>2) <a href="https://developer.codef.io/products/card/common/p/account">보유 카드 조회</a>  </p>
<p>보유카드 조회 버튼 클릭 시 실행되는 함수이다.</p>
<pre><code class="language-python">@api_view([&#39;GET&#39;])
def mycardView(request,tocken,connectedID):
    codef_mycard_url = &#39;https://development.codef.io/v1/kr/card/p/account/card-list&#39;
    codef_account_create_body = {
                    &#39;organization&#39;:&#39;0306&#39;,
                    &#39;connectedId&#39;:connectedID,
                    &#39;loginType&#39;:&#39;1&#39;,
                    &#39;inquiryType&#39;:&#39;1&#39;
    }
    response_account_create = http_sender(codef_mycard_url, tocken, codef_account_create_body)
    my_data=unquote(response_account_create.text)
    my_data=json.loads(my_data)
    my_data=json.dumps(my_data,ensure_ascii=False)
    return render(request,&#39;home.html&#39;,{&#39;my_data&#39;: my_data,&#39;tocken&#39;:tocken,&#39;connectedID&#39;:connectedID})</code></pre>
<p>토큰을 header에, connectedId와 카드회사, 로그인type등 정보를 담아 보유카드를 조회하는 EndPoint에 정보를 전달한다. </p>
<p>그 결과는 다음과 같다. 
카드 이름과 카드 번호, 카드 종류 뿐만아니라 교통카드 기능이 있는지, 카드 이미지 등의 정보가 반환된다.<img src="https://velog.velcdn.com/images/julia_heo/post/c1602be4-d450-48a9-92de-010313f7594a/image.png" alt=""></p>
<p>3) <a href="https://developer.codef.io/products/card/common/p/approval">승인 내역 조회</a></p>
<p>(2)번 보유 카드 조회처럼 api 명세서의 필수 파라미터들을 담고, 해당 엔드포인트에 요청을 보내면 된다.</p>
<p>카드 승인 내역 정보로, 날짜와 시간, 금액과 사용한 결제처 등의 정보를 확보 가능하다. 사용자가 자주 들른 매장인 경우 GPS를 이용해 근처일 경우 알림을 울리는 기능을 구현할 때, 사용자의 단골 매장을 찾는데 사용될 API이다.<img src="https://velog.velcdn.com/images/julia_heo/post/50f653a2-0e61-446c-b089-2d9a4590b88d/image.png" alt=""></p>
<p>4) <a href="https://developer.codef.io/products/card/common/p/result-check">실적 현황 조회</a></p>
<p>각 카드가 가지고 있는 혜택들과 해당 혜택을 받기 위한 실적이 채워졌는지 여부를 알려준다.
이번 달 해당 카드로 사요한 금액도 확인이 가능하다.<img src="https://velog.velcdn.com/images/julia_heo/post/9036b9af-adb5-4620-bcb3-063a1632d3b8/image.png" alt=""></p>
<br>

<hr>
<br>

<h1 id="카드-혜택-정보-크롤링">카드 혜택 정보 크롤링</h1>
<ol>
<li>크롤링이란?<br>
Web상에 존재하는 Contents를 수집 하는 작업 (프로그래밍으로 자동화 가능)<br>
1) HTML 페이지를 가져와서, HTML/CSS등을 파싱하고, 필요한 데이터만 추출하는 기법<br>
2)Open API(Rest API)를 제공하는 서비스에 Open API를 호출해서, 받은 데이터 중 필요한 데이터만 추출하는 기법<br>
3)Selenium등 브라우저를 프로그래밍으로 조작해서, 필요한 데이터만 추출하는 기법<br><br></li>
</ol>
<ol start="2">
<li><p>ONCE에서의 필요<br>
 원스는 사용자의 결제처, 전월실적, 혜택을 받기 위한 최저 결제 금액과 최고 할인 금액을 파악하고 있어야 한다. <br>그러기 위해 각 카드 상품들의 페이지에 나와있는 유의사항 하나하나까지 파악해야 한다. <br>이에 따라 카드 혜택 정보 저장을 위해 크롤링을 이용하기로 하였다.<br><br></p>
</li>
<li><p>크롤링 종류<br></p>
</li>
</ol>
<p><strong>정적 크롤링</strong>:<br>
웹에 있는 정적인 데이터를 수집할 때 사용. 주소를 통한 단발적 접근.
정적 데이터: 변하지 않는 데이터. 한 페이지 안에서 사전 작업 없이 드러나는 정적인 데이터
수집속도가 빠르지만, 수집 대상에 한계가 있음
사용 라이브러리: requests, BeautifulSoup<br>
<strong>동적 크롤링</strong>:<br>
웹에 있는 동적인 데이터를 수집할 때 사용. 브라우저를 사용한 연속적 접근.
동적 데이터: 입력, 클릭, 로그인 등과 같이 페이지 이동이 있어야 얻을 수 있는 데이터. 
속도가 느리지만 더 많은 정보 수집 가능
사용 라이브러리: selenium, chromedriver<br><br></p>
<ol start="4">
<li>구현</li>
</ol>
<p>1) 정적 크롤링, csv파일로 저장<br></p>
<pre><code class="language-python">import csv

csvFile = open(&#39;kb_test.csv&#39;, &#39;w+&#39;)

writer = csv.writer(csvFile)
writer.writerow((&#39;card name&#39;, &#39;part&#39;, &#39;discount&#39;,&#39;notice&#39;))</code></pre>
<p>카드 혜택을 저장할 csv파일을 연다. 칼럼명에는 카드이름, 혜택 분야, 할인 내용, 유의사항이 있다.</p>
<pre><code class="language-python">from urllib.request import urlopen
from bs4 import BeautifulSoup

html=urlopen(&#39;https://card.kbcard.com/CRD/DVIEW/HCAMCXPRICAC0076?mainCC=a&amp;cooperationcode=09123&#39;)
bs=BeautifulSoup(html,&#39;html.parser&#39;)</code></pre>
<p>urlopen함수를 이용해 원하는 카드 상품의 페이지를 연다. 
<img src="https://velog.velcdn.com/images/julia_heo/post/b680ddbe-4150-406a-96f9-654fbea8986a/image.png" alt=""><img src="https://velog.velcdn.com/images/julia_heo/post/8e5f9bea-8ffd-4c98-9e88-3e325a895868/image.png" alt=""></p>
<pre><code class="language-python">
name = bs.find(&#39;h1&#39;, {&#39;class&#39;:&#39;tit&#39;}).get_text()
soups=bs.findAll(&#39;div&#39;,{&#39;id&#39;:&#39;tabCon011&#39;})
for i in range(1, 3):

    partname=soups[i].find(&#39;h2&#39;).text[7:]
    parts=soups[i].findAll(&#39;div&#39;, {&#39;class&#39;:&#39;titArea&#39;})

    for part in parts:
        discount=part.find(&#39;h2&#39;, {&#39;class&#39;:&#39;titDep2&#39;}).text
        notice=part.find(&#39;div&#39;, {&#39;class&#39;:&#39;benefitBox1&#39;}).text
        writer.writerow((name,partname,discount,notice))</code></pre>
<p>html의 구조를 파악하고, 원하는 정보의 class, id등을 이용하여 경로를 찾고 값을 저장한다.
<code>writerow</code>함수로 찾은 값들을 csv파일에 한줄씩 적는다.</p>
<p>생성된 결과는 다음과 같다. 카드의 이름과 혜택 분야별 할인 내용이 잘 저장되었음을 확인 가능하다. 
<img src="https://velog.velcdn.com/images/julia_heo/post/707cefda-f13e-4996-bcff-482d1cadbb31/image.png" alt="">
저장이 완료되면 <code>csvFile.close()</code>명령어로 csv파일을 닫는다.<br><br></p>
<p>2) 동적 크롤링</p>
<pre><code class="language-python"># selenium의 webdriver를 사용하기 위한 import
from selenium import webdriver
from selenium.common.exceptions import TimeoutException

# selenium으로 키를 조작하기 위한 import
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

# 페이지 로딩을 기다리는데에 사용할 time 모듈 import
import time</code></pre>
<p>필요 라이브러리들을 import</p>
<pre><code class="language-python"># 크롬드라이버 실행
driver = webdriver.Chrome() 

url = &#39;https://card.kbcard.com/CRD/DVIEW/HCAMCXPRICAC0076?mainCC=a&amp;cooperationcode=09123&#39;
#크롬 드라이버에 url 주소 넣고 실행
driver.get(url)

# 페이지가 완전히 로딩되도록 3초동안 기다림
time.sleep(2)</code></pre>
<p>크롬드라이버를 이용해 해당 url의 페이지를 연다.
<img src="https://velog.velcdn.com/images/julia_heo/post/c903e7d0-6991-4f47-b9ed-224c57127388/image.png" alt=""></p>
<pre><code class="language-python"># 링크 클릭
link = driver.find_element(By.ID,&#39;topTab1&#39;).find_element(By.TAG_NAME,&#39;a&#39;)
link.click()</code></pre>
<p>상세 혜택을 보기 위해 &quot;상세 혜택&quot; 버튼을 클릭한다. 크롬 드라이버에서 코드의 명령에 따라 click해준다.
<img src="https://velog.velcdn.com/images/julia_heo/post/cfec312b-fccc-44a2-bc65-93907a26da9c/image.png" alt=""></p>
<pre><code class="language-python"># 페이지 소스 가져오기
page_source = driver.page_source

# BeautifulSoup을 사용하여 파싱
soup = BeautifulSoup(page_source, &#39;html.parser&#39;)

# 원하는 데이터 추출
title = soup.find(&#39;div&#39;, {&#39;id&#39;:&#39;tabCon010&#39;}).get_text()</code></pre>
<p>클릭을 통해 새롭게 나타난 페이지에서 원하는 데이터를 추출한다.</p>
<p>동적 크롤링만으로 정보를 얻을 수 없는 카드사 사이트인 경우 위와 같이 Selenium을 이용해 원하는 데이터를 추출할 예정이다. csv파일에 저장하는 과정은 (1)과 같으므로 생략하겠다<br><br></p>
<p>3) AWS RDS에 연결해 크롤링한 데이터를 바로 저장</p>
<pre><code class="language-python">host_name = &quot;[RDS 엔드포인트]&quot;
username = &quot;[rds username]&quot;
password = &quot;[rds password]&quot;
database_name = &quot;[데이터베이스 이름]&quot;
db = pymysql.connect(
    host=host_name, 
    port=3306,
    user=username,  
    passwd=password, 
    db=database_name, 
    charset=&#39;utf8&#39;
)</code></pre>
<p>이 코드를 실행하여 AWS의 RDS와 연결한다</p>
<pre><code class="language-python">cursor.execute(&quot;DROP DATABASE IF EXISTS once;&quot;)
db.commit()

cursor.execute(&quot;CREATE DATABASE itta DEFAULT CHARSET=utf8 COLLATE=utf8_bin;&quot;)
db.commit()

cursor.execute(&quot;USE once;&quot;)
db.commit()

cursor.execute(&quot;DROP TABLE IF EXISTS `CARD`;&quot;)
db.commit()

cursor.execute(&quot;&quot;&quot;
CREATE TABLE `CARD` (
    # 테이블 구성..
) DEFAULT CHARSET=utf8;
&quot;&quot;&quot;)
db.commit()</code></pre>
<p>SQL문을 직접 입력하여 동작을 수행한다. 이 코드는 CARD 테이블을 생성한다.<br><br></p>
<p>이후 (1), (2)처럼 원하는 데이터를 추출하는 크롤링을 진행한다.
원하는 데이터들을 변수에 저장해둔 뒤,
<code>cursor.execute()``db.commit()</code>을 이용해 INSERT문을 실행하면 된다.</p>
<br>

<hr>
<br>


<h1 id="gpt-파인튜닝으로-카드-추천">GPT 파인튜닝으로 카드 추천</h1>
<ol>
<li><strong>파인튜닝</strong>이란?<br>
사전 학습된 인공지능 모델의 가중치를 새로운 데이터에 맞게 세밀하게 조정하여 성능을 향상시키고 학습 시간을 줄이는 과정 (이미 배운 것을 기반으로 새로운 문제를 해결하는 과정)<br>
인공지능 모델 활용 시 파인튜닝이 필요한 이유:<br><ul>
<li>특정 도메인이나 작업에 최적화: 
사전 학습된 인공지능 모델은 대규모 데이터셋으로 학습되어 일반적인 작업에 적합하지만, 특정 작업에 대한 성능이 제한적일 수 있기에, 파인튜닝으로 특정 작업에 맞게 조정해 성능을 향상시킨다.<br></li>
<li>자원 및 시간 절약: <br>사전 학습된 모델을 기반으로 파인튜닝하여, 작은 양의 데이터와 상대적으로 짧은 학습 시간으로도 좋은 성능을 얻을 수 있다.<br></li>
<li>새로운 데이터에 대한 적응: 모델이 새로운 데이터에 더 잘 적응하고, 그에 따른 예측이나 추론 성능이 향상된다.<br><br></li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>GPT</strong>란?<br>
 1) 개념<br><br>자동 회귀 언어 모델로 <strong>ChatGPT</strong>와 같은 <strong>생성형 AI</strong> 애플리케이션을 지원하는 인공 지능(AI) 분야의 발전을 보여주는 주요 모델<br></p>
<pre><code> **Transformer 아키텍처**를 기반으로 구축된 신경망 기반 언어 예측 모델로, 프롬프트라는 자연어 쿼리를 분석하고 언어에 대한 이해를 바탕으로 가능한 최상의 응답을 예측&lt;br&gt;
 애플리케이션에서 인간과 유사한 텍스트 및 콘텐츠(이미지, 음악 등) 를 생성하고 대화형 방식으로 질문에 답할 수 있는 기능을 제공&lt;br&gt;
 업계 전반의 조직들이 Q&amp;A 봇, 텍스트 요약, 콘텐츠 생성 및 검색에 GPT 모델 및 생성형 AI를 사용&lt;br&gt;&lt;br&gt;</code></pre><p> 2) 실습<br>
 GPT를 파이썬으로 실행시켜보자.<br>
  <strong>① OpenAI API 신청</strong><br>
  &nbsp;&nbsp;&nbsp;&nbsp;<a href="https://platform.openai.com/">OpenAI</a>에 접속해 회원가입해준다. <a href="https://platform.openai.com/docs/models">여러 모델</a>이 존재하고, 특징 및 가격이 다르니 사이트를 잘 참고하자.<br>
  &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; API활용을 위해 Settings&gt;Billing에 카드를 등록했다.
  &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 프로필 클릭 &gt; Views API keys <img src="https://velog.velcdn.com/images/julia_heo/post/b129d315-eb59-4f2b-94fc-78b036f4fa30/image.png" alt=""> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 나온 API key를 복사한다.<br>
 ** ② 파이썬 openai 모듈 설치<strong><br>
  &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 터미널에 <code>pip install openai</code>를 입력해 openai 관련 패키지를 다운받는다.<br><br>
  **③ 파이썬 코드 실행하기</strong><br>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; GPT3.5를 지원해주는 text-davinci-003 모델을 사용해보자
 &nbsp;&nbsp;&nbsp;&nbsp;</p>
<p> import os
 import openai</p>
<p> openai.api_key = &quot;[API 키]&quot;</p>
<p> response = openai.Completion.create(</p>
<pre><code> model = &quot;text-davinci-003&quot;,
 prompt = &quot;my name is chaerin.\n\nQ: who am I?\nA:&quot;,
 temperature = 0,
 max_tokens=100,
 top_p = 1,
 frequency_penalty = 0.0,
 presence_penalty = 0.0,
 stop = [&quot;\n&quot;]</code></pre><p> )</p>
<p> print(response)
 print(response.choices[0].text.strip())</p>
</li>
</ol>
<p><code>model</code>: openai가 제공해주는 모델 중 사용할 모델의 이름<br>
<code>prompt</code>: 원하는 실행어 입력 - 좋은 결과를 위해 많은 테스트 필요<br>
<code>max_tokens</code>: 입력+출력 값으로 잡을 수 있는 최대 토큰 길이. 초과되면 에러가 난다 (한글은 토큰이 많이 쓰여 영어 위주로 사용할 예정)<br>
<code>stop</code>: stop 지점을 설정한다. 여기서는 &quot;\n&quot;이다
<br>
결과는 다음과 같다. 내 이름을 알려준 뒤 내가 누군지 물었는데, 의도대로 내 이름을 답해준다.<img src="https://velog.velcdn.com/images/julia_heo/post/869b4078-f618-446b-b853-9f1e28dbc507/image.png" alt=""></p>
<ol start="3">
<li>GPT 파인튜닝이란?<br>
 GPT 3.5 &quot;davinci-003&quot;모델은 2021년까지의 데이터만 학습되어 있어, 2022년도에 대한 답을 하지 못한다. 또한 ChatGPT는 웹 페이지, 책, 기타 문서 등 방대한 양의 일반 텍스트 데이터로 학습하여 언어의 패턴과 구조를 학습한 상태이므로, 특정 주제에 대한 질문에 대한 텍스트를 생성할 때 최적의 성능을 발휘하지 못할 수 있다.<br><br>예를 들어 특정 제품이나 서비스에 대한 고객의 질문에 답변할 수 있는 챗봇을 만들려면 해당 제품이나 서비스에 대한 고객 문의 및 응답 데이터 세트에 대해 ChatGPT를 Fine-tuning 하여 해당 도메인에서 사용되는 언어의 패턴과 뉘앙스를 더 잘 이해하고 보다 관련성 있고 정확한 응답을 생성할 수 있다.
질문에 대한 답을 알려주기도 하고, 원하는 format으로 답이 나오도록 유도할 수 도 있다.<br><br></li>
</ol>
<ol start="4">
<li><p>OpenAI의 Fine-tuning API 장점<br>
1) 프롬프트 디자인보다 높은 품질의 결과<br>
2) 프롬프트에 맞지 않는 예제를 학습할 수 있는 능력<br>
3) 짧은 프롬프트로 인한 토큰 절약<br>
4) 낮은 지연 요청 시간<br>
파인튜닝은 프롬프트에 맞지 않는 예제를 포함한 훨씬 많은 예제로 학습함으로써 다양한 작업에서 더 나은 결과를 얻을 수 있다. 모델을 파인튜닝한 후에는 더 이상 프롬프트에서 예제를 제공할 필요가 없기에 비용을 절감하고 지연 시간이 낮은 요청이 가능하다.<br><br></p>
</li>
<li><p>모델 별 파인튜닝 비용
<img src="https://velog.velcdn.com/images/julia_heo/post/48fbdef8-b8dd-446c-845f-bcf07c2d838a/image.png" alt=""></p>
</li>
</ol>
<ol start="6">
<li><p>ONCE에서의 필요<br>
GPT는 뛰어난 모델이지만, 카드 추천이라는 우리의 서비스에 적합하게 학습된 상태가 아니다.
따라서 우리가 질문할 예정인 문장과, 이에 원하는 대답의 형식을 정해주어 학습시킬 것이다.<br>
또한 GPT는 모든 은행의 카드 혜택 정보를 가지고 있지 않으며, 더더욱이 2022 이후의 데이터는 알 수 없기 때문에 두번째 기술 크롤링을 통해 얻은 카드 및 혜택 정보들을 입력하여 우리의 서비스에 관련된 정확한 응답을 생성하도록 할 것이다.</p>
</li>
<li><p>GPT 파인튜닝 과정<br>
① <strong>데이터 준비(format 맞추기)</strong><br>
모델을 파인튜닝하기 위한 데이터를 먼저 준비해야 한다.
<img src="https://velog.velcdn.com/images/julia_heo/post/b6d20321-51bb-49a8-9ea3-35301525ef79/image.png" alt="">
prompt(원하는 프롬프트)-completion(원하는 답 형태) 쌍으로 이루어진 JSON 형태의 데이터
단일 입력 예제와 해당하는 출력으로 구성<img src="https://velog.velcdn.com/images/julia_heo/post/2eadab76-dec8-416e-87a4-d932a1379baf/image.png" alt="">
CLI 데이터 준비 도구로 Fine-tuning 할 수 있는 데이터 생성:
<code>openai tools fine_tunes.prepare_data -f &lt;로컬_파일&gt;</code>
<br><br>
② <strong>Fine-tuning 모델 생성</strong><br>
OpenAi에서 제공하는 base model을 활용 (ada, babbage, curie, davinci)<br>
OpenAI CLI를 사용하여 파인튜닝 작업 시작
<code>openai api fine_tunes.create -t &lt;TRAIN_FILE_ID_OR_PATH&gt; -m &lt;BASE_MODEL&gt;</code>
&nbsp; ex. openai --api-key [YOUR_KEY] api fine_tunes.create -t example_prepared.jsonl -m davinci
<br> 이 코드의 작업:
&nbsp; &nbsp; &nbsp; &nbsp;- 파일을 파일 API를 사용하여 업로드 (또는 이미 업로드된 파일을 사용)
&nbsp; &nbsp; &nbsp; &nbsp;- 파인튜닝 작업을 생성
&nbsp; &nbsp; &nbsp; &nbsp;- 작업이 완료될 때까지 이벤트를 스트리밍 (보통 몇 분 소요, 대기 중인 작업이 많거나 데이터셋의 크기에 따라 몇 시간)</p>
<h1 id="추가-명령어들">추가 명령어들</h1>
<h1 id="생성된-모든-파인튜닝-작업-나열">생성된 모든 파인튜닝 작업 나열</h1>
<p> openai api fine_tunes.list</p>
<h1 id="파인튜닝-상태-검색-결과-객체에는-작업-상태-대기-중-실행-중-완료-또는-실패-중-하나일-수-있음와-기타-정보가-포함됩니다">파인튜닝 상태 검색. 결과 객체에는 작업 상태 (대기 중, 실행 중, 완료 또는 실패 중 하나일 수 있음)와 기타 정보가 포함됩니다.</h1>
<p> openai api fine_tunes.get -i <YOUR_FINE_TUNE_JOB_ID></p>
<h1 id="작업-취소">작업 취소</h1>
<p> openai api fine_tunes.cancel -i <YOUR_FINE_TUNE_JOB_ID>
<br><br>③ <strong>Fine-tuning 모델 사용</strong> <br>
OpenAI 내 계정 정보 &gt; Fine-tune training에 정보 추가됨, playground에서 확인 및 사용 가능
<br>작업이 성공 시, fine_tuned_model 필드에 모델의 이름 저장됨
이 모델을 Completions API의 매개변수로 지정하여 Playground를 사용하여 요청 보내기 가능
<img src="https://velog.velcdn.com/images/julia_heo/post/fb58a91e-1a51-43b1-a35f-507360d7a2c4/image.png" alt="">
<br>+ 파인튜닝된 모델 삭제<img src="https://velog.velcdn.com/images/julia_heo/post/5cfa58cf-42b4-4a7a-a873-f8988d686c2f/image.png" alt=""></p>
</li>
<li><p>오버피팅과 언더피팅 대응 전략<br></p>
</li>
</ol>
<p><strong>데이터 추가 수집</strong>: 더 많은 데이터는 모델이 다양한 패턴을 학습하고 오버피팅의 위험을 줄이는 데 도움이 된다<br>
<strong>데이터 증강</strong>: 백트랜스레이션과 유의어 대체와 같은 데이터 증강 기법을 사용하여 추가적인 훈련 예제를 생성하고 오버피팅을 줄일 수 있다<br>
<strong>모델 단순화</strong>: 모델이 오버피팅되었다면, 모델의 크기나 복잡성을 줄이는 것을 고려하라<br>
<strong>모델 복잡성 증가</strong>: 언더피팅이 문제라면, 데이터의 패턴을 파악하기 위해 더 복잡한 모델이 필요할 수 있다<br><br></p>
<ol start="9">
<li>파인튜닝 모델 평가<br>
모든 인공지능 모델은 학습 및 테스트 후 모델이 학습한 내용을 얼마나 잘 일반화했는짖 명확하고 객관적으로 측정할 수 있어야 한다. 
평가를 통해 모델이 세밀 조정 과정에서 실제로 유용한 내용을 학습했는지 아니면 훈련 데이터에만 과적합되었는지를 정량적으로 판단할 수 있다.
<br><br> 자연어 처리(NLP)에서 사용되는 일반적인 지표:</li>
</ol>
<ul>
<li><strong>정밀도</strong>(Precision): <br>
이 지표는 양성으로 예측한 사례 중 실제 양성인 경우의 비율로, &quot;모델이 양성으로 예측한 사례 중 실제로 양성인 것이 얼마나 많은가?&quot;라는 질문에 대답한다.
정밀도 = TP / (TP + FP)<br><br></li>
<li><strong>재현율</strong>(Recall, 민감도 또는 진양성 비율): <br>
실제 양성 사례 중 모델이 정확하게 양성으로 예측한 경우의 비율로, &quot;실제 양성 사례 중 모델이 얼마나 정확하게 양성으로 예측했는가?&quot;라는 질문에 대답한다.
재현율 = TP / (TP + FN)<br><br></li>
<li><strong>F1-Score</strong>: <br>
F1-Score는 정밀도와 재현율의 조화 평균으로, 이 둘을 균형 있게 조합한다. 데이터에 클래스 불균형이 있는 경우 특히 유용하다.
F1-Score = 2 * (정밀도 * 재현율) / (정밀도 + 재현율)<br><br></li>
<li><strong>정확도</strong>(Accuracy): <br>
정확하게 예측한 사례의 비율로, 데이터의 클래스 불균형이 있는 경우에는 신뢰할 수 없는 지표이다. 다수 클래스의 사례가 소수 클래스의 사례보다 많은 경우에는 특히 그렇다.
정확도 = (TP + TN) / (TP + TN + FP + FN)<br><br></li>
<li><strong>AUC-ROC</strong> (Receiver Operating Characteristic Curve의 면적): 
모든 가능한 분류 임계값에서의 성능을 종합적으로 제공한다. 모델이 클래스를 구별할 수 있는 정도를 나타낸다.<br><br></li>
</ul>
<ol start="9">
<li>GPT 파인튜닝 실습<br>
현재 감정을 표현하고 이름을 말하면 기분에 맞는 반응을 해주고, 이름을 다시 출력하도록 만들자<br>
파인튜닝 전, 기존 &quot;text-davinci-003&quot;모델을 사용해 다음과 같은 입력을 주면
<code>prompt = &quot;Feel: Smile \nMSG:my name is Chaerin.\n -&gt;&quot;,</code>
<img src="https://velog.velcdn.com/images/julia_heo/post/5d103c6b-70d1-4d7c-83d2-a15a3a6b1c21/image.png" alt="">
이러한 결과가 나온다.<br>
하지만 내가 원하는 형식대로 데이터를 만들어 보겠다.<img src="https://velog.velcdn.com/images/julia_heo/post/d2b257e1-6895-410a-b550-1c1b74b6514b/image.png" alt="">
<img src="https://velog.velcdn.com/images/julia_heo/post/413aa0d7-0d1a-47bf-bb21-5ecaae3d2780/image.png" alt="">example_prepared.jsonl이 생성되었다.
<img src="https://velog.velcdn.com/images/julia_heo/post/3a878cd5-d90f-4e00-9cad-d312b237bcde/image.png" alt="">
<code>openai --api-key [API KEY] api fine_tunes.create -t example_prepared.jsonl -m ada</code>
<img src="https://velog.velcdn.com/images/julia_heo/post/6ac23b9f-5159-4ff4-94ac-c0616267e049/image.png" alt="">만들어진 모델을 이용해 prompt를 보내보았다.<img src="https://velog.velcdn.com/images/julia_heo/post/e74d08c3-f6f6-4e21-bc08-62265d7341e4/image.png" alt=""> </li>
</ol>
<ol start="10">
<li>ONCE에 GPT 파인튜닝 적용 방안
<code>prompt</code>와 <code>completion</code>의 형식에서, 카드 추천을 위해 파인튜닝을 적용할 방법을 구상해 보았다.<br>
1) 각 사용자가 가진 카드와 해당 카드의 혜택 정보를 원하는 결제처와 함께 입력하여 그 중 하나의 카드를 도출해낸다.<br>
<code>{”prompt” : “&lt;결제처, [카드 1, 카드 1 상세 혜택, 카드 1 실적 충족 여부], ..., [카드 N, 카드 N 상세 혜택, 카드 N 실적 충족 여부]&gt;”, “completion” : “&lt;결제처에서 사용할 카드&gt;”}</code><br></li>
</ol>
<p>-&gt; 각 사용자마자 파인튜닝을 진행해야 하므로 현실성이 떨어진다.<br><br>
2) 한 카드의 혜택정보와 결제처를 입력하여 해당 결제처에서 받을 수 있는 하나의 혜택 항목만을 뽑아낸다. 사용자가 보유한 모든 카드에 대한 혜택 추출이 완료되면, 이 결과들을 비교해 최적의 카드를 찾는다.<br>
 GPT 모델 1 : <code>{”prompt” : “&lt;결제처, 카드명, 그 카드의 혜택, 실적 조건&gt;”, “completion” : “&lt;결제처에서 해당 카드로 누릴 수 있는 혜택(실적 채운 경우와 채우지 못한 경우 나눠서)&gt;”}</code><br><br></p>
<p>   →  [결제처, 카드명, 카드의 혜택]을 보고 누릴 수 있는 혜택을 찾아주는 역할<br><br>
    GPT 모델 2 : 모델 1에서 나온 결과들을 비교하여 가장 최대 혜택을 제공하는 카드 선택<br>
   <code>{”prompt” : “&lt;[결제처 A, 카드명1, 결제처에서 해당 카드로 누릴 수 있는 혜택1], ... , [결제처 A, 카드명N, 결제처에서 해당 카드로 누릴 수 있는 혜택N]&gt;”, “completion” : “&lt;결제처, 사용할 카드, 그 카드의 혜택&gt;”}</code><br><br></p>
<p>   → 직접 사용자가 [결제처]를 입력했을 때, [사용할 카드]를 출력<br><br>
3) Chat Completion을 이용해 사용자가 가진 카드의 정보들을 GPT에게 준 다음, 결제처를 입력해 하나의 최선의 카드를 출력하도록 한다.
<br>
<strong>Chat Completion</strong>: openai로 요청을 보낼 때 과거의 대화 기록을 포함하게 한다(원래 openai는 과거 요청을 기억x)
&nbsp; &nbsp; &nbsp; &nbsp; - user 메시지 : 사용자의 질문이나 설명
&nbsp; &nbsp; &nbsp; &nbsp; - assistant 메시지 : 이전 assistant 메시지(openai 응답)을 저장, 사용자가 원하는 동작의 예를 제공하기 위해 작성하기도<br>
<code>&quot;user&quot;: &quot;A카드, B카드, D카드의 혜택이 뭐야?&quot;,</code>
<code>&quot;assistant&quot;: &quot;A카드 혜택: [A 카드 혜택 정보],B카드 혜택: [B 카드 혜택 정보],C카드 혜택: [C 카드 혜택 정보]&quot;</code>
<code>&quot;user&quot;: &quot;GS25에서 10000원 결제하려면 이 중 어느 카드를 쓸까?&quot;</code>
이와 같은 형식으로, user의 질문에 대한 GPT의 답변을 지정해주어 카드의 혜택을 GPT가 파악하도록 하고, 결제처 입력을 통해 이 중 사용할 카드를 출력하도록 한다.<br>
여기서 카드 목록, 카드별 헤택 정보, 결제처는 백엔드 파트에서 입력받은 입력들을 알맞은 자리에 대입하여 완성한다. </p>
<br> 

<p>파인 튜닝 관련 자문 및 조사를 통해 3번의 방식대로 파인 튜닝을 진행하기로 하였다.
크롤링한 정보를 어떤 형식으로 넘길지를 포함하여 input과 output의 형식에 따라 GPT의 답변과 정확도가 달라질 것이다. 이를 모두 고려해 test해보며, 최고의 서비스를 구현해보자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ASC] Auto Scaling & ELB]]></title>
            <link>https://velog.io/@julia_heo/ASC-Auto-Scaling-ELB</link>
            <guid>https://velog.io/@julia_heo/ASC-Auto-Scaling-ELB</guid>
            <pubDate>Tue, 21 Nov 2023 18:00:16 GMT</pubDate>
            <description><![CDATA[<h1 id="auto-scaling">Auto Scaling</h1>
<ul>
<li>클라우드 리소스를 자동으로 조정하여 지정된 조건에 따라 필요할 때만 필요한 만큼의 컴퓨팅 리소스를 사용 할 수 있도록 하는 AWS 서비스</li>
<li>사용량이 증가하면 Auto Scaling은 사용자 정의 규칙에 따라 자동으로 EC2 인스턴스를 추가하여 용량을 늘 리고, 사용량이 감소하면 리소스를 줄여 비용을 절감</li>
</ul>
<p>“ 수요 예측을 정확히 하기란 사실상 불가능 하므로 트래픽 변화에 유연하게 대응하자 ! ”</p>
<h2 id="scale-up-vs-scale-out">Scale up vs Scale out</h2>
<p><strong>Scale Up &lt;-&gt; Scale Down</strong>
하나의 인스턴스의 리소스(예: CPU, 메모리)를 늘리는 것을 의미
예) 더 강력한 CPU나 더 많은 RAM을 갖는 더 큰 인스턴스 유형으로 전환(리소스 업그레이드 / 다운그레이드)
주로 수동으로 수행 (리부팅, 서비스 중단 야기 -&gt; 수평 확장 선호)<img src="https://velog.velcdn.com/images/julia_heo/post/b7731a6e-c4d5-4ed7-bd44-522a478d7d3b/image.png" alt="">
<strong>Scale Out &lt;-&gt; Scale In</strong>
더 많은 인스턴스를 추가하여 전체 시스템의 용량을 늘리는 방식 (리소스 개수 추가 / 축소)
AWS Auto Scaling - AWS의 관리형 서비스들은 주로 Scale out &amp; in을 지원<img src="https://velog.velcdn.com/images/julia_heo/post/b81216dd-238f-482d-8b3d-b657d425ee64/image.png" alt=""></p>
<h2 id="aws-auto-scaling-resource">AWS Auto Scaling Resource</h2>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/abc9aa0d-cda6-4a30-95df-c1013e73e7c1/image.png" alt=""></p>
<h2 id="ec2-auto-scaling">EC2 Auto Scaling</h2>
<p>: EC2 인스턴스에 대한 Scale in &amp; out을 제어할 수 있는 서비스
이점
- <strong>비용절감</strong>: 필요에 따라 용량을 동적으로 늘리거나 줄임, 사용한만큼만 비용 지불
- <strong>내결함성 향상</strong> : 인스턴스가 비정상 상태일 때 이를 종료하고, 새로운 인스턴스를 시작 -&gt; 하나의 가용 영역이 불가 상태가 되면 다른 가용 영역에서 새 인스턴스를 시작
- <strong>가용성 향상</strong> : 현재 트래픽 요구를 처리할 수 있는 적정 용량을 갖추도록 도움을 주고, 가용 영역 전반에 인스 턴스 분산<img src="https://velog.velcdn.com/images/julia_heo/post/3048f534-aa8e-4b99-a167-887c295aad34/image.png" alt=""></p>
<h2 id="ec2-auto-scaling-구성요소">EC2 Auto Scaling 구성요소</h2>
<p>1.** Launch Configurations / Launch Templates**: 인스턴스를 시작할 때 사용할 EC2 인스턴스 설정을 정의. 여기에 는 인스턴스 유형, AMI, 키 페어, 보안 그룹 및 기타 구성 세부 정보가 포함됨<img src="https://velog.velcdn.com/images/julia_heo/post/aaa1f320-e0d3-4bb8-afad-8bd4fc0cd8da/image.png" alt=""></p>
<ol start="2">
<li><p><strong>Auto Scaling Groups (ASGs)</strong>: Auto Scaling이 관리할 EC2 인스턴스의 집합입니다. ASG는 최소, 최대, 원하는
용량을 설정하여 그 범위 내에서 인스턴스를 유지하도록 합니다.<img src="https://velog.velcdn.com/images/julia_heo/post/59e82b76-2d8d-4fc0-a3b8-6fd7344d4f27/image.png" alt=""></p>
</li>
<li><p><strong>Scaling Policies</strong>: 인스턴스 수를 언제 확장하거나 축소할지 결정하는 규칙을 정의. 메트릭(예: CPU 사용률, 네트워 크 입출력 등)에 기반하여 동작.<img src="https://velog.velcdn.com/images/julia_heo/post/eea84328-e116-46a7-8730-3a9ccad1e1e4/image.png" alt=""></p>
<br>
(1) **CPU 사용률**: 애플리케이션의 CPU 사용이 높고, 이것이 서비스 성능에 직접적인 영 향을 미치는 경우 이 지표를 사용하면 좋다!
ex. 계산 작업이 많은 애플리케이션 또는 서버가 최대 성능으로 운영되어야 하는 게 임 서버, 데이터베이스 서버 등</li>
<li><p><strong>네트워크 입출력</strong>: 네트워크 트래픽이 성능의 제한 요소가 되는 환경에서는 이 지표가 유용하다.
ex. 큰 파일을 전송하거나 스트리밍 서비스를 운영하는 경우</p>
</li>
<li><p><strong>ALB 요청 수 (LoadBalancerRequestCountPerTarget)</strong>: 웹 애플리케이션이나 API 게이트웨이를 운영하고 요청 처리 능력을 기반으로 스케일링을 하고 싶을 때 적합하 다.이 지표를 사용하면 각 인스턴스가 처리해야 하는 HTTP/HTTPS 요청의 수를 기준 으로 인스턴스 수를 조정할 수 있다.</p>
</li>
<li><p><strong>사용자 지정 지표 (Custom Metrics)</strong>: Amazon CloudWatch를 통해 사용자가 직접 지표를 생성하고 이를 Auto Scaling에 활용할 수 있음. 이는 표준 지표로는 해결되 지 않는 특수한 요구 사항을 가진 시스템에 적합.
ex. 특정 애플리케이션 큐의 길이나 특정 데이터베이스의 쓰기/읽기 지연 시간 등</p>
</li>
</ol>
<h1 id="load-balancer">Load Balancer</h1>
<p>네트워크 트래픽을 여러 서버에 균일하게 분산시키는 장치 또는 소프트웨어. 웹사이트 또는 어플리케이션의 가 용성과 내구성을 높이기 위해 사용
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;대표적인 AWS의 서비스 : Elastic Load Balancer</p>
<h2 id="load-balancing">Load Balancing</h2>
<p>여러 리소스에 부하를 분산시키는 기술</p>
<ul>
<li>부하 분산 처리
하나의 리소스에 트래픽이 과도하게 몰려 서비스 중단되는 현상을 막기 위함</li>
<li>하나의 엔드포인트 제공
트래픽을 하나의 경로로 받아 여러 인스턴스에 분산시키기 때문에, 사용자 입장에서는 서로 다른 인스턴스에 접속하게 되더라도 같은 엔 드포인트(주소)로 접속하면 됨</li>
</ul>
<p>Scale in &amp; out으로 사이즈 조절하는 Auto Scaling과 뗄레야 뗄 수 없는 관계!</p>
<h3 id="load-balancer--auto-scaling">Load Balancer + Auto Scaling</h3>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/987c28c7-e80a-430b-8e4e-0228443cd830/image.png" alt=""><a href="https://suyeon96.tistory.com/46">참고</a></p>
<h2 id="elastic-load-balancer">Elastic Load Balancer</h2>
<p>트래픽을 여러 대상에 자동으로 분산시켜주는 서비스
VPC에 탑재되며, 사용자의 요청을 받아 VPC 내의 리소스를 적절한 부하 분산<img src="https://velog.velcdn.com/images/julia_heo/post/ee7d2432-fc47-41d0-ab59-fd8ff67b55b5/image.png" alt=""></p>
<h3 id="elastic-load-balancer-내부-구성요소">Elastic Load Balancer 내부 구성요소</h3>
<p><strong>리스너</strong>(Listener)
- 프로토콜과 포트를 기반으로 요청을 받아 검사하고 이를 적절한 타겟으로 전달하는 기능을 수행
- 리스너가 외부의 요청을 받아들이기 때문에 모든 로드 밸런서는 최소 한 개 이상의 리스너를 필요로 하며 최대 10개까지 설정 가능.
+ SSL 인증서를 게시하여 SSL Offload를 실시할 수도 있다</p>
<p><strong>규칙</strong>(Rule)
- 리스너와 타겟그룹 사이의 트래픽 분배를 위한 라우팅 규칙
- 우선순위, 액션, 조건 등의 정보를 담고 있으며 특정 조건이 만족되었을 때 지정된 액션을 수행<img src="https://velog.velcdn.com/images/julia_heo/post/67705aaa-ad04-46be-82ac-750579a0bb4e/image.png" alt=""></p>
<p><strong>대상 그룹</strong> (Target Group)
- 리스너가 전달한 요청을 처리하기 위한 부하 분산 대상들의 모임
- 대상 그룹에 등록된 <strong>인스턴스의 정보</strong> + EC2가 전달받은 요청을 처리할 수 있는 지를 체크하는 헬스체크 + 이 대상 그룹에 요청처리가 가능한 EC2가 몇 개인지, 불가능 한 EC2는 몇 개인지를 확인하는 모니터링 기능이 들어있다
- 쉽게 말해 오토스케일링 하기 위해 오토스케일링 그룹 만든 것처럼, 로드 밸런싱 하기 위해 대상 인스턴스들 묶어 놓은 그룹 -&gt; 그리고 이 대상 그대로 오토스케일링 그룹으로도 만들 수 있다<img src="https://velog.velcdn.com/images/julia_heo/post/45ccd316-1d86-4388-a566-01be00c02e61/image.png" alt=""></p>
<p><strong>유휴 제한 시간</strong> (Connection Time out)
- 사용자가 ELB를 거쳐 EC2에 접근하여 서비스를 접속하면 Connection이 생성됨
- 이 커넥션으로 통신하는데, 더 이상의 통신이 없을 때엔 유후 제한 시간이 작동하게 되고 그 시간이 지나면 커 넥션이 사라짐</p>
<h2 id="cross-zone-load-balancing">Cross-Zone Load Balancing</h2>
<p>두 개의 AZ 영역이 있고 각 영역에서 로드 밸런서가 위치하고 있다면<img src="https://velog.velcdn.com/images/julia_heo/post/e71e3bda-e3d8-464c-83ce-82ebfea9703c/image.png" alt="">
Cross-Zone Load Balancing이 이를 보완
&nbsp;&nbsp;&nbsp;AZ를 가리지 않고 고르게 분배 / ALB는 기본적으로 활성화 / NLB는 기본적으로 비활성화
<img src="https://velog.velcdn.com/images/julia_heo/post/cb20ff7a-1ed0-4838-b623-779af39fb82c/image.png" alt=""></p>
<h2 id="health-check">Health Check</h2>
<p>ELB에 연결된 인스턴스에 직접 트래픽을 발생시켜 인스턴스가 살아있는지 체크함 타겟그룹에 대한 헬스 체크를 통해 정상적으로 작동하는 인스턴스로만 트래픽을 분배
 - InService : 서비스 살음 / OutofService : 서비스 죽음</p>
<h2 id="elastic-load-balancer의-종류">Elastic Load Balancer의 종류</h2>
<h3 id="application-load-balanceralb">Application Load Balancer(ALB)</h3>
<ol>
<li>ALB는 OSI 7 Layer에서 일곱 번째 계층에 해당하는 Application Layer를 다루는 로드밸런서</li>
<li>ALB는 HTTP/HTTPS 프로토콜의 헤더를 보고 적절한 패킷으로 전송 (지능적인 라우팅)</li>
<li>ALB는 IP주소 + 포트번호 + 패킷 내용을 보고 스위칭</li>
<li>ALB는 IP 주소가 변동되기 때문에 Client에서 Access 할 ELB의 DNS Name을 이용</li>
<li>ALB는 L7단을 지원하기 때문에 EC2 대신에 SSL 적용이 가능
<img src="https://velog.velcdn.com/images/julia_heo/post/f2c4dd74-2d84-477b-a253-04c51201b3a7/image.png" alt=""></li>
</ol>
<h3 id="network-load-balancernlb">Network Load Balancer(NLB)</h3>
<ol>
<li>NLB는 L4(Transport Layer)단의 로드 밸런서를 지원</li>
<li>NLB는 TCP/IP 프로토콜의 헤더를 보고 적절한 패킷으로 전송 (당연히 HTTP헤더는 해석 못함)</li>
<li>NLB는 IP + 포트번호를 보고 스위칭</li>
<li>NLB는 할당한 Elastic IP를 Static IP로 사용이 가능하여 DNS Name과 IP주소 모두 사용이 가능 (IP 고정 가능!)</li>
<li>NLB는 SSL 적용이 인프라 단에서 불가능하여 애플리케이션에서 따로 적용해 주어야 함</li>
</ol>
<p>=&gt;ALB처럼 똑똑하게 주소로 보내주는게 아닌 단순한 라우팅이 필요하고,트래픽이 극도로 많은 경우에는 ALB 보다는 NLB를 사용하는 것이 적합하다고 할 수 있다.
<img src="https://velog.velcdn.com/images/julia_heo/post/1279af23-2e09-4341-8f0e-c5d1bc97b08b/image.png" alt=""></p>
<h3 id="gateway-load-balancerglb">Gateway Load Balancer(GLB)</h3>
<p>- GWLB는 L3단의 로드 밸런서를 지원
- 트래픽이 EC2에 도달하기 전에 먼저 트래픽을 검사하거나 분석하거나 인증하거
나 로깅하는 작업을 수행
- 트래픽을 체크하는 애, 위의 일반 로드밸런서와 조금 역할이 다름<img src="https://velog.velcdn.com/images/julia_heo/post/fc667b7a-1581-471c-897f-ce25b4b52a22/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ [ASC] AWS RDS & DynamoDB]]></title>
            <link>https://velog.io/@julia_heo/AWS-RDS-DynamoDB</link>
            <guid>https://velog.io/@julia_heo/AWS-RDS-DynamoDB</guid>
            <pubDate>Mon, 20 Nov 2023 15:01:17 GMT</pubDate>
            <description><![CDATA[<h2 id="rdsrelational-database-service">RDS(Relational Database Service)</h2>
<p><strong>SQL</strong>을 언어로 사용하는 managed database service</p>
<ul>
<li>프로비저닝, OS 패칭 전부 자동</li>
<li>지속적 백업 &amp; 특정 시간으로 복구 (Point in Time Restore)</li>
<li>모니터링 대시보드 존재</li>
<li>read Replicas
  읽기 요청 몰리면 읽기 전용 용량 자동으로 늘어남
  ex. 회사에서 하루/한달에 한번씩 통계용으로 데이터 읽어오는데 성능에 문제발생</li>
<li>multi AZ 셋업 가능
  High Availability
  다중AZ 설정-&gt; AZ별 인스턴스에 마스터DB의 Sync 복제
  여러 rds지만, 하나의 DNS
  active가 아닌 standby - 장애 대비 용도</li>
<li>Scaling capability
  Storage Auto Scaling (Horizontal scaling)
  데이터베이스 인스턴스 수를 조절(증가/감소), 읽기부하높을 때 read replica 생성해 부하 분산</li>
<li>EBS 스토리지 사용</li>
</ul>
<p>+) active-active VS activce-standby :(서버를 이중화 하는 방법)
Active-active: 여러 개의 자원이 동시에 서비스 되는것
Active-standby: 말 그대로 하나의 서버가 대기(Stanby) 상태
<img src="https://velog.velcdn.com/images/julia_heo/post/04a0996a-3433-4ff5-8cdc-fa1eb94e71e8/image.png" alt=""></p>
<h4 id="ec2에서-mysql-vs-rds">EC2에서 MySQL vs RDS</h4>
<p>rds는 HA, Scalability지원 / EC2보다 비쌈
-&gt; 데이터를 오래 관리해야하고, 안정성이 중요한 입장(데이터 무결성, 내결함성이 너무 중요)이라면 rds</p>
<h2 id="dynamodb">DynamoDB</h2>
<p>AWS에서 만든 DBMS (독점기술)
managed serverless NoSQL DB
&nbsp;&nbsp;&nbsp;&nbsp;Managed: 완전 관리형
&nbsp;&nbsp;&nbsp;&nbsp;Serverless: 서버리스
&nbsp;&nbsp;&nbsp;&nbsp;NoSQL: 관계형 DB 아님</p>
<p><strong>Key-value store, 키-값 데이터베이스</strong>
기본 연산
put(key, value): 키-값 쌍을 저장소에 저장
get(key): 인자로 주어진 Key에 대응되는 값 조회</p>
<p>테이블: 데이터를 저장하는 기본 단위
아이템: 테이블 내의 개별 레코드 (RDB의 row)
Primary Key = PK + SK
<img src="https://velog.velcdn.com/images/julia_heo/post/4bf41f4b-39b0-49b0-8da0-ebf0ff8e4424/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/julia_heo/post/7769ba2d-b1b6-4122-806d-84d7d6dbec32/image.png" alt=""></p>
<ul>
<li>key/value 스토어, TTL 기능 지원</li>
<li>HA, Multi AZ, Read and Writes are decoupled, transaction capability</li>
<li>DAX 클러스터를 읽기 캐시로 생성할 수 있다. -&gt; ms 단위의 latency</li>
<li>IAM을 통한 보안, 인증, 인가</li>
<li>Event Processing: DynamoDb 스트림과 Lambda/Kinesis Data Streams를 통합할 수 있음.
- 따라서 Dynamo 테이블의 모든 변화에 따라 함수를 호출할 수 있다. </li>
<li>Global Table 기능: active-active 설정이 가능하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ASC] Lightsail & EC2]]></title>
            <link>https://velog.io/@julia_heo/ASC-1107-Lightsail-EC2-Auto-Scaling-ELB</link>
            <guid>https://velog.io/@julia_heo/ASC-1107-Lightsail-EC2-Auto-Scaling-ELB</guid>
            <pubDate>Tue, 14 Nov 2023 08:19:17 GMT</pubDate>
            <description><![CDATA[<h1 id="cloud-computing-service">Cloud Computing Service</h1>
<p>인터넷을 통해 서버, 스토리지, DB, 네트워크 및 기타 컴퓨팅 자원에 대한 <strong>온디맨드 액세스</strong>를 제공하는 서비스 사용자가 직접 인프라를 보유하거나 관리할 필요 없이 필요할 때 원하는 컴퓨팅 <strong>리소스</strong>를 사용하도록 함</p>
<h3 id="aws의-주요-컴퓨팅-엔진">AWS의 주요 컴퓨팅 엔진</h3>
<h4 id="infrastructure-as-a-serviceiaas">Infrastructure as a Service(IAAS)</h4>
<p>EC2: 
- AWS에서 가장 기본적이고 널리 쓰이는 인프라, 가상머신으로 제공되며 인스턴스라고 불림
- 물리 환경의 컴퓨터처럼 컴퓨팅 리소스를 제공 (피씨방가서 돈내고 컴퓨터 이용하는 것과 비슷)
Lightsail:
- 초보자 친화적인 서비스로, 주어진 리소스 옵션 중 하나를 골라 단일 가상 서버를 간단히 설정하는 서비스
- 사전에 구성된 템플릿을 이용해 인스턴스를 빠르게 배포 가능</p>
<h4 id="serverless">Serverless</h4>
<p>(PaaS) Elastic Beanstalk:
- EC2 위에 구축된 고급 관리 플랫폼
- 사용자는 애플리케이션 코드를 EB에 업로드하기만 하면 EB가 EC2인스턴스를 자동으로 생성하고 배포과정을 관리함
- 코드를 업로드하기만 하면 프로비저닝, 로드밸런싱, Auto Scaling, 운영체제 관리 등의 개발과 배포 자동화
(FaaS) Lambda:
- 모든 유형의 애플리케이션이나 백엔드 서비스에 대한 코드를 별도의 관리 없이 실행하는 서비스
- 서버에 대한 걱정없이 코드만으로 서비스를 실행, 이벤트 기반으로 실행되는 서버리스 컴퓨팅서비스
- 서버 및 운영 체제 유지 보수, 용량 프로비저닝 및 자동 확장, 코드 모니터링 및 로깅과 같은 컴퓨팅 리소스의 모든 관리를 자체적으로 수행하므로, Lambda가 사용하는 언어 중 하나로 코드를 제동하기만 하면 됨</p>
<h1 id="lightsail--ec2">Lightsail &amp; EC2</h1>
<h2 id="lightsail">Lightsail</h2>
<p>가상 머신, 컨테이너, 데이터베이스, CDN, 로드 밸런서, DNS 관리 등 프로젝트를 빠르게 시작하는 데 필요한 모든 기능을 신속하게 이용 가능하도록 하는 <strong>VPC 서비스</strong>
(Virtual Private Server)
1대의 물리적 서버에 다수의 가상 서버를 구축<img src="https://velog.velcdn.com/images/julia_heo/post/ec5f170c-8dcb-4f90-94da-b23a1ef3b000/image.png" alt=""> 
인스턴스, 컨테이너, DB, 스토리지, DNS 등을 한 곳에서 관리 가능</p>
<h4 id="기능">기능</h4>
<ol>
<li>Instance</li>
</ol>
<ul>
<li>AWS 클라우드에 있는 가상 프라이빗 서버(VPS)</li>
<li>리전과 가용영역 선택, 인스턴스 이미지로 선택할 플랫폼과 <strong>블루프린트</strong>(미리 정의된 설정, 구성 및 자원의 모음) 선택 </li>
<li>인스턴스 시작 시 스크립트 설정, SSH 키 페어, 데일리 백업 설정 및 인스턴스 플랜 설정</li>
<li>인스턴스 이름, 갯수, 태그 설정 후 생성, SSH접속이 가능하며, 방화벽 설정 가능</li>
<li>기본 수준의 CPU 성능을 발휘하면서 추가적으로 버스트 성능을 발휘할 수 있는 인스턴스 이용</li>
<li>즉 비디오 인코딩같은 어플리케이션과 같은 일관되게 높은 CPU 성능을 쓰려면 EC2 추천</li>
<li>가격에 따라 정해진 CPU, 메모리, 디스크 용량을 선택하게되고 정해진 가격만큼 과금되는 형식</li>
<li>한달 내내 실행시키되 트래픽이 많지않다면 EC2보다 훨씬저렴!</li>
<li>실행가능한 소프트웨어
◦ 다양한 운영 체제와 자동으로 설치되는 인스턴스 이미지(AMI) 제공
◦ 인스턴스 이미지에는 특정 운영체제와 소프트웨어 구성이 들어있음
◦ WordPress, Django, Nginx(LEMP) Node.js, Ubuntu, CentOs 등이 있고, 브라우저 내 SSH 또는 자체적인 SSH
클라이언트를 사용하여 인스턴스에 소프트웨어를 추가로 설치할 수 있음
(1) Amazon Linux : 기본적인 웹 애플리케이션, 백엔드 서버, 개발 및 테스트 환경 등에 적합한 이미지. 여러
프로그래밍 언어와 관련된 라이브러리 및 도구가 설치되어 있음
(2) Ubuntu: 다양한 웹 애플리케이션, DB 서버, 컨테이너 환경 등을 호스팅할 때 사용. 널리쓰임
(3) WordPress: 블로그나 웹사이트 호스팅에 특화. Apache 웹 서버, MySQL DB가 미리 설치됨
(4) LAMP: Linux, Apache, MySQL, PHP를 포함한 전통적인 LAMP 스택을 위한 이미지. 웹 애플리케이션 개
발과 호스팅을 위해 널리 사용되는 이미지로, PHP 기반의 웹 애플리케이션을 개발할 때 적합. 
(5) Node.js: 노드 기반 웹 애플리케이션을 개발하고 호스팅에 사용. Node.js와 npm이 사전 설치됨</li>
</ul>
<ol start="2">
<li>Container Service</li>
</ol>
<ul>
<li>클라우드에서 컨테이너형 애플리케이션을 쉽게 실행하도록 돕는 서비스</li>
<li>필요한 배포 이미지, 성능, 노드 수만 지정하면 인프라를 관리할 필요 없이 컨테이너 서비스 실행</li>
<li>도커 컨테이너 실행이 가능하며 windows 컨테이너는 지원되지 않음</li>
<li>단, Lightsail 컨테이너 서비스가 직접적으로 CDN의 오리진 서버로 사용될 수는 없음
- Lightsail의 컨테이너 서비스로 실행되는 애플리케이션을 CDN을 통해 직접적으로 분배하려면 추가적인 설정이나 중간 계층 필요</li>
<li>컨테이너 서비스는 Lightsail의 로드밸런스 대상이 될 수 없음</li>
<li>Lightsail의 로드밸런서는 인스턴스 간의 트래픽을 분산하는 데 사용되지만, 컨테이너 서비스의 경우 수동으로 로드밸런싱을 해주어야 함</li>
</ul>
<ol start="3">
<li>Database
DBMS 선택 및 master user name, 데이터베이스 이름 설정, 데이터베이스 선택 가능</li>
</ol>
<ol start="4">
<li><p>Networking
고정 IP : Lightsail로 만들어진 인스턴스에 고정 IP주소 할당 
Distribution : 콘텐츠 전송 네트워크 (CDN) 기능 제공
Load Balancer : 트래픽을 여러 서버로 균등하게 나누는 기능을 제공
(ec2만큼의 퀄리티의 서비스를 지원해주지 않지만 간단하고 빠르게 필요하다면 매력적!!)</p>
</li>
<li><p>Storage
Bucket 생성 : 파일, 이미지, 비디오등의 객체를 저장하고 엑세스하는 공간을 제공
Disk 생성 : 라이트세일 인스턴스에서 하드 드라이브로 마운트 할 수 있는 스토리지 볼륨</p>
</li>
<li><p>Snapshot
인스턴스나 디스크의 상태를 캡처하여 백업 또는 복원에 사용하는 기능
문제 발생시 백업, 애플리케이션을 확장을 위한 복제, 다른 AWS서비스로의 마이그레이션 등에 이용
(lightsail로 시작했는데 사용자 늘어난다면 스냅샷 찍은 후 AMI 만들어서 그대로 EC2로 옮길 수 있음)</p>
</li>
<li><p>DNS Zone
도메인 이름 시스템(DNS) 레코드를 관리하는 방식을 제공</p>
</li>
</ol>
<h2 id="ec2-amazon-elastic-compute-cloud">EC2 (Amazon Elastic Compute Cloud)</h2>
<p> AWS 클라우드에서 확장 가능한 컴퓨팅 용량을 제공하는 서비스로, 하드웨어 투자 없이 빠르게 어플리케이션을 개발하고 배포하도록 가상 서버의 구축과 네트워킹 구성, 스토리지 관리등을 돕는 서비스</p>
<ol>
<li>Amazon Machine Image (AMI)
도커의 이미지와 같은 역할이지만 EC2를 특정 환경으로 실행시키기 위한 <strong>레시피</strong><ul>
<li>인스턴스를 실행하기 위한 정보를 모은 단위</li>
<li>직접 만들 수 있으며, 백업에도 사용</li>
<li>Lightsail을 EC2로 마이그레이션 할 때에도 AMI를 이용</li>
</ul>
</li>
</ol>
<ol start="2">
<li>보안 그룹과 키페어
(1) 보안 그룹: 가상 방화벽 역할을 하여 인스턴스로의 트래픽을 제어. 인바운드(들어오는)와 아웃바운드(나가는) 규칙을 정의하여 특정 트래픽만을 허용하도록 설정할 수 있다
(2) 키 페어: AWS에서는 공개 키 인프라(PKI)를 사용하여 인스턴스에 로그인함. 키 페어는 사용자가 EC2 인스턴스의 암호화된 로그인 정보를 안전하게 전달할 수 있는 방법을 제공
EC2 Keywords <br></li>
</ol>
<p><strong>공개 키</strong>(Public Key): AWS에서 인스턴스를 생성할 때 사용되며, 인스턴스 내부에 자동으로 저장
<strong>개인 키</strong>(Private Key): 사용자가 인스턴스를 처음 생성할 때 다운로드하며, 해당 키는 절대로 AWS에 업로드되지 않는다. 사용자만이 이 개인 키를 가지고 있어야 하며, 이를 사용해 EC2 인스턴스에 접근한다.<img src="https://velog.velcdn.com/images/julia_heo/post/874dd6e8-6fa4-42eb-8295-5af3565bb17f/image.png" alt=""></p>
<ol start="3">
<li><p>탄력적 IP 주소(EIP) : 인스턴스 재시작시 바뀌는 IP 주소의 고정을 위한 것
(주의 : 프리티어에는 1개의 EIP가 포함되어 있는데 중단된 인스턴스와 연결되어 있거나, 아무 인스턴스와도 연결되어 있지 않으면 과금된다.)</p>
</li>
<li><p>인스턴스 스토어 볼륨 : 
인스턴스와 물리적으로 직접 연결된 스토리지(디스크 드라이브)로 임시 데이 터를 저장하는 스토리지 볼륨</p>
</li>
<li><p>Amazon Elastic Block Store(Amazon EBS)
- 클라우드에서 사용하는 가상의 하드디스크
- EC2와 별개로 동작 -&gt; 인스턴스가 종료되어도 별개로 작동 가능 
- EC2와 같은 AZ에 있어야 함</p>
</li>
</ol>
<p>Lightsail vs EC2<img src="https://velog.velcdn.com/images/julia_heo/post/97e3025b-33bb-4264-99a4-53fe2104f3fe/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>