<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>nice-one-roy.log</title>
        <link>https://velog.io/</link>
        <description>내가 보려고 쓰는 글</description>
        <lastBuildDate>Tue, 26 Nov 2024 08:42:40 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>nice-one-roy.log</title>
            <url>https://velog.velcdn.com/images/nice-one-roy/profile/21175e57-19ca-4a38-a15e-aaf71c247811/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. nice-one-roy.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/nice-one-roy" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Floating Point]]></title>
            <link>https://velog.io/@nice-one-roy/Floating-Point</link>
            <guid>https://velog.io/@nice-one-roy/Floating-Point</guid>
            <pubDate>Tue, 26 Nov 2024 08:42:40 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/db33198c-d0b3-4cf3-bf6d-5a805cf1ff24/image.png" alt=""></p>
<h3 id="예시">예시</h3>
<h4 id="-118625">-118.625</h4>
<ol>
<li><p>먼저 음수이므로 최상위 비트를 1로 설정합니다. (양수일 경우 0)</p>
</li>
<li><p>절댓값 118.625를 이진법으로 변환합니다.
118.625 -&gt; 1110 110.101 </p>
</li>
<li><p>소수점을 왼쪽으로 이동시켜 정수부가 한자리가 되도록 합니다.
1110 110.101 -&gt; 1.1101 1010 1</p>
</li>
<li><p>3번에서 이동시킨 자릿수(6)만큼을 2의 지수로 사용하여 곱해주고, 이 수를 정규화된 부동 소수점이라고 합니다.
1.1101 1010 1 x 2^6</p>
</li>
<li><p>4번의 소수점 아래 부분(1101 1010 1)이 가수부(23bit)가 되도록 나머지 비트를 0으로 채웁니다.
1101 1010 1000 0000 0000 000 x 2^6</p>
</li>
<li><p>위 23비트를 가수부로 설정합니다.  </p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/07b6f1ce-966a-4591-aa59-94c7ba4d318a/image.png" alt=""></p>
<ol start="7">
<li><p>32bit IEEE 754 형식엔 &quot;Bias&quot; 라는 고정된 값이 있습니다. 이는 127이며, bias를 2의 지수인 6에 더하고 이진수로 변환합니다.
6 + 127 = 133 -&gt; 1000 0101  (8bit 지수부)</p>
</li>
<li><p>위 8비트를 지수부로 설정합니다.</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/d6679c91-c42c-44fd-8a18-c0202e749b89/image.png" alt=""></p>
<h2 id="floating-point-에러">Floating Point 에러</h2>
<p>컴퓨터는 부동소수점(floating-point) 숫자를 정확하게 표현할 수 없을 때가 있어, 더하기나 빼기 같은 연산 결과가 우리가 기대한 것과 미세하게 다를 수 있습니다.</p>
<p>이유:
2진법 표현의 한계:
대부분의 실수(예: 0.1, 0.2)는 2진법으로 정확히 표현할 수 없습니다.</p>
<p>예를 들어, 0.1은 2진법으로 무한히 반복되는 값(0.000110011...)이 되어 근사값으로 저장됩니다.
근사값 연산:
이런 근사값들로 계산하다 보니 결과에 오차가 생깁니다.</p>
<p>예: 0.1 + 0.2는 우리가 기대하는 0.3 대신 0.30000000000000004가 나올 수 있습니다.</p>
<p>파이썬에서 부동소수점 덧셈 오류를 잘 보여주는 간단한 예시를 살펴보겠습니다. 부동소수점 연산은 항상 정확한 결과를 보장하지 않기 때문에, 덧셈 연산 후 기대한 값과 실제 값이 약간 다를 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/430ac0ae-7b24-488d-8037-ef866b94bc02/image.png" alt=""></p>
<p>뭐 이런 식으로 해결할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/7fcb953e-d893-4c80-a0d1-c50cbbc02811/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[IP, 서브넷 마스크, CIDR]]></title>
            <link>https://velog.io/@nice-one-roy/IP-CIDR-VPC-Subnet</link>
            <guid>https://velog.io/@nice-one-roy/IP-CIDR-VPC-Subnet</guid>
            <pubDate>Tue, 26 Nov 2024 07:43:12 GMT</pubDate>
            <description><![CDATA[<p>IPv4 기준으로 작성합니다.</p>
<h2 id="ip">IP</h2>
<p>아이피는 네트워크 ID + 호스트 ID 로 구성되어 진다.</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/53f9abcb-4c68-44f1-b2a1-95c2d56fd428/image.png" alt="">
아래 그림과 같은 식이다. Class는 CIDR 이전에 IP를 나누고 표현하는 방식이다.
<img src="https://velog.velcdn.com/images/nice-one-roy/post/042eb56c-cbec-4dd0-a583-88a0185f5210/image.png" alt="">
<img src="https://velog.velcdn.com/images/nice-one-roy/post/b93f2d5e-c917-4bc4-af8a-dbd9295c76fe/image.png" alt="">
예약된 주소들도 있다.<img src="https://velog.velcdn.com/images/nice-one-roy/post/82906e0e-81c8-457e-b402-ceb44c95f2f2/image.png" alt=""></p>
<h2 id="서브넷-마스크">서브넷 마스크</h2>
<p>서브넷 마스크는 IP 주소에서 네트워크와 호스트를 구분하는 역할을 하는 32비트 값이다.</p>
<p>서브넷 마스크는 연속된 1과 연속된 0으로 구성되어있다
11111111.11111111.11111100.00000000 =&gt; 이런 식의 값이다.
10011111.11011111.11110011.00000000 =&gt; 이런 식의 값은 갖을 수 없다.</p>
<p>그냥 어디까지가 네트워크 파트인지 표시해주는 것 뿐이다.</p>
<h2 id="cidr">CIDR</h2>
<p>CIDR의 full name은 (Classless Inter-Domain Routing) 으로 클래스 없는 도메인간 라우팅 기법이라는 뜻을 내포한다.
즉, 도메인간의 라우팅에 사용되는 인터넷 주소를 원래 IP주소 클래스 체계를 쓰는 것보다 더욱 능동적의로 할수 잇도록 할당하여 지정하는 방식중 하나 이다.</p>
<p>=&gt; 그냥 IP를 나누고 할당하는 걸 잘 표현하기 위한 표기법이라고 생각하면 된다.</p>
<p>그냥 예시로 이해해보자.</p>
<p>192.168.10.70/26이라고 하면</p>
<p>이 아이피 표기법에서는 서브넷마스크의 1의 갯수가 26개 인걸 알 수 있다.
네번째 옥텟의 최상위 2개의 비트가 1이고, 나머지 6비트를 호스트ID로 할당하게 된다.
즉 호스트ID 부분이 .11000000 가 되며 10진법으로 192가 된다.
그러면 서브넷마스크는 255.255.255.192가 되게 된다.</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/0abaf118-15ce-4d93-9ea3-34619d538e52/image.png" alt=""></p>
<p>32비트 중에 앞 26자리까지가 네트워크 비트가 되고 뒤 6자리를 호스트로 사용하게 된다.
256 - 192 = 64 (특수목적 IP - 2) = 62 이므로 62개의 호스트를 가질 수 있게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD 구축하기(3)-Helm Chart + GCP DNS, secret manager + ssl certificate(Gitlab-ci + Helm + ArgoCD, GCP kubernetes 등)]]></title>
            <link>https://velog.io/@nice-one-roy/CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B03-Helm-Chart-GCP-DNS-secret-manager-ssl-certificateGitlab-ci-Helm-ArgoCD-GCP-kubernetes-%EB%93%B1</link>
            <guid>https://velog.io/@nice-one-roy/CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B03-Helm-Chart-GCP-DNS-secret-manager-ssl-certificateGitlab-ci-Helm-ArgoCD-GCP-kubernetes-%EB%93%B1</guid>
            <pubDate>Mon, 23 Sep 2024 11:43:30 GMT</pubDate>
            <description><![CDATA[<h2 id="목표">목표</h2>
<p>앞선 두 개의 게시물에서 완성한 gitlab-ci.yml과 helm manifest를 통해 CD를 만들어보자.</p>
<ul>
<li>ArgoCD 구축</li>
</ul>
<h2 id="환경-세팅">환경 세팅</h2>
<ul>
<li>kubectl create namespace argocd</li>
<li>kubectl apply -n argocd -f <a href="https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml">https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml</a></li>
<li>kubectl patch svc argocd-server -n argocd -p &#39;{&quot;spec&quot;: {&quot;type&quot;: &quot;LoadBalancer&quot;}}&#39;</li>
<li>external ip를 domain에 A Record에 연결해주기(optional)</li>
<li>argocd ip에 접속 후 settings =&gt; repositories =&gt; connect repo =&gt; gcp source repository 연결. </li>
</ul>
<p>나 같은 경우엔 connection method로 gcloud 선택 후 repository url은 아래 포맷을 입력하고  올바른 GCP service account key를 입력하면 된다.
<a href="https://source.developers.google.com/p/%7Bproject%7D/r/%7Brepo%7D">https://source.developers.google.com/p/{project}/r/{repo}</a></p>
<ul>
<li>그리고 Application을 만들면 된다. 그리고 sync가 잘 되는지 확인 후 gitlab-ci에서 sync가 잘 되는지도 확인하면 완료</li>
</ul>
<h2 id="명령어-모음">명령어 모음</h2>
<ul>
<li>argocd admin initial-password -n argocd</li>
<li>kubectl edit configmap argocd-cm -n argocd
argoCD api를 사용하려면 추가적인 권한이 필요하다. 아래 부분을 추가하면 된다.<pre><code class="language-jsx">data:
    accounts.admin: apiKey, login</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD 구축하기(2)-Helm Chart + GCP DNS, secret manager + ssl certificate(Gitlab-ci + Helm + ArgoCD, GCP kubernetes 등)]]></title>
            <link>https://velog.io/@nice-one-roy/CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B02-Helm-Chart-GCP-DNS-secret-manager-ssl-certificateGitlab-ci-Helm-ArgoCD-GCP-kubernetes-%EB%93%B1</link>
            <guid>https://velog.io/@nice-one-roy/CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B02-Helm-Chart-GCP-DNS-secret-manager-ssl-certificateGitlab-ci-Helm-ArgoCD-GCP-kubernetes-%EB%93%B1</guid>
            <pubDate>Fri, 20 Sep 2024 08:15:59 GMT</pubDate>
            <description><![CDATA[<h2 id="목표">목표</h2>
<p>앞선 게시물에서 등장한 Helm chart manifest의 예시를 보면서 helm install로 배포할 수 있는 상태로 만드는 과정을 살펴보자. 최종적으는 다음 게시물에서 helm 명령어가 아니라 ArgoCD를 통해 sync를 통해 배포할 예정이다.</p>
<ul>
<li>Helm chart manifest를 작성해서 Backend 서비스를 GCP k8s engine에 배포할 수 있도록 한다.</li>
<li>Domain에 HTTPS(SSL 인증서)까지 적용한다.</li>
<li>Env variable을 사용하기 위해 GCP secret manager 서비스를 사용하도록 한다.</li>
<li>gcp k8s에 배포하기 위한 helm, gcloud 명령어들</li>
</ul>
<p>완성한 소스코드는 <a href="https://github.com/HoneyJung/helm-gcp-example%EC%97%90">https://github.com/HoneyJung/helm-gcp-example에</a> 있다.</p>
<h2 id="파일-설명">파일 설명</h2>
<p>exterenalSecret.yaml, externalSecretServiceAccount.yaml, secretStore는 아래에서 더 자세히 설명합니다.</p>
<pre><code class="language-jsx">├── Chart.yaml
├── templates
│   ├── backendConfig.yaml - timeout을 위한 것. 필수 아님
│   ├── deployment.yaml
│   ├── exterenalSecret.yaml
│   ├── externalSecretServiceAccount.yaml
│   ├── frontendConfig.yaml - http =&gt; https로 redirect하기 위함. 이것도 필수 아님.
│   ├── ingress.yaml
│   ├── managedCertificate.yaml =&gt; ssl 인증서를 다루기 위한 파일. 아래에서 더 자세히 설명 
│   ├── secretStore.yaml
│   └── service.yaml - 
├── values-dev.yaml - 환경 별로 사용되는 변수는 분리해서 다룬다.
└── values.yaml - 변수는 따로 저장한다.</code></pre>
<h2 id="prerequisite">Prerequisite</h2>
<ul>
<li>GCP를 사용한다면 cluster를 만들 때 Workload Identity를 enable 해줘야한다. 이는 GCP의 service account(gsa)의 권한을 K8s service account(ksa)에게 부여하기 위해 쓰인다.</li>
<li>external-secret을 사용하기 위한 일종의 라이브러리 설치라고 보면 된다.</li>
</ul>
<pre><code class="language-jsx">gcloud container clusters update opuslab-be-dev \
    --update-addons ConfigConnector=ENABLED --zone us-central1-c

helm install external-secrets \
   external-secrets/external-secrets \
    -n external-secrets \
    --create-namespace </code></pre>
<ul>
<li>gsa =&gt; ksa 권한 바인딩. 이 명령어를 사용하기 전에 gcp에서 gsa를 생성하고 아래 두 권한이 부여되어 있어야 한다.<pre><code class="language-jsx">gcloud iam service-accounts add-iam-policy-binding \
  --role roles/iam.workloadIdentityUser \
  --member &quot;serviceAccount:lgair-aihub.svc.id.goog[opuslab-be-main/opuslab-be-main]&quot; \
  external-secret@lgair-aihub.iam.gserviceaccount.com

</code></pre>
</li>
</ul>
<p>gcloud iam service-accounts add-iam-policy-binding <br>    --role roles/iam.serviceAccountTokenCreator <br>       --member &quot;serviceAccount:lgair-aihub.svc.id.goog[opuslab-be-main/opuslab-be-main]&quot; <br>    <a href="mailto:external-secret@lgair-aihub.iam.gserviceaccount.com">external-secret@lgair-aihub.iam.gserviceaccount.com</a></p>
<p>```</p>
<ul>
<li>gcp console에서 secret manager에서 secret을 만들고 값을 넣어준다.<h2 id="명령어-모음">명령어 모음</h2>
</li>
<li>helm install app-name . -n namespace-name -f values.yaml -f values-dev.yaml</li>
<li>helm uninstall app-name -n namespace-name</li>
<li>helm upgrade --install app-name . -n namespace-name</li>
</ul>
<h2 id="secret">Secret</h2>
<img style= "margin-left: 40px" src="https://velog.velcdn.com/images/nice-one-roy/post/0fd31ac6-28a7-44b7-a00c-2cdec1f0bab1/image.png" width="70%" height="60%" >

<h2 id="주의할-점">주의할 점</h2>
<ul>
<li>DNS에 도메인을 구매하고 subdomain을 만드는 건 따로 해야한다.</li>
<li>helm install하면 고정 ip가 하나 생긴다. 이를 subdomain의 A 레코드에 등록한다.</li>
<li>helm install하면 managed certificate도 생긴다. 시간이 좀 소요된다. dns에 ip를 등록해야 validate된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD 구축하기(1)-Gitlab-ci(Gitlab-ci + Helm + ArgoCD, GCP kubernetes 등)]]></title>
            <link>https://velog.io/@nice-one-roy/CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B01-Gitlab-ciGitlab-ci-Helm-ArgoCD-GCP-kubernetes-%EB%93%B1</link>
            <guid>https://velog.io/@nice-one-roy/CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B01-Gitlab-ciGitlab-ci-Helm-ArgoCD-GCP-kubernetes-%EB%93%B1</guid>
            <pubDate>Sun, 04 Aug 2024 08:21:55 GMT</pubDate>
            <description><![CDATA[<h2 id="목표">목표</h2>
<ul>
<li>GCP에 있는 K8S 서비스의 CI/CD를 구축해본다.</li>
<li>기술 스택은 Gilab-ci, helm chart, argoCD</li>
<li>최종 결과의 Flow는 gitlab에 소스코드를 푸시하면 gitlab ci가 소스 코드를 빌드하고 Docker Registry에 푸시한다. Helm chart manifest(이 프로젝트에서는 GCP Source repository라는 서비스를 사용했다. 특별한 서비스는 아니고 gcp에 있는 git이다. 회사 네트워크 보안 때문에 이를 사용하게 됐다.)의 태그를 바꿔주고 argoCD가 Helm manifest를 참조해서 알맞는 Docker image를 가져와 k8s에 서비스를 배포하게 되는 구조다.<img src="https://velog.velcdn.com/images/nice-one-roy/post/114fa0c0-0373-49db-9039-a41533d311bb/image.png" width="70%" height="60%">

</li>
</ul>
<p>구축하기에 순서는 helm, argoCD, gitlab-ci가 맞는 것 같은데 정리는 가장 쉬운 Gitlab-ci부터 해보자
일단 stage라는 걸 나누게 되는데 나는 아래와 같이 stage를 나눴고. 추후에 테스트 코드 작성 후에 test라는 stage는 추가해야겠다.</p>
<ul>
<li>build: 소스 코드를 빌드 후 Docker Registry(GCP Artifact Registry)에 푸시. 태그는 date를 사용했다.</li>
<li>bake: Helm manifest가 있는 Repository(GCP Source Repository)에 variable 중 tag를 위에서 사용한 tag로 수정해준다. </li>
<li>migration: DB migration 해주는 부분. Database를 사용하지 않는 서비스라면 필요하지 않는 stage</li>
<li>deploy: 구축된 argoCD 서버에 sync 명령을 날려주는 stage. argoCD가 먼저 구축되어 있어야하고, auto sync만 사용해도 괜찮은 경우도 있을 듯하다.</li>
</ul>
<p>코드도 어렵지 않고 읽어보면 쉽게 이해할 수 있을 것 같다. 지피티와 함께라면..</p>
<pre><code class="language-yaml">stages:
  - build
  - bake
  - migration
  - deploy
variables:
  DEV_VALUES_FILE: &quot;values-dev.yaml&quot;

build:
  stage: build
  image: docker:24.0.5
  tags:
    - shared
  only:
    - dev
  environment:
    name: dev
  services:
    - docker:24.0.5-dind
  script:
    - export TAG=$(date +&#39;t%Y%m%d%H%M%S&#39;)
    - echo &quot;TAG=$TAG&quot; &gt;&gt; build.env
    - apk update
    - apk --no-cache add git curl tar python3 ca-certificates
    - curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-457.0.0-linux-x86_64.tar.gz
    - tar -xf google-cloud-cli-457.0.0-linux-x86_64.tar.gz
    - ./google-cloud-sdk/install.sh --quiet
    - ./google-cloud-sdk/bin/gcloud init --console-only
    - ./google-cloud-sdk/bin/gcloud auth activate-service-account --key-file $SERVICE_ACCOUNT_SECRET --project $PROJECT_ID
    - ./google-cloud-sdk/bin/gcloud auth configure-docker $REGION
    - cat $SERVICE_ACCOUNT_SECRET | docker login -u _json_key --password-stdin https://$REGION
    - docker build -t $IMAGE_NAME:latest -f Dockerfile .
    - docker tag $IMAGE_NAME:latest $DOCKER_REGISTRY/$IMAGE_NAME:$TAG
    - docker push $DOCKER_REGISTRY/$IMAGE_NAME:$TAG
    - docker rmi $IMAGE_NAME:latest
  artifacts:
    reports:
      dotenv: build.env

bake_dev_manifest:
  stage: bake
  image: google/cloud-sdk:alpine
  tags:
    - shared
  environment:
    name: dev
  only:
    - dev
  dependencies:
    - build
  script:
    - gcloud init --console-only
    - gcloud auth activate-service-account --key-file $SERVICE_ACCOUNT_SECRET --project $PROJECT_ID
    - gcloud --version
    - gcloud auth configure-docker $REGION
    - docker login -u _json_key --password-stdin https://$REGION &lt; &quot;$SERVICE_ACCOUNT_SECRET&quot;
    - gcloud source repos clone $MANIFEST_REPOSITORY
    - echo &quot;Modifying manifest file!&quot;
    - ls
    - cd $MANIFEST_REPOSITORY
    - sed -i &quot;s|tag:.*|tag:\ $TAG|&quot; $DEV_VALUES_FILE
    - git config user.email &quot;argo-cd@xxxxxx.iam.gserviceaccount.com&quot;
    - git config user.name &quot;argo-cd&quot;
    - git add $DEV_VALUES_FILE
    - git commit -m &quot;Modify docker tag $TAG&quot;
    - export MANIFEST_COMMIT_HASH=$(git rev-parse HEAD)
    - git push origin HEAD
    - cd ..
    - echo &quot;MANIFEST_COMMIT_HASH=$MANIFEST_COMMIT_HASH&quot; &gt;&gt; build.env
  artifacts:
    reports:
      dotenv: build.env

migration_dev:
  stage: migration
  image: node:18
  tags:
    - shared
  environment:
    name: dev
  only:
    - dev
  dependencies:
    - bake_dev_manifest
  script:
    - npm install @nestjs/typeorm typeorm pg
    - npm run typeorm:migration:run

deploy_dev:
  stage: deploy
  image: alpine:latest
  tags:
    - shared
  environment:
    name: dev
  only:
    - dev
  dependencies:
    - migration_dev
  script:
    - apk add --no-cache wget
    - &gt;
      wget --no-check-certificate \
        --method POST \
        --header &quot;Authorization: Bearer $ARGOCD_API_KEY&quot; \
        --header &quot;Content-Type: application/json&quot; \
        --body-data &#39;{
          &quot;appNamespace&quot;: &quot;argocd&quot;,
          &quot;revision&quot;: &quot;HEAD&quot;,
          &quot;prune&quot;: false,
          &quot;dryRun&quot;: false,
          &quot;strategy&quot;: { &quot;hook&quot;: { &quot;force&quot;: false } },
          &quot;resources&quot;: null,
          &quot;syncOptions&quot;: { &quot;items&quot;: [] }
        }&#39; \
        &quot;$ARGOCD_API_SERVER/api/v1/applications/xxxxxxx/sync&quot;
  artifacts:
    reports:
      dotenv: build.env
</code></pre>
<p>Gitlab UI에서 요런걸 볼 수 있게 된다. 언제 파이프라인을 돌릴 지 브랜치 정책과 관련해서 잘 결정하면 된다.
<img src="https://velog.velcdn.com/images/nice-one-roy/post/32413231-ac68-4957-a8ab-b87ee45d40ee/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ElasticSearch Practice(2)]]></title>
            <link>https://velog.io/@nice-one-roy/ElasticSearch-Practice</link>
            <guid>https://velog.io/@nice-one-roy/ElasticSearch-Practice</guid>
            <pubDate>Mon, 15 Jan 2024 02:40:03 GMT</pubDate>
            <description><![CDATA[<h2 id="목표">목표</h2>
<ul>
<li>K8S에 ES + kibana를 배포해보자.</li>
<li>RestAPI를 통해 테스트해보고 예상한대로 작동하는지 확인해보자.</li>
<li>Kibana를 통해 데이터를 확인해보자.</li>
</ul>
<h2 id="directory-structure">Directory Structure</h2>
<pre><code class="language-jsx">elasticsearch
├── LICENSE
├── README.md
├── elasticsearch
│   ├── elasticsearch.yml
│   ├── jvm.options
│   └── log4j2.properties
├── kustomization.yaml
└── resources
    ├── client.yaml
    ├── data.yaml
    ├── loadbalancer.yaml
    └── master.yaml
kibana
├── LICENSE
├── README.md
├── kibana
│   └── kibana.yml
├── kustomization.yaml
└── resources
    ├── deployment.yaml
    └── loadbalancer.yaml</code></pre>
<h1 id="code">Code</h1>
<p><a href="https://github.com/HoneyJung/k8s-elasticsearch-kibana">https://github.com/HoneyJung/k8s-elasticsearch-kibana</a>
<a href="https://prod-files-secure.s3.us-west-2.amazonaws.com/9b8f058a-d127-430c-8d68-e58c356b1e38/2b73c722-3f27-4887-bdb7-babda0d45994/es.zip">es.zip</a></p>
<h1 id="cluster-구성">Cluster 구성</h1>
<ol>
<li>kubectl apply -k elasticsearch</li>
<li>kubectl apply -k kibana</li>
<li>kubectl get all 결과</li>
</ol>
<img src="https://velog.velcdn.com/images/nice-one-roy/post/e074ebd4-994d-4271-82d3-852717971912/image.png" width="70%" height="60%">



<h1 id="insert하고-search-해보자">insert하고 search 해보자</h1>
<ol>
<li>Create Index</li>
</ol>
<img style= "margin-left: 40px" src="https://velog.velcdn.com/images/nice-one-roy/post/a0c0da74-0acd-4c33-b573-f640bc348f32/image.png" width="70%" height="60%" >


<ol>
<li><p>Insert data
(똑같은 방식으로 데이터를 하나 더 넣었음)</p>
<pre><code class="language-jsx"> curl -XPOST &quot;http://34.28.75.67/test_index/_doc&quot; -H &#39;Content-Type: application/json&#39; -d&#39;
 {
   &quot;name&quot;: &quot;정재헌&quot;,
   &quot;message&quot;: &quot;Backend 개발자&quot;           
 }&#39;</code></pre>
</li>
</ol>
<img style= "margin-left: 40px" src="https://velog.velcdn.com/images/nice-one-roy/post/aa7f1beb-9cf7-4827-bca1-2c948a04d091/image.png" width="70%" height="60%" >



<ol start="2">
<li><p>search 해보기</p>
<ol>
<li><p>search all</p>
<pre><code class="language-jsx"> curl -X GET &quot;http://34.28.75.67/test_index/_search&quot; -H &#39;Content-Type: application/json&#39; -d&#39;
 {
   &quot;query&quot;: {
     &quot;match_all&quot;: {}
   }
 }&#39;</code></pre>
</li>
</ol>
</li>
</ol>
<img style= "margin-left: 80px" src="https://velog.velcdn.com/images/nice-one-roy/post/65caa4c0-4ec6-4a27-bf34-06505461a5c4/image.png" width="70%" height="60%">


<pre><code>2. search

    ```jsx
    curl -X GET &quot;http://34.28.75.67/test_index/_search&quot; -H &#39;Content-Type: application/json&#39; -d&#39;
    {
      &quot;query&quot;: {
        &quot;match&quot;: {&quot;name&quot;: &quot;정재헌&quot;}
      }
    }&#39;
    ```

    ![Untitled](https://prod-files-secure.s3.us-west-2.amazonaws.com/9b8f058a-d127-430c-8d68-e58c356b1e38/70965475-3f45-4a6c-b468-262787a1d1f1/Untitled.png)</code></pre><ol start="3">
<li>Kibana에서 데이터 확인하기</li>
</ol>
<img style= "margin-left: 40px" src="https://velog.velcdn.com/images/nice-one-roy/post/1c889b3c-698a-4ac5-8140-7f6a2efccd07/image.png" width="70%" height="60%">   

<p>잘 들어가 있다.</p>
<h1 id="node-shard-replica">Node, Shard, Replica</h1>
<img src="https://velog.velcdn.com/images/nice-one-roy/post/58fc05b1-d729-4608-a0a1-ed89fffe50ac/image.png" width="70%" height="60%"> 


<p>p ⇒ primary, r ⇒ replica라는 뜻이다.</p>
<p>1개의 primary shard가 1번 노드에 저장되어 있고 1개의 replica가 0번 노드에 저장되어 있다는 것을 알 수 있다. primary shard와 replica 모두 2개의 데이터와 8.8kb 크기인 것을 확인할 수 있다.</p>
<p>데이터를 좀 더 넣어보자.</p>
<img src="https://velog.velcdn.com/images/nice-one-roy/post/5439b675-ff16-48e5-afc4-53f72720edaf/image.png" width="70%" height="60%"> 

<p>10개가 됐다. 근데 뭔가 이상하다. 데이터 노드가 3개로 설정되어 있는데 왜 정작 사용되는건 2개이다.</p>
<blockquote>
<aside>
💡 An Elasticsearch index consists of one or more primary shards. As of Elasticsearch version 7, the current default value for the number of primary shards per index is **1**. In earlier versions, the default was 5 shards.
</blockquote>
</aside>

<blockquote>
<aside>
💡 The number of replicas each primary shard has. **Defaults to 1**. WARNING: Configuring it to 0 may lead to temporary availability loss during node restarts or permanent data loss in case of data corruption. Auto-expand the number of replicas based on the number of data nodes in the cluster.
</blockquote>
</aside>

<p>Primary shard와 replica 수의 default 값이 1이다. 현재 클러스터에서 test_index만 운영한다고 했을 때는 이 default 값은 효율적이지 않다. 설정 값을 바꿔보자. 근데 es는 기본적으로 존재하는 index의 primary 수를 바꿀 수 없다. 바꾸는 방법이 있긴 하다. 궁금하면 <a href="https://opster.com/guides/elasticsearch/operations/how-to-increase-primary-shard-count-in-elasticsearch/">이곳</a>을 참고하자. 지금은 그냥 새롭게 index를 만들어 보자.</p>
<ul>
<li>생성<img src="https://velog.velcdn.com/images/nice-one-roy/post/c5a9e730-5681-4fc8-b628-fa46f6c6061a/image.png" width="70%" height="60%"> 


</li>
</ul>
<ul>
<li>데이터 insert<img src="https://velog.velcdn.com/images/nice-one-roy/post/c129b848-4e48-4197-a564-ddae7ccbcf49/image.png" width="70%" height="60%"> 

</li>
</ul>
<p>response가 달라졌다. shard 수가 3으로 나온다. 3의 의미는 primary shard + replica 2개를 의미한다.</p>
<pre><code class="language-jsx">  curl -X GET &quot;http://34.28.75.67/_cat/shards&quot;

  Reponse Info
  index shard prirep state docs storage ip node

  index: 인덱스의 이름
  shard: 샤드의 번호
  prirep: primary 또는 replica 샤드인지를 나타냄 (p: primary, r: replica)
  state: 샤드의 상태 (STARTED, UNASSIGNED 등)
  docs: 샤드에 속한 문서의 수
  storage: 샤드가 차지하는 디스크 공간 크기
  ip: 샤드가 위치한 노드의 IP 주소
  node: 샤드가 위치한 노드의 이름</code></pre>
<img src="https://velog.velcdn.com/images/nice-one-roy/post/97a03e75-4faa-4995-99c9-3f9939f9013a/image.png" width="70%" height="60%"> 


<p>총 10개를 넣었는데 분산되고 replica도 잘 작동하는 듯 하다.</p>
<h2 id="nrtnear-realtime">NRT(Near Realtime)</h2>
<p>사실 insert 후에  shard 상태를 보는 작업을 할 때, insert 했음에도 shard 상태에 변화가 없는 경우를 확인할 수 있다. es는 batch 형태로 refresh 되기 때문이다. 그런 경우에 refresh api를 사용하면 된다. </p>
<pre><code class="language-jsx">curl -X GET &quot;http://34.28.75.67/test_index/_refresh&quot;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[ElasticSearch 개념과 동작원리(1)]]></title>
            <link>https://velog.io/@nice-one-roy/ElasticSearch-%EA%B0%9C%EB%85%90%EA%B3%BC-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@nice-one-roy/ElasticSearch-%EA%B0%9C%EB%85%90%EA%B3%BC-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Thu, 11 Jan 2024 08:53:58 GMT</pubDate>
            <description><![CDATA[<h1 id="elasticsearch">ElasticSearch</h1>
<aside>
💡 ElasticSearch는 Apache Lucene(library) 기반의 JAVA 오픈소스 분산 검색 엔진이다. 코어 검색 로직은 Apache Lucene으로 하면서 분산 아키텍처를 적용하고 REST API 등과 같이 사용성을 높이는 추가 기능들이 포함되어 있다.

</aside>

<h1 id="목표">목표</h1>
<ul>
<li>ElasticSearch가 무엇인지 안다.</li>
<li>ElasticSearch의 특징과 핵심 용어들을 안다.</li>
<li>ElasticSearch를 실무적으로 사용하기 위해 최소한의 내부 동작 원리를 파악한다.</li>
</ul>
<h1 id="elasticsearch의-특징">ElasticSearch의 특징</h1>
<ul>
<li>Scale out, Availability - 분산 시스템이기 때문에 수평적으로 확장 가능하다. Replica를 사용하기 때문에 안정성이 높다.</li>
<li>Restful - Data의 CRUD 작업이 HTTP Restful API를 통해 수행된다.</li>
<li>오픈소스</li>
<li>NRT(Near Realtime) - 새로운 문서를 indexing할 때부터 검색 가능한 대기 시간이 1초 정도 걸림.</li>
<li>schema X, Json으로 문서를 다룸.</li>
</ul>
<h2 id="단점">단점</h2>
<ul>
<li>Learning Curve, 진입 장벽</li>
<li>Join X</li>
<li>Transaction and Rollback X</li>
<li>데이터 변경에 효율적이지 않음.</li>
</ul>
<h1 id="elk">ELK</h1>
<p>ELK stack이란 elasticsearch를 포함해서 자주 함께 쓰이는 기술 stack들이다. <strong>E</strong>lasticsearch <strong>L</strong>ogstash <strong>K</strong>ibana 세가지 오픈소스 프로젝트의 이니셜을 합쳐 만든 말이다. 최근 Beats가 새롭게 포함된다.</p>
<img src="https://velog.velcdn.com/images/nice-one-roy/post/17bf0953-6ec3-4f6e-af49-933a18fd7e81/image.png" width="80%" height="80%">
<img src="https://velog.velcdn.com/images/nice-one-roy/post/e79d39c7-b987-4944-823d-c56b287896a7/image.png" width="80%" height="80%">


<ul>
<li>Beats - Beats는 로그 혹은 메트릭이 발생하는 서버에 에이전트로 설치되어 데이터를 수집하고, 이 데이터를 Logstash로 전송한다. 위 다이어그램에는 Beats와 Logstash 사이에 메시지 큐가 위치하는 것을 확인할 수 있는데, 이는 안정성 또는 확장성 등을 위함이다.</li>
<li>Logstash - 데이터 처리 파이프라인 도구. Logstash는 데이터를 적절히 필터링, 가공하여 Elastic Search로 전달한다.</li>
<li>ElasticSearch - 검색</li>
<li>Kibana - REST API를 통해 Elastic Search로부터 데이터를 가져와 유저에게 시각화와 간편한 데이터 검색 기능 등을 제공한다.</li>
</ul>
<p>검색은 어떤 <strong>과정을 통해서 이뤄질까?</strong></p>
<h1 id="유사도-알고리즘">유사도 알고리즘</h1>
<h2 id="tfidf">TF/IDF</h2>
<p>ES 5.0 버전부터 default 알고리즘이 TF/IDF에서 BM25로 변경되었다. BM25는 TF/IDF의 variation이면서 좀 더 복잡하기 때문에 여기서는 TF/IDF로 만족하자.</p>
<p>TF = Term Frequency, 문서 내 특정 단어의 등장 빈도를 의미한다.</p>
<p> IDF = Inverse Document Frequency, 얼마나 특이한 단어인지를 의미한다.  특성 없는 단어들보다 중요한 단어에 가중치를 주기 위함이다.</p>
<img src="https://velog.velcdn.com/images/nice-one-roy/post/0b27a7d0-f5c9-4581-9399-4715dbfd985b/image.png" width="80%" height="80%">

<p>검색을 위해서는 키워드와 문서들의 TF/IDF와 같은 Metric을 계산하게 된다.</p>
<p>대용량 텍스트 데이터가 있고 검색어와 문서들과의 TF/IDF를 계산한다고 가정해보자. RDB에 데이터가 있다고 가정하면 모든 문서를 돌면서 단어의 등장 여부를 count 해야 한다. ES는 어떻게 이걸 빨리 할 수 있을까?</p>
<h1 id="apache-lucene">Apache Lucene</h1>
<p>Apache Lucene은 검색 엔진 라이브러리이다. ES가 대용량 텍스트 데이터 검색을 빠른 속도로 할 수 있는 것은 Lucene을 사용하기 때문이다. 위에서 말했듯이 Lucene이 ElasticSearch의 코어이기 때문에 Lucene의 내부동작을 알면 ElasticSearch의 내부동작의 상당 부분 이해할 수 있다.</p>
<h2 id="inverted-index">Inverted Index</h2>
<p>ES가 RDB보다 더 효과적인 텍스트 검색이 가능한 이유가 뭘까 </p>
<p>ES는 Inverted Index를 사용한다. 이는 책 젤 뒤에 있는 <strong>찾아보기</strong>와 유사하며 단어사전이라도 부른다. 키워드를 키로 하고 키워드가 등장하는 위치를 기록하는 방식이다.</p>
<img src="https://velog.velcdn.com/images/nice-one-roy/post/d2ec33a0-3bba-4af6-b51e-2de0ec38668d/image.png" width="40%" height="40%">


<p>RDB에 있는 책 데이터에서 “과적합”(키워드)가 등장하는 문서를 검색한다고 가정해보자. like %과적합%와 같은 쿼리를 사용하게 될텐데 이는 인덱스를 타지 못하며 대용량 데이터에서 매우 느리다.</p>
<p>반면, Inverted Index를 사용한다고 할 때는 “과적합”이 등장하는 문서를 검색한다고 하면, Inverted Index에서 과적합의 value인 077, 099, 105, 108, 111, 126임을 매우 빠르게 알 수 있다.</p>
<p>사실 Inverted Index를 만드는 것이 이렇게 간단하지는 않고 실제로는 많은 복잡한 처리가 수행된다. </p>
<ul>
<li>term 단위로 쪼갠다(tokenizing) token이 항상 단어가 되는 것은 아니다(whitespace tokenizer)</li>
<li>대소문자 처리</li>
<li>동의어 처리</li>
<li>a, the 같은 불용어 처리</li>
<li>형태소 분석을 통한 ~s, ~ing 처리</li>
<li>유사어 사전 제작 등</li>
</ul>
<p>여기까지 어떻게 ES가 검색을 빨리할 수 있는지에 대해 큰 그림은 이해할 수 있었다.</p>
<h1 id="architecture와-용어">Architecture와 용어</h1>
<img src="https://velog.velcdn.com/images/nice-one-roy/post/7f34c3a5-0026-4829-95a0-fa226d6e6016/image.png" width="70%" height="60%">
<img src="https://velog.velcdn.com/images/nice-one-roy/post/4724d8ae-aab1-4074-a929-655e4f9b3c67/image.png" width="70%" height="60%">


<p>아래의 설명들은 분산 시스템에 익숙한 분들에겐 익숙한 개념일테니 빠르게 넘어가셔도 좋습니다.</p>
<p><strong>클러스터(cluseter)</strong></p>
<ul>
<li>가장 큰 시스템 단위로, 최소 하나 이상의 노드로 이루어진 노드들의 집합</li>
<li>서로 다른 클러스터는 데이터의 접근, 교환을 할 수 없는 독립적인 시스템으로 유지됨</li>
<li>여러 대의 서버가 하나의 클러스터를 구성할 수 있고, 한 서버에 여러 개의 클러스터가 존재할 수도 있음</li>
</ul>
<p><strong>노드(node)</strong></p>
<ul>
<li>Elasticsearch를 구성하는 하나의 단위 프로세스</li>
<li>역할에 따라 Master-eligible, Data, Ingest, Tribe 노드로 구분</li>
<li>master-eligible node : 클러스터를 제어하는 마스터로 선택할 수 있는 노드</li>
<li>master-eligible node 역할 : 인덱스 생성, 삭제 / 클러스터 노드들의 추적, 관리 / 데이터 입력 시 어느 샤드에 할당할 것인지 결정</li>
<li>Data node : 데이터와 관련된 CRUD 작업과 관련있는 노드 / CPU, 메모리 등 자원을 많이 소모하므로 모니터링이 필요 / master 노드와 분리되는 것이 좋음</li>
<li>Ingest node: 데이터를 변환하는 등 사전 처리 파이프라인을 실행하는 역할</li>
<li>Coordination only node : data node와 master-eligible node의 일을 대신하는 노드 / 대규모 클러스터에서 큰 이점이 있음 / 로드밸런서와 비슷한 역할</li>
</ul>
<p><strong>인덱스(Index)</strong></p>
<ul>
<li>RDBMS에서 database와 대응하는 개념.</li>
<li>shard와 replica는 Elasticsearch에만 존재하는 개념이 아니라, 분산 데이터베이스 시스템에도 존재하는 개념.</li>
</ul>
<p><strong>샤드(Shard)</strong></p>
<ul>
<li>데이터를 분산해서 저장하는 방법.</li>
<li>Elasticsearch에서 스케일 아웃을 위해 index를 여러 shard로 쪼갠 것.</li>
<li>기본적으로 1개가 존재하며, 검색 성능 향상을 위해 클러스터의 샤드 갯수를 조정하는 튜닝을 하기도 함.</li>
</ul>
<p><strong>복제(Replica)</strong></p>
<ul>
<li>또 다른 형태의 shard라고 할 수 있음</li>
<li>노드를 손실했을 경우 데이터의 신뢰성을 위해 샤드들을 복제하는 것</li>
<li>따라서 replica는 서로 다른 노드에 존재할 것을 권장</li>
<li>아래 사진에서 보는 바와 같이 Replica1은 Node2에 존재하는 것을 확인할 수 있음</li>
</ul>
<p><a href="https://esbook.kimjmin.net/03-cluster/3.2-index-and-shards">Index, Shard, Replica 개념 링크</a></p>
<p>새로운 데이터가 들어오면 어떤 일이 일어날까</p>
<h1 id="segment">Segment</h1>
<p>Segment : 실제 데이터가 담겨있는 조각. 실제 document와 Inverted Index가 모두 포함되어 있다. 각 노드에는 다수의 segment가 있을 수 있고 검색이 발생하면 모든 segment를 차례로 검색하고 결과를 합치게 된다.</p>
<p>segment의 수가 많으면 당연하게도 성능이 떨어지기 때문에 Lucene은 background에서 주기적으로 segment 파일들을 merge한다. 더 이상 추가 색인이 없이 일정 시간이 지나면 결과적으로 1개의 큰 segment만 남게된다.</p>
<p>새로운 document가 insert 되면 벌어지는 일</p>
<ul>
<li>인메모리 버퍼에 쌓인다.</li>
<li>정책에 따라 내부 버퍼(큐)에 일정 크기 이상의 데이터가 쌓이거나 일정 시간이 흐를 경우 버퍼에 쌓은 데이터를 한꺼번에 모아 처리한다. (ElasticSearch가 NRT인 이유)</li>
<li>모인 데이터를 indexing하고 새로운 하나의 segment를 추가한다.</li>
<li>fsync가 아닌 write로 Disk가 아닌 메모리에만 쓴다. (Disk 쓰는 작업이 cost가 크기 때문에)</li>
<li>일정 주기마다 Disk로 flush해준다.</li>
</ul>
<h3 id="segment의-불변성">Segment의 불변성</h3>
<p>루씬에선 세그먼트가 주기적인 merge 작업에 의해 통합되기 전까지 수정을 허용하지 않는 불변성 (Immutability) 을 지님.</p>
<p>그렇기 때문에 update 연산은 기존 데이터 삭제 후 다시 생성하게 된다.</p>
<p>Delete 연산이 발생하면 flag만 up해서 검색 대상에서 제외를 시키지만 바로 삭제하진 않는다. 실제로 삭제되는 시점은 주기적으로 진행되는 segment merge 시점.</p>
<p>이로 인해 얻을 수 있는 것</p>
<ul>
<li>동시성 문제 회피 가능</li>
<li>Cache 활용성</li>
<li>만약 불변성을 정책이 없다면, 수정이 발생하면 inverted index 다시 전면 수정해야함.</li>
</ul>
<h1 id="reference">Reference</h1>
<p><a href="https://amazoneberea.tistory.com/41">https://amazoneberea.tistory.com/41</a>
<a href="https://m.blog.naver.com/occidere/222855273511">https://m.blog.naver.com/occidere/222855273511</a>
<a href="https://amazoneberea.tistory.com/41">https://amazoneberea.tistory.com/41</a>
<a href="https://loosie.tistory.com/833">https://loosie.tistory.com/833</a>
<a href="https://jaemunbro.medium.com/elastic-search-%EA%B8%B0%EC%B4%88-%EC%8A%A4%ED%84%B0%EB%94%94-ff01870094f0">https://jaemunbro.medium.com/elastic-search-기초-스터디-ff01870094f0</a>
<a href="https://velog.io/@soyeon207/%EC%9D%B4%EB%A1%A0-Elasticsearch-%EB%9E%80">https://velog.io/@soyeon207/이론-Elasticsearch-란</a>
<a href="https://velog.io/@mayhan/Elasticsearch-%EC%9C%A0%EC%82%AC%EB%8F%84-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">https://velog.io/@mayhan/Elasticsearch-유사도-알고리즘</a>
<a href="https://wikidocs.net/31698">https://wikidocs.net/31698</a>
<a href="https://blog.naver.com/occidere/222855568900">https://blog.naver.com/occidere/222855568900</a>
<a href="https://hudi.blog/elk-stack-overview/">https://hudi.blog/elk-stack-overview/</a>
<a href="https://icarus8050.tistory.com/52">https://icarus8050.tistory.com/52</a>
<a href="https://ksk-developer.tistory.com/24">https://ksk-developer.tistory.com/24</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023 회고]]></title>
            <link>https://velog.io/@nice-one-roy/2023-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@nice-one-roy/2023-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 23 Dec 2023 12:00:29 GMT</pubDate>
            <description><![CDATA[<h1 id="good-bye-bobidi">Good Bye Bobidi</h1>
<p>내 첫 정직원 회사인 바비디를 떠나게 됐다. 바비디를 떠나기로 마음 먹었던 이유</p>
<blockquote>
<ul>
<li>바비디에서 에너지를 쏟아냈기 때문에 바비디에서의 남은 에너지가 고갈되었음을 느꼈다.</li>
</ul>
</blockquote>
<ul>
<li>기술적으로 더욱 다양한 challenge들을 경험하고 싶었다. 바비디에서는 서비스를 빠르게 기획하고 만드는 것이 중요했다. 개발자들도 기획과 user research까지 할 정도로 product의 처음부터 끝까지를 경험할 수 있다는 장점이 있었다. 동시에 기술적인 challenge한 상황이 생길 정도로 고도화된 제품을 경험하지 못한다는 단점이 있었다.</li>
<li>&quot;한 살이라도 어릴 때 큰 회사를 경험해보자!&quot;. 스스로를 스타트업을 선호하는 사람이라고 생각해왔는데 &quot;정말 그럴까?&quot;라는 생각이 들었다. 물론 대기업에서 주는 안락함(?)도 부러웠고 겪어보고 싶었다.</li>
</ul>
<p>이직은 정말 너무 힘든 일인 것 같다. 힘든 것도 힘든거지만 너무 에너지 소모가 큰 일이다. 다니고 있는 회사에서 일은 일대로 하면서 이직 준비를 해야하기 때문이다. 회사도 알아보고 지원해야하며 이력서를 작성해야하고 면접 준비도 해야한다. 그래도 성공적으로 이직한 나 아주 칭찬한다.</p>
<p>떠나게 됐지만 좋은 추억들이 많고 많이 배웠던 바비디, 같이 재밌고 열심히 일했던 동료들, 나를 신뢰해주시고 격려해주신 대표님들에게 감사함을 전하고 싶다.</p>
<h1 id="여행">여행</h1>
<p>이직은 너무 힘들고 스트레스를 유발하는 일이다. 그럼에도 이직의 장점 중 하나는 이직 사이 텀에 가는 쉴 수 있는 시간이 아닐까 싶다. 여행을 좋아하는 나에게는 긴 기간 동안 여행을 갈 수 있는 최고의 기회가 아닐까 싶다.</p>
<h3 id="사하라의-로망">사하라의 로망</h3>
<p>내가 가장 좋아하는 책은 생택쥐베리의 &quot;어린왕자&quot;다. 최근에 재밌게 읽은 책은 &quot;연금술사&quot;. 문학이 아닌 책 중 가장 재밌게 읽은 책은 &quot;나는 세계일주로 경제를 배웠다&quot;.</p>
<p>이 세 책의 공통점은 사하라 사막이 등장한다는 것이다. 그렇다. 난 사하라 사막에 대한 로망이 있었다. 특히 어린왕자를 읽고 상상해왔다. 쏟아질 듯한 별들 아래서 모닥불을 피워놓고 어린왕자와 대화할 수 있는 곳. 귀여운 사막 여우가 있는 곳. 숨겨진 보물이 있는 곳. 나에게 사하라 사막이란 낭만 있는 곳이었다.</p>
<p>모로코를 선택했다. 모로코는 신비한 곳이다. 아프리카이면서 중동과 유럽이 섞여있다. 무슬림이면서 프랑스어를 공용어로 사용한다. (프랑스와 사이가 좋지 않고 베르베르어와 아랍어도 많이 사용하지만) 사실, 무슬림에 대한 막연한 두려움이 있었다. 하지만 거기서 느낀 것은 매우 착하고 친절한 사람들이다. 위협적인 상황은 전혀 없었다.</p>
<p>사하라 사막의 밤하늘이 정말 아름다웠다. 사하라 사막 투어를 어떤 가족과 함께 했는데 가족끼리 밤하늘을 보고 감상하는 모습이 부럽고 좋아보였다. 다음에는 내가 사랑하는 사람과 사막의 밤하늘을 감상해보고 싶다.</p>
<h4 id="사하라에-대한-감상">사하라에 대한 감상</h4>
<p>사하라 사막의 낮은 당연하게도 너무 너무 덥다. 계속 물을 마시게 되고 정말 숨이 턱턱 막히는 더위와 햇빛이다. 낮 투어가 끝나고 어둠이 찾아오자 리터럴리 언제 그랬냐는 듯 사막은 차갑게 식었다. 마치 생동감 있던 영화가 갑자기 갑자기 흑백 사진으로 바뀐 것 같았다.</p>
<p>의외로 내가 느낀 감정은 슬픔이었다. 지금 나한테 있는 어떠한 것도 언젠가 그렇게 될 것이라는 생각이 들었다. 예를 들면, 지금은 나름 건강하고 에너지 넘치는 나의 생명도 한순간에 언제 그랬냐는 듯이 사라져버릴 것 같은 느낌이었다. 사실은 매우 자연스러운 일이지만 너무 적나라하게 느껴졌다.</p>
<h4 id="식중독">식중독</h4>
<p>음식은 나와 맞지 않았다. 그나마 꼬치 같은 음식을 먹을 수 있었는데 막바지에 먹은 음식이 문제가 있었나 보다. 아무래도 위생이 좋지 않다 보니 그랬나보다. 모로코를 떠나서 런던에 도착하자 마자 정말 심각한 식중독에 걸린다...</p>
<h4 id="6대주-정복">6대주 정복</h4>
<p>무슨 의미가 있겠냐만은 지구의 6개의 대륙에 다 가보게 됐다. 뿌듯하다. &quot;태어난 김에 세계일주&quot;라는 프로그램처럼 태어난 김에 더 열심히 여행다녀야겠다.</p>
<h3 id="손흥민">손흥민</h3>
<p>흥민이형의 나이가 어느덧 만 31살이 되었다. 토트넘의 주장이 됐고 여전히 절정의 기량을 펼치고 있다. 하지만 언제 에이징 커브가 와도 이상하지 않은 나이가 됐다. 개인적으로 대한민국에서 손흥민 같은 선수는 내가 죽을 때까지 나오기 힘들다고 생각한다. 손흥민 국가대표 경기는 본 적이 있지만 소속팀 경기를 직관을 해본적이 없었다. 따라서 이번에 무조건 봐야겠다는 생각이었다. 이번 여행의 가장 큰 목표였다. 그래서 축구 경기 일정에 맞추다 보니, 원래 모로코와 포르투갈이 가까워서 묶어서 가곤 하는데 경로가 이렇게 비효율적이게 됐다. 그만큼 축구가 중요했다.</p>
<p>런던에 도착하자마자 끔찍한 식중독 증상이 왔다. 원래도 축구에만 관심 있었기 때문에 관광지에 대해서는 깊게 생각하지 않았다. 하지만 정말 몸이 너무 아팠고 설사도 너무 심했다. 그래서 아무것도 못하고 숙소에만 있었다...일주일 넘게...축구만 겨우 봤다. 특히 토트넘 대 첼시는 경기 보면서 화장실도 많이 갔고, 경기 중에도 너무 아팠다. 또 얼마나 춥던지...돌아오는 길에 진짜 춥고 아파서 너무 고통스러웠다. 정신력으로 악으로 깡으로 다녀왔다. 근데 결과도 충격적이었다. 무패로 1등을 달리던 토트넘이 핵심 선수 2명 퇴장에 핵심 선수 반더벤의 부상까지 대패했다. 2명이 부족한 상황에서도 라인을 올리는 낭만 있는 경기를 펼쳤치긴 했지만. 결과적으로 완패했다. 인상적인 경기이긴 했다.</p>
<p>2번째 경기인 울버햄튼 경기는 1타 2피 경기였다. 울버햄튼에 황희찬도 있기 때문이다. 울버햄튼이란 도시는 축구팀 빼고는 생소한 도시였는데 너무 예쁘고 평화로운 도시였다. 토트넘 경기 좌석이 경기를 보기 가장 좋은 좌석(60만원)이었다면, 이번 경기는 응원이 가장 격한 곳이었다. 코너 플레그 바로 앞이고(15만원) 바로 옆에 어웨이 팬들이 있다. 애초에 스탠딩 좌석이라고 쓰여있는 곳이다. 현지 분위기를 느끼기에는 이런 좌석을 추천한다. 정말 미친 응원의 열기를 느낄 수 있다. 어웨이 팬들을 향한 욕과 조롱은 덤. 울버햄튼 같은 팀의 특성이기도 하다. 빅클럽이 아니기 때문에 팬들이 더 열성적이고 찐팬들이 많다. 토트넘은 런던 연고이기도 하고 빅클럽이기 때문에 &quot;한번 구경 온&quot; 느낌의 팬들이 많다. 경기장도 비교가 많이 됐다. 토트넘 구장은 EPL에서 가장 최신 구장인만큼 깨끗하고 신식이다. 반면, 울버햄튼 경기장은 비교적 작고 구식이다. 복도가 좁고 화장실도 작아서 하프타임에 화장실 가려다 돌아왔다...이 경기도 레전드 경기였다. 두 코리안 리거가 별다른 활약은 없었지만 울버햄튼이 극적인 역전승을 거뒀다. 미쳐버린 울버햄튼 로컬 팬들의 모습이 아직도 눈에 선하고 그 속에 내가 있었다는게 추억이 된 것 같다. 한 가지 아쉬운 점은 이 경기 이후에 바로 A매치 경기가 있었기 때문에 선수들을 만나지 못했다. 애꿏은 극적인 결승골 주인공 사라비아 선수만 한국 사람들(나 포함)한테 싸인을 해주고 있었다.</p>
<p>현지 할머니가 &quot;영어 할 줄 알아요?&quot;라는 말과 함께 나에게 다가왔다.
이후 조금 스몰톡을 나눴는데 엄청난 한국인 인파를 보고 신기했던 모양이다. 저들은 누구이며 어디에서 왔으며 심지어 티켓은 어디서 났냐며 질문 폭탄을 날렸다. 울버햄튼 시골에 저렇게 많은 한국인들을 본 것이 아마 처음이었나보다. 할머니의 표정이 너무 재밌었다.</p>
<h3 id="포르투">포르투</h3>
<p>저번에 가족 스페인 여행에서 나만 일정상 포르투갈을 가지 못했다. 그 중 가장 가고 싶었던 도시는 포르투였다. 조용하고 아기자기한 도시. 아쉬웠기 때문에 이번에 가게 됐다.</p>
<p>날씨가 안좋았고 흐렸던 것이 아쉬웠지만 도시가 아기자기하고 낭만 가득했다. 여행 마지막에 좀 편하게 쉬는 것이 계획이었고 식중독이 거의 회복하고 왔기 때문에 나름 잘 쉬었던 것 같다.</p>
<h4 id="강도를-만나다">강도를 만나다</h4>
<p>멘탈을 잘 회복하던 중 내 멘탈은 다시 박살난다. 동행과 걸어가다 소매치기를 만난다. 동행이 도와달라고 소리쳤다. 북아프리카계 사람 둘이 있었고 그 중 한명을 붙잡고 있었다. 자신의 여권을 가방에서 빼갔다고 했고 도망치려고 하자 도와달라고 한거였다. 나도 달려들어 거들었다. 거의 제압하자 소매치기는 주머니에서 칼을 빼들었다. 우린 혼비백산해서 도망쳤다. 난 길을 건너 도망치다 영화처럼 차에 치일 뻔 했다.(끼이이익!) 그 동행은 도망치다 넘어져서 칼에 찔릴뻔 했다.(비오는 날이었다) 다행히 착한 현지인들이 도와줬다. 아찔했다. 충격적인건 경찰서가 바로 옆이었는데 아무 도움이 안됐다는 것이다. 계속 도움을 요청했지만 나와보지도 않았다. 참...</p>
<p>격투기를 좋아하기도 하고 MBTI가 극 N인 나는 칼든 괴한을 만나는 상상을 살면서 수천번은 했던 것 같다. 칼을 보자마자 아무 생각도 안들고 도망쳐야겠다는 생각이 들었다. 무력감을 느꼈다. 만약 동행이 아니라 내 가족이었다면 어땠을까?라는 생각이 들었다. 격투기를 제대로 배워볼까...많은 감정을 느끼게 됐다.</p>
<h1 id="hello-lg-ai-research">Hello LG AI Research</h1>
<p>대학교 동기들이 다니고 있기도 해서 일찍이 관심이 있던 회사였다. 동기의 추천을 받기도 했다. 입사가 더 맛있게 느껴졌던 것은 채용 시장이 얼어붙은 시기이기도 하고 면접 날짜가 밀리는 등 채용 과정이 너무 길어져서 애를 태웠기 때문이다.</p>
<p>LG AI Research에 출근한지 얼마 안됐지만 참 오묘한 곳이다. 대기업스럽기도 하고 스타트업스럽기도 하다. LG의 계열사이지만 생긴지 얼마 안됐기도 하고 보다 자유로운 분위기를 추구하는 것 같다. 확실한 건 LG에서 가장 빵빵한 지원을 받는 곳인만큼(현 시점..) 근무 환경이 좋다.</p>
<p>팀 분들이 매우 친절하게 대해준다. 가족같은 분위기가 있고 온화하다. 이게 입사하고 알게된 LG의 &quot;인화사상&quot;인가 싶다. 거친 토론(?)에 익숙해져있는 나에게 좀 생소한 분위기이다.</p>
<p>같은 날 입사한 동료도 있고 며칠 뒤에 입사하신 분도 있어서 한결 수월하게 적응하고 있는 것 같다. 팀에 친구가 있는 것도 역시 큰 도움이 된다. 감사하게 생각한다.</p>
<h1 id="반성--다짐">반성 &amp; 다짐</h1>
<h2 id="걱정-적응-성장">걱정, 적응, 성장</h2>
<p>새로운 회사에 잘 적응하기. 열심히 잘 해보고 싶은 마음이 크다. 이곳에선 MLOps를 많이 하는 것 같은데 나에게는 생소한 분야다. 러닝 커브가 걱정되기도 하지만 그만큼 많이 배울 수 있을 것 같다. 열심히 공부해서 빨리 퍼포먼스를 내고싶다. 기술적으로도 잘 성장하고 싶고 내 커리어에 대해서도 잘 생각해보고 준비하고 싶다.</p>
<p>사실 LG AI Research는 지금까지 경력직들만 뽑아왔다. 나도 경력직으로 오긴 했지만 내 경력은 해봐야 2년이다. 그래서 더욱 걱정되고 긴장이 된다. 비슷한 시기에 입사한 2명도 나보다 훨씬 긴 경력을 보유하고 있다. 걱정되지만 몸으로 떼워야지 싶다. 압박감이 있는 만큼 빠르게 성장할 수 있길 기대한다.</p>
<h2 id="체력과-게으름">체력과 게으름</h2>
<p>늘 생각하지만 난 게으르다. 여젼히 잠이 너무나 많고 게으름 피우는 걸 좋아한다. 그런 내 모습을 좋아하지 않다. 그리고 그렇게 살수록 체력이 좋지 않게됨을 느끼기도 한다. 체력은 정말 중요하다. 내년에는 체력적으로 강한 사람이 되고 싶다. 강한 체력과 더불어 시간을 더 효율적으로 쓸 수 있는 사람이 되고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vector DB]]></title>
            <link>https://velog.io/@nice-one-roy/Vector-DB</link>
            <guid>https://velog.io/@nice-one-roy/Vector-DB</guid>
            <pubDate>Wed, 27 Sep 2023 04:03:45 GMT</pubDate>
            <description><![CDATA[<h1 id="vector-db">Vector DB</h1>
<p>LLM, Generative AI, semantic search 등을 포함하는 기술이 발전하면서 Vector DB의 중요성도 커졌다. AI는 벡터로 Data를 이해한다. 여기서 말하는 vector는 간단하게는 다차원 공간에서 존재하는 한 점 혹은 좌표라고 상상하면 된다. </p>
<p>그래서 Vector DB가 뭐냐? vector를 다루는데에 특화된 Database이다. 대표적으로는 일반적인 RDB나 NoSQL에서 제공하지 않는 vector embeddings의 search나 retrieval을 효과적으로 수행할 수 있는 알고리즘 및 시스템을 제공한다.</p>
<h1 id="vector-index">Vector Index</h1>
<p>Vector Index랑 Vector DB가 혼용돼서 쓰이곤 하는데, Vector Index는 Vector DB의 핵심 기능이자 포함되는 기능이다. </p>
<p>대표적인 Vector Index는 FAISS(Facebook AI Similarity Search).</p>
<p>FAISS는 facebook research에서 개발한, dense vector들의 클러스터링과 유사도를 구할때 사용하는 Standalone vector index 라이브러리이다. C++로 작성되었으며 python에서 지원된다. 그리고 GPU 상에서도 효율적으로 동작하도록 개발 되었다. 구체적인 알고리즘은 뒤에서 살펴보자.</p>
<p>어쨌든 Vector Index에 추가로 일반적인 Database이 하는 다음과 같은 일들도 해준다.</p>
<ul>
<li>Data management : CRUD와 같은 기능들.</li>
<li>Metadata storage and filtering : vector 뿐만 아니라 일반적인 형태의 데이터(meta data)들도 함께 다룰 수 있다.</li>
<li>Scalability : Support for distributed and parallel processing.</li>
<li>Backup, 다른 tool들과 integration, Security, Access control</li>
</ul>
<blockquote>
<p>In short, a vector database provides a superior solution for handling vector embeddings by addressing the limitations of standalone vector indices, such as scalability challenges, cumbersome integration processes, and the absence of real-time updates and built-in security measures, ensuring a more effective and streamlined data management experience.</p>
</blockquote>
<h1 id="how-does-vector-db-work">How does vector DB work?</h1>
<p>Vector DB가 잘 해야하는 일은 Approximate Nearest Neighbor(ANN) search이다. 이를 위해서 Hashing, quantization, graph-based search 등의 기법을 사용한다. 때로는 조합해서!</p>
<p>일반적인 Flow
<img src="https://velog.velcdn.com/images/nice-one-roy/post/36e6a0f8-96d1-4cce-ac3d-7d9beaf20905/image.png" alt=""></p>
<ol>
<li><p>Indexing: The vector database indexes vectors using an algorithm such as PQ, LSH, or HNSW (more on these below). This step maps the vectors to a data structure that will enable faster searching.</p>
</li>
<li><p>Querying: The vector database compares the indexed query vector to the indexed vectors in the dataset to find the nearest neighbors (applying a similarity metric used by that index)</p>
</li>
<li><p>Post Processing: In some cases, the vector database retrieves the final nearest neighbors from the dataset and post-processes them to return the final results. This step can include re-ranking the nearest neighbors using a different similarity measure.</p>
</li>
</ol>
<h1 id="algorithms">Algorithms</h1>
<h2 id="prerequisite">Prerequisite</h2>
<h4 id="k-means-algorithm">K-means algorithm</h4>
<p>데이터를 k개의 cluster로 묶는 알고리즘.
<img src="https://velog.velcdn.com/images/nice-one-roy/post/b46d0c25-ab51-48cc-9aec-83151c5a26bb/image.png" alt=""></p>
<h2 id="random-projection">Random Projection</h2>
<p>가장 간단한 알고리즘. 기본 아이디어는 Random Projection Matrix을 사용하여 고차원 벡터를 저차원 공간으로 투영하는 것입니다. 우리는 random number로 matrix를 만든다. 행렬의 크기는 원하는 저차원 공간의 크기로 한다. 그런 다음 입력 벡터와 행렬의 Dot product을 계산하고, 그 결과 원래 벡터보다 더 적은 차원을 가지지만 여전히 유사성을 유지할 것이라고 기대한다.</p>
<p>쿼리가 들어오면 마찬가지로 쿼리도 같은 Projection Matrix을 사용해서 저차원 벡터로 projection하고 저차원 vector를 기반으로 nearest neighbors를 찾는다. 차원을 줄였기 때문에 Search process의 속도를 개선할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/4db362a1-1dc3-4e16-878b-a17120c93b0b/image.png" alt=""></p>
<p>이미 느껴졌을테지만, Random Projection은 Approximate method이다. 그리고 참고로 matrix가 random할수록 성능이 좋다고 한다.</p>
<h2 id="product-quantizationpq">Product Quantization(PQ)</h2>
<p>PQ는 제품 고차원 벡터에 대한 Lossy Compression 기술이다. 원본 벡터를 작은 청크들로 나누고 각 청크에 대한 대표 &quot;코드&quot;를 만들어 각 청크의 표현을 단순화한 다음 유사성 작업에 필수적인 정보를 잃지 않고 모든 청크를 다시 결합합니다.</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/bbd2ac27-8ca9-41be-8d5c-bbdbd0f3ca93/image.png" alt=""></p>
<ol>
<li><p>Splitting -The vectors are broken into segments.</p>
</li>
<li><p>Training - we build a “codebook” for each segment. Simply put - the algorithm generates a pool of potential “codes” that could be assigned to a vector. In practice - this “codebook” is made up of the center points of clusters created by performing k-means clustering on each of the vector’s segments. We would have the same number of values in the segment codebook as the value we use for the k-means clustering.</p>
</li>
<li><p>Encoding - The algorithm assigns a specific code to each segment. In practice, we find the nearest value in the codebook to each vector segment after the training is complete. Our PQ code for the segment will be the identifier for the corresponding value in the codebook. We could use as many PQ codes as we’d like, meaning we can pick multiple values from the codebook to represent each segment.</p>
</li>
<li><p>Querying - When we query, the algorithm breaks down the vectors into sub-vectors and quantizes them using the same codebook. Then, it uses the indexed codes to find the nearest vectors to the query vector.</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/3f65a597-784d-4839-afa9-0635ceb5b69d/image.png" alt=""></p>
<p>PQ알고리즘도 결국 Approximate하는 방식이다. 따라서 K-means 알고리즘에 쓰인 K의 값에 따라 accuracy와 latency의 trade-off가 발생한다.</p>
<h2 id="locality-sensitive-hashinglsh">Locality-sensitive hashing(LSH)</h2>
<p>요거도 비슷하다. vector들을 Locality를 보존하는 hash function을 사용해 bucketdㅡ로 분류하여 저장한다. 그리고 쿼리 또한 같은 hash function으로 해당하는 bucket을 찾는다. 그리고 그 버킷 안에 있는 벡터들과 거리를 계산한다. 버킷을 통해 한번 걸러서 비교해야할 벡터들의 모수를 줄이는 방식.</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/7a5ec258-7975-4883-bd80-1376307e6380/image.png" alt=""></p>
<h2 id="hierarchical-navigable-small-world-hnsw">Hierarchical Navigable Small World (HNSW)</h2>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/fe68f36f-cd02-485e-8489-56007e3d9a07/image.png" alt=""></p>
<p><a href="https://towardsdatascience.com/similarity-search-part-4-hierarchical-navigable-small-world-hnsw-2aad4fe87d37">https://towardsdatascience.com/similarity-search-part-4-hierarchical-navigable-small-world-hnsw-2aad4fe87d37</a>
<a href="https://www.pinecone.io/learn/series/faiss/hnsw/">https://www.pinecone.io/learn/series/faiss/hnsw/</a></p>
<h2 id="vector-db-종류-비교">Vector DB 종류 비교</h2>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center">Qdrant</th>
<th align="center">Milvus</th>
<th align="center">Chroma</th>
<th align="center">Pincone</th>
<th align="center">Weaviate</th>
</tr>
</thead>
<tbody><tr>
<td align="center">Opensource</td>
<td align="center">O</td>
<td align="center">O</td>
<td align="center">O</td>
<td align="center">x</td>
<td align="center">O</td>
</tr>
<tr>
<td align="center">Document</td>
<td align="center">좋음</td>
<td align="center">보통</td>
<td align="center">보통</td>
<td align="center">좋음</td>
<td align="center">보통</td>
</tr>
<tr>
<td align="center">Cloud</td>
<td align="center">O</td>
<td align="center">O</td>
<td align="center">X(준비중)</td>
<td align="center">O</td>
<td align="center">O</td>
</tr>
<tr>
<td align="center">Git stars</td>
<td align="center">13k</td>
<td align="center">23k</td>
<td align="center">8.7k</td>
<td align="center">.</td>
<td align="center">7.6k</td>
</tr>
</tbody></table>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/3861d2cd-37d7-4ebc-8d19-85f66c337d42/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/a49b98dd-af03-48ea-bdb8-1152a0425059/image.png" alt=""></p>
<p><a href="https://objectbox.io/2023_06_13_vector-databases.htm">https://objectbox.io/2023_06_13_vector-databases.htm</a></p>
<h3 id="benchmark">Benchmark</h3>
<p>gist-960-euclidean dataset, which consists of 1 million vectors.
client machine 4cores 8G
DB engine machine 16Cores 32G Ram</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/a80d535d-2c49-47bf-962c-b656f3fbdd31/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/1f3412a6-eaa2-4664-81bc-b77c95be0fba/image.png" alt=""></p>
<p>참고
<a href="https://medium.com/@richard_50832/benchmarking-epsilla-with-some-of-the-top-vector-databases-543e2b7708e5">https://medium.com/@richard_50832/benchmarking-epsilla-with-some-of-the-top-vector-databases-543e2b7708e5</a></p>
<p><a href="https://jina.ai/news/benchmark-vector-search-databases-with-one-million-data/">https://jina.ai/news/benchmark-vector-search-databases-with-one-million-data/</a></p>
<h1 id="references">References</h1>
<p><a href="https://mccormickml.com/2017/10/13/product-quantizer-tutorial-part-1/">https://mccormickml.com/2017/10/13/product-quantizer-tutorial-part-1/</a>
<a href="https://www.pinecone.io/learn/vector-database/">https://www.pinecone.io/learn/vector-database/</a>
<a href="https://www.youtube.com/watch?v=PNVJvZEkuXo">https://www.youtube.com/watch?v=PNVJvZEkuXo</a>
<a href="https://www.pinecone.io/learn/series/faiss/product-quantization/">https://www.pinecone.io/learn/series/faiss/product-quantization/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[RAG(Retrieval Augmented Generation)]]></title>
            <link>https://velog.io/@nice-one-roy/RAG</link>
            <guid>https://velog.io/@nice-one-roy/RAG</guid>
            <pubDate>Mon, 25 Sep 2023 05:57:01 GMT</pubDate>
            <description><![CDATA[<h1 id="rag">RAG</h1>
<p>RAG(Retrieval Augmented Generation)는 미리 학습된 LLM(대규모 언어 모델) 및 자체 데이터를 사용하여 응답을 생성하는 패턴이다.</p>
<h1 id="왜-필요한가">왜 필요한가</h1>
<p>현재 널리 쓰이고 있는 Chatgpt와 같은 LLM은 수많은 범용적인 데이터로 그리고 특정 시점 데이터로(과거 어떤 시점) 학습되었습니다. 그러나 때로는 범용적이지 않고 자신만이 갖고 있는 데이터나 최신 데이터로 작업해야 하는 경우도 있습니다. </p>
<p>해당 경우에 LLM을 활용할 수 있는 두 가지 방식이 있다.</p>
<ol>
<li>새로운 데이터로 모델을 Fine tuning한다.</li>
<li>RAG를 활용한다.</li>
</ol>
<p>Fine tuning은 좋은 해결책이 될 수 있지만 값이 비싸다는 단점이 있어서 RAG가 효율적인 방법이 될 수 있다.</p>
<p>추가적으로 RAG를 잘 사용하면 LLM의 고질적인 문제인 Hallucination도 어느 정도 줄일 수 있다는 장점도 있다.</p>
<h1 id="그래서-어떻게-동작하는건데">그래서 어떻게 동작하는건데</h1>
<p>Flow는 다음과 같다.</p>
<ol>
<li>Data를 chunk로 쪼갠다.</li>
<li>쪼갠 chunk를 embedding model로 vector화한다.</li>
<li>Vector들을 Vector DB에 저장한다. 다음 글에서 Vector DB에 대해 다룰거라서 간단하게만 이야기하자면, 아래 그림의 파란색 부분처럼 Vector Space에 벡터들을 두고 벡터들간의 유사도를 계산할 수 있는 상태.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/55257bd4-2896-4e1f-a0f4-50602369f603/image.png" alt=""></p>
<ol start="4">
<li>유저가 질문한다.(Query)</li>
<li>유저의 query도 embedding model로 vector화한다.</li>
<li>유저의 query와 가장 유사도가 높은 N개의 vector를 retrieval한다.</li>
<li>벡터들에 해당하는 원본 데이터들을 가져온다.</li>
<li>그 데이터들(A)을 Prompt(B)와 조합해 LLM에게 &quot;A라는 정보에서 B라는 질문 대답해줘&quot;라는 식의 명령을 내리고 결과를 유저에게 전달한다. </li>
</ol>
<img src="https://velog.velcdn.com/images/nice-one-roy/post/391c3c38-e281-4dda-ba8e-de9c496ea967/image.png" width="100%" height="100%">


<h1 id="vector-embedding참고">Vector Embedding(참고)</h1>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/2bea340c-e95c-4c20-8f4d-0e03709d2858/image.png" alt=""></p>
<h1 id="memo">Memo</h1>
<p>최근 엄청난 성능을 보이는 LLM들을 보면서 신기함을 느꼈지만 활용에 있어서, 특히 비지니스적으로 애매한 부분이 있었다고 생각하는데 RAG가 좋은 Use case? Framework?이 될 수 있을 것 같당.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nested Loop Join -Join Algorithm(1)]]></title>
            <link>https://velog.io/@nice-one-roy/Nested-Loop-Join-Join-Algorithm1</link>
            <guid>https://velog.io/@nice-one-roy/Nested-Loop-Join-Join-Algorithm1</guid>
            <pubDate>Sat, 29 Jul 2023 06:57:23 GMT</pubDate>
            <description><![CDATA[<h1 id="nested-loop-join">Nested Loop Join</h1>
<h2 id="what-is-nest-loop-join">What is Nest Loop Join</h2>
<p>줄여서 NL JOIN이라고도 불리는 NESTED LOOP JOIN은 2개 이상의 테이블에서 하나의 집합을 기준으로 순차적으로 상대방 Row를 결합하여 원하는 결과를 조합하는 조인 방식입니다. 조인해야 할 데이터가 많지 않은 경우에 유용하게 사용됩니다. NESTED LOOP JOIN은 드라이빙 테이블로 한 테이블을 선정하고 이 테이블로부터 where절에 정의된 검색 조건을 만족하는 데이터들을 걸러낸 후, 이 값을 가지고 조인 대상 테이블을 반복적으로 검색하면서 조인 조건을 만족하는 최종 결과값을 얻어냅니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/nice-one-roy/post/939ade0e-8c93-4241-9c74-b5a39e7cc7d8/image.png" width="90%" height="100%"></p>


<h2 id="cost-analysis">Cost Analysis</h2>
<p>이 Nested Loop Join은 멍청하다. 아래 그림에서 볼 수 있듯이 drving table의 튜플마다 Driven table을 full scan하기 때문이다. 참고로 Cost: M + (m*N)에서 M은 table R을 page 단위로 가져오기 때문에 그 비용이다. 뭔가 더 최적화할 수 있을 것이라 생각한다면 당신은 짱! 하지만 일단 차근차근 보자.</p>
<p align="center"><img src="https://velog.velcdn.com/images/nice-one-roy/post/8f5c924d-7552-4dc3-802f-0dc346dfdf90/image.png" width="90%" height="100%"></p>

<p>수식을 보면 알아차릴 수 있듯이 어떤 테이블이 Driving, Driven table인지에 따라서 Cost가 달라진다. 작은 테이블이 Driving하면 이득. 
<img src="https://velog.velcdn.com/images/nice-one-roy/post/0e26cee2-33ee-4a9b-bfa5-cd9ffb2c542e/image.png" alt=""></p>
<h1 id="block-nested-loop-join">Block Nested Loop Join</h1>
<p>Disk Access에 관해 조금 더 최적화된 버전의 Nested Loop Join이 있다. 그게 바로 Block Nested Loop Join이다. 아래 Pseudo 코드를 보자. </p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/1b5f333f-bb12-4318-9c60-5d70f6b2d981/image.png" alt=""></p>
<h2 id="cost-analysis-1">Cost Analysis</h2>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/9d6580b0-02de-4d30-ad65-0af1fbcd3d99/image.png" alt=""></p>
<p>마찬가지로 작은 테이블이 Driving table일 때 더 이득이다.</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/05562ae7-0237-41d8-ab23-7cdf0ed02d3b/image.png" alt="">
<img src="https://velog.velcdn.com/images/nice-one-roy/post/df420040-0e28-44c1-aabd-887adf991195/image.png" alt=""></p>
<p>근데 위에서 본 Block Nested Loop Join은 2개 block 크기의 메모리를 사용한다. 근데 만약 B개의 block을 사용한다면?</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/d04f712a-8fea-4ad5-8bbc-f02b46cbce8d/image.png" alt="">
<img src="https://velog.velcdn.com/images/nice-one-roy/post/42feeed5-f863-4bcd-b160-83d4bab30fa7/image.png" alt="">
<img src="https://velog.velcdn.com/images/nice-one-roy/post/3054b4ce-27ee-4e89-8b0a-522ef63ff3f5/image.png" alt=""></p>
<p>지금까지는 index가 설정되어 있지 않다는 가정이 있었다. 이번엔 Index와 연관지어 생각해보자.</p>
<h1 id="index-nested-loop-join">Index Nested Loop Join</h1>
<p>먼저 Index에 대해 복습 !
<a href="https://velog.io/@nice-one-roy/Database-Index">https://velog.io/@nice-one-roy/Database-Index</a></p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/0654f463-21a9-4311-89b8-9ec3558619e7/image.png" alt="">
<img src="https://velog.velcdn.com/images/nice-one-roy/post/2eaab6a2-1fe9-4321-9c0e-4f33ab740560/image.png" alt=""></p>
<blockquote>
</blockquote>
<ul>
<li>Key Takeaways
→ Pick the smaller table as the outer table.
→ Buffer as much of the outer table in memory as possible.
→ Loop over the inner table (or use an index).</li>
<li>Algorithms
→ Simple / Stupid
→ Block
→ Index</li>
</ul>
<p>참고자료 CMU database lecture note
<a href="https://15445.courses.cs.cmu.edu/fall2022/slides/11-joins.pdf">https://15445.courses.cs.cmu.edu/fall2022/slides/11-joins.pdf</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[join이란? -Join Algorithm(0)]]></title>
            <link>https://velog.io/@nice-one-roy/join%EC%9D%B4%EB%9E%80-Join-Algorithm0</link>
            <guid>https://velog.io/@nice-one-roy/join%EC%9D%B4%EB%9E%80-Join-Algorithm0</guid>
            <pubDate>Fri, 28 Jul 2023 09:51:49 GMT</pubDate>
            <description><![CDATA[<h2 id="join이-무엇이며-왜-필요한가">Join이 무엇이며 왜 필요한가?</h2>
<p>관계형 데이터베이스에서 테이블을 정규화하여 불필요한 정보의 반복을 방지합니다. 그런 다음 조인 연산자를 사용하여 정보 손실 없이 두 개 이상의 테이블로 나누었던 데이터를 조합할 수 있다.</p>
<center>
<img src="https://velog.velcdn.com/images/nice-one-roy/post/b42877cf-010f-401a-9bfa-1ff2aaefe168/image.png" width="50%" height="50%">
</center>

<center>  
<img src="https://velog.velcdn.com/images/nice-one-roy/post/b01c9cac-92e3-4941-869d-84b7605293cd/image.png" width="50%" height="30%"></center>


<h2 id="성능">성능</h2>
<h3 id="optimizer">optimizer</h3>
<p>optimizer는 query에 대해 최적의 실행방법을 결정하는 역할을 하는 녀석이다. 즉 query를 실제로 어떻게 실행시킬지 Excution Plan을 짠다.</p>
<p><strong>Cost Metric for Analysis: # of IOs to compute join</strong></p>
<p>Assume:
→ M pages in table R, m tuples in R
→ N pages in table S, n tuples in S</p>
<p><strong>There are many algorithms for reducing join cost, but no algorithm works well in all scenarios.</strong></p>
<p>=&gt; 늘 그렇듯 Silver bullet은 없고 다양한 알고리즘이 있고 상황에 따라 알맞는 알고리즘이 사용되야한다.</p>
<p>유명한 알고리즘들을 순서대로 정리해보자. 첫번째로 Nested Loop !!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Javascript 동작 원리 (2) - 실행순서]]></title>
            <link>https://velog.io/@nice-one-roy/Javascript-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-2-%EC%8B%A4%ED%96%89%EC%88%9C%EC%84%9C</link>
            <guid>https://velog.io/@nice-one-roy/Javascript-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-2-%EC%8B%A4%ED%96%89%EC%88%9C%EC%84%9C</guid>
            <pubDate>Wed, 19 Jul 2023 01:39:14 GMT</pubDate>
            <description><![CDATA[<p>이번에는 Event loop에서 javascript를 특히 비동기 함수들을 어떤 순서로 실행 순서로 실행시키는지 예시 위주로 알아보자.</p>
<h2 id="동기--settimeout">동기 &amp; SetTimeout</h2>
<h3 id="1">1</h3>
<p>첫번째로 기본적인 동기 함수와 SetTimeout의 콜백 함수가 어떤 방식으로 실행되는지 보자.</p>
<pre><code>const foo = () =&gt; console.log(&#39;First&#39;);
const bar = () =&gt; setTimeout(() =&gt; console.log(&#39;Second&#39;), 500);
const baz = () =&gt; console.log(&#39;Third&#39;);

bar();
foo();
baz();</code></pre><p>정답 : Frist -&gt; Third -&gt; Second</p>
<ol>
<li>bar 함수를 Call Stack에 넣었다.</li>
<li>setTimeout이 있으므로 Web API에 집어넣고 bar 함수는 Call Stack에서 제거된다.</li>
<li>Web API에서 타이머가 실행되는 동안 foo가 호출되어 Call Stack에 쌓였다.         console을 찍는다.</li>
<li>baz가 호출되었고 console을 찍고 제거된다.</li>
<li>그 동안 Web API에서는 Task Queue에 setTimeout에 있던 콜백을 집어넣는다.</li>
<li>Event Loop는 Call Stack에서 아무것도 없음을 확인한 후 Task Queue에 있던 콜백을 Call Stack에 넣는다. 그리고 실행.</li>
</ol>
<p align="center"><img src="https://velog.velcdn.com/images/nice-one-roy/post/c8fb7967-c91d-4404-ba0a-d290d8250170/image.gif" width="90%" height="100%"></p>



<p>이런 식으로 Event Loop는 실행할 함수를 관리하는 역할로 Call Stack과 Task Queue의 함수를 계속 확인한다. 이렇게 반복되는 매 순회(iteration)를 tick 이라고 부른다.</p>
<p><strong>SetTimeout같은 콜백 함수는 Task Queue로 들어가게 되고 Task Queue에 있는 task들은 Call Stack이 비어 있어야지만 실행된다.</strong></p>
<h3 id="2">2</h3>
<pre><code>function delay() {
  for (var i = 0; i &lt; 100000; i++);
}
function foo() {
  delay();
  bar();
  console.log(&#39;foo!&#39;); // (3)
}
function bar() {
  delay();
  console.log(&#39;bar!&#39;); // (2)
}
function baz() {
  console.log(&#39;baz!&#39;); // (4)
}

setTimeout(baz, 10); // (1)
foo();</code></pre><p>출력은 어떤 순서로 될까? delay 함수는 10만의 연산을 해야하므로, 꽤 올래 걸리기 때문에 baz가 가장 먼저 찍힐까? 아니다. setTimout이 Task Queue에 넣은 후, Call Stack이 비어있을 경우 Event Loop가 Task Queue에 있는 baz를 Call Stack으로 넘겨줄 것이기 때문에 baz가 가장 나중에 찍힌다. setTimeout의 두번째 인자인 10 은 10ms 라는 의미를 가진다. 즉, 0.01초다. 그럼에도 불구하고 10ms 보다 더 늦게 실행될 것이다. 즉, 자바스크립트의 타이머는 정확한 타이밍을 보장해주지 않는다.</p>
<h2 id="promise">Promise</h2>
<h3 id="1-1">1</h3>
<pre><code>setTimeout(function () {
  // (A)
  console.log(&#39;A&#39;);
}, 0);
Promise.resolve()
  .then(function () {
    // (B)
    console.log(&#39;B&#39;);
  })
  .then(function () {
    // (C)
    console.log(&#39;C&#39;);
  });</code></pre><p>Promise도 비동기로 실행되니까 Task Queue에 추가되어 순서대로 A -&gt; B -&gt; C로 찍힐까? 아니다 답은, B -&gt; C -&gt; A다. 이유는 바로 Promise가 MicroTask Queue를 사용하기 때문이다.</p>
<p>MicroTask Queue는 일반 Task Queue보다 더 높은 우선순위를 갖는 태스크다. Task Queue에 대기중인 태스크가 있더라도 MicroTask Queue가 먼저 실행된다. setTimeout은 콜백 A를 Task Queue에 추가하고 Promise의 then() 메서드는 콜백 B를 Task Queue가 아닌 MicroTask Queue에 추가한다. 콜백 B가 실행되고 나면 두번째 then() 메서드가 콜백 C를 MicroTask Queue에 추가한다. Event Loop는 다시 MicroTask Queue를 확인하고, 큐에 있는 콜백 C를 실행한다.</p>
<p>이후에 MicroTask Queuerk 비었음을 확인한 다음 Task Queue에서 콜백 A를 꺼내와 실행한다. 즉, MicroTask Queue에는 Promise가 담기며 Event Loop가 Task Queue 보다 먼저 실행한 후, 다시 then 절이 있는지 확인하고 다시 MicroTask Queue에 집어넣었다.</p>
<p>MicroTask Queue에는 Promise뿐 아니라, Observer API, Node.js의 process.nextTick 등이 그 대상이 된다.</p>
<p>📍 마이크로 태스크 vs 매크로 태스크</p>
<ul>
<li>Queue는 Macro Queue와 MicroQueue가 있다. Micro Queue가 우선순위가 더 높다.</li>
</ul>
<p>마이크로 태스크들은 실행하면서 새로운 마이크로 태스크를 큐에 추가할 수도 있다. 새롭게 추가된 마이크로 태스크도 큐가 빌 때까지 계속해서 실행된다.</p>
<p>반대로, 이벤트 루프는 매크로 태스크 큐에 있는 것을 실행시키기 시작할 때 있는 매크로 태스크만 실행시킨다. 매크로 태스크가 추가한 매크로 태스크는 다음 이벤트 루프가 실행될 때까지 실행되지 않는다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/nice-one-roy/post/0ccf7b3e-7230-445e-bc0c-217029a17b69/image.gif" width="90%" height="100%"></p>


<h3 id="2-1">2</h3>
<pre><code>console.log(&#39;Start!&#39;);

setTimeout(() =&gt; {
  console.log(&#39;Timeout!&#39;);
}, 0);

Promise.resolve(&#39;Promise!&#39;).then(res =&gt; console.log(res));

console.log(&#39;End!&#39;);</code></pre><p>정답 : Start =&gt; End =&gt; Promise! =&gt; TimeOut!</p>
<p>Promise는 Macro Queue, Timeout!은 Micro Queue에 들어가고 Call stack이 빌때까지 기다렸다 Micro Queue부터 실행되기 때문이다.</p>
<h2 id="asyncawait">Async/Await</h2>
<p>비동기 함수가 Promise를 반환하는데 await 키워드를 비동기 함수 앞에 붙여주면 비동기 함수가 Promise를 반환할 때까지 코드를 일시 중지 할 수 있다. 다음 코드가 어떻게 실행되는지 살펴보자.</p>
<pre><code>const one = () =&gt; Promise.resolve(&#39;One!&#39;);

async function myFunc() {
  console.log(&#39;In function!&#39;);
  const res = await one();
  console.log(res);
}

console.log(&#39;Before function!&#39;);
myFunc();
console.log(&#39;After function!&#39;);</code></pre><ul>
<li>Before function!</li>
<li>In function!</li>
<li>After function!</li>
<li>One!</li>
</ul>
<p>Before function!이 실행되었고, myFunc 함수 내부의 In function!이 먼저 찍혔다.</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/640e2e86-4575-4206-95c0-33201cce0442/image.gif" alt=""></p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/4f516e4b-28df-4e13-b24b-5e240ec78972/image.gif" alt=""></p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/8ef11ee5-f6c7-4643-83b3-a47486ac1c50/image.gif" alt=""></p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/175e57dc-4abb-4ada-bee0-40c1d61f90ba/image.gif" alt=""></p>
<p>이 과정으로 Promise.then 과 async 함수의 차이점을 알 수 있다.</p>
<p>async 함수에서는 await 를 만나면 함수가 중단되고 MicroTask Queue로 들어간다.
Promise는 곧바로 MicroTask Queue에 들어간다.</p>
<h2 id="마지막-문제">마지막 문제</h2>
<pre><code>function a() {
  console.log(&#39;a1&#39;);
  b();
  console.log(&#39;a2&#39;);
}

function b() {
  console.log(&#39;b1&#39;);
  c();
  console.log(&#39;b2&#39;);
}

async function c() {
  console.log(&#39;c1&#39;);
  setTimeout(() =&gt; console.log(&#39;setTimeout&#39;), 0);
  await d();
  console.log(&#39;c2&#39;);
}

function d() {
  return new Promise(resolve =&gt; {
    console.log(&#39;d1&#39;);
    resolve();
    console.log(&#39;d2&#39;);
  }).then(() =&gt; console.log(&#39;then!&#39;));
}

a();</code></pre><ol>
<li>a 함수 호출, console.log 실행, 출력 → a1</li>
<li>b 함수 호출, console.log 실행, 출력 → b1</li>
<li>c 함수 호출, console.log 실행, 출력 → c1</li>
<li>setTimeout이 Task Queue에 쌓임.</li>
<li>d 함수 호출, 첫 번째 console.log 실행, 출력 → d1 (비동기X)</li>
<li>두 번째 console.log 실행, 출력 → d2 (비동기X)</li>
<li>.then 콜백은 백그라운드를 거쳐 마이크로 태스크 큐에 쌓임</li>
<li>d 함수 호출 완료 후 await를 만나고 async 함수 c는 중단
 async 함수의 나머지는 마이크로 태스크 큐에 쌓임</li>
<li>c 함수를 호출한 실행 컨텍스트(b함수)로 돌아가서 console.log 실행, 출력 → b2</li>
<li>b 함수를 호출한 실행 컨텍스트(a함수)로 돌아가서 console.log 실행, 출력 → a2</li>
<li>Call Stack이 모두 비워지고, Event Loop가 MicroTask Queue를 확인. then 콜백, async 함수가 쌓여있음.</li>
<li>.then 콜백 실행, console.log 출력 → then!</li>
<li>async 함수 중단된 곳부터 이후로 실행, console.log 출력 → c2</li>
<li>또다시 Event Loop가 Task Queue를 확인, setTimeout의 콜백이 쌓여있음.         setTimeout의 콜백을 Call Stack으로 옮겨 실행 및 출력 → setTimeout</li>
</ol>
<p>정답 </p>
<pre><code>a1
b1
c1
d1
d2
b2
a2
then!
c2
setTimeout</code></pre><p>참고
<a href="https://joshua1988.github.io/web-development/translation/javascript/how-js-works-inside-engine/">https://joshua1988.github.io/web-development/translation/javascript/how-js-works-inside-engine/</a>
<a href="https://hmk1022.tistory.com/entry/task-queue-micro-task-queue">https://hmk1022.tistory.com/entry/task-queue-micro-task-queue</a>
<a href="https://pozafly.github.io/javascript/event-loop-and-async/">https://pozafly.github.io/javascript/event-loop-and-async/</a>
<a href="https://velog.io/@devstone/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EA%B8%B0%EB%B0%98%EC%9C%BC%EB%A1%9C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0">https://velog.io/@devstone/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EA%B8%B0%EB%B0%98%EC%9C%BC%EB%A1%9C-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Javascript 동작 원리 (1)]]></title>
            <link>https://velog.io/@nice-one-roy/Javascript-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@nice-one-roy/Javascript-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Tue, 18 Jul 2023 15:13:04 GMT</pubDate>
            <description><![CDATA[<h2 id="javascript-engine">Javascript Engine</h2>
<p>자바스크립트 엔진의 대표적인 예는 Google V8 엔진이다. Chrome 브라우저, NodeJS 런타임 등에서 사용되고 있다. V8 은 Chrome과 Node.js에서 사용합니다. 아래는 엔진의 구조도를 간단히 나타낸 그림입니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/nice-one-roy/post/ead193c9-08fb-4d0e-8ec8-d074ba3e76e4/image.png" width="90%" height="100%"></p>

<p>1) Memory Heap : 메모리 할당이 일어나는 곳(객체, 배열, 함수 등)
2) Call Stack : 코드 실행에 따라 호출 스택이 쌓이는 곳</p>
<h2 id="호출-스택call-stack">호출 스택(Call Stack)</h2>
<p>자바스크립트는 기본적으로 싱글 쓰레드 기반 언어입니다. 호출 스택이 하나라는 소리죠. 따라서 한 번에 한 작업만 처리할 수 있습니다.</p>
<p>호출 스택은 기본적으로 우리가 프로그램 상에서 어디에 있는지를 기록하는 자료구조입니다. 만약 함수를 실행하면(실행 커서가 함수 안에 있으면), 해당 함수는 호출 스택의 가장 상단에 위치하는 거죠. 함수의 실행이 끝날 때(리턴 값을 돌려줄 때), 해당 함수를 호출 스택에서 제거합니다. 그게 스택의 역할입니다.</p>
<p>아래 예제 코드를 살펴볼까요.</p>
<pre><code>function multiply(x, y) {
    return x * y;
}
function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}
printSquare(5);</code></pre><p>처음 엔진이 이 코드를 실행하는 시점에는 호출 스택이 비어있습니다. 하지만 코드가 실행되면서 호출 스택은 아래와 같이 변합니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/nice-one-roy/post/a3f7193c-095a-48ba-bd83-1dac140a3763/image.png" width="90%" height="100%"></p>


<h2 id="javascript-runtime-environment">Javascript Runtime Environment</h2>
<p>JavaScript는 싱글 스레드 언어다. 한 번에 하나의 작업만 실행할 수 있다. JavaScript로 30초가 걸리는 작업을 해야한다고 가정해보자. 이 작업을 시작하면 유저는 30초 동안 ui에서 할 수 있는 일이 없다. 단지 기다려야 할 뿐이다. 이를 블로킹이라고 한다.</p>
<p>하지만, 웹 어플리케이션에서는 동시에 다른 무언가를 할 수있다. 예를 들면, 브라우저에서는 파일을 다운로드 받고 있으면서 다른 링크로 이동한다던지, Node.js에서는 HTTP 요청을 처리하기도 한다. V8과 같은 JavaScript 엔진은 단일 호출 스택(Call Stack)을 사용하며, 요청이 들어올 때마다 해당 요청을 순차적으로 Call Stack에 담아 처리할 뿐이다.</p>
<p>JavaScript 언어 자체에는 Multi Threading을 할 수 없지만, JavaScript가 동작하고 있는 ’브라우저‘(브라우저라는 프로그램)에서는 여러 스레드를 활용할 수 있다. 브라우저에서 지원하는 Web API를 사용하는 것이다. Web API는 JavaScript엔진 자체가 제공하지 않는, 브라우저에서 제공하는 API다. DOM API, setTimeout, HTTP 요청 등이 여기에 포함된다. JavaScript Runtime Environment(브라우저 or Node.js)에는 Event Loop가 있다. 이 Event Loop를 통해 여러 처리를 동시에 할 수 있는 것이다. 브라우저 환경을 그림으로 표현하면 다음과 같다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/nice-one-roy/post/bb25b028-b71e-41f1-8376-301ceb30999a/image.png" width="90%" height="100%"></p>


<p>비동기 호출을 위해 사용하는 setTimeout, fetch 같은 함수는 자바스크립트 엔진이 아니라 Web APIs 영역에 따로 정의 되어 있다. 또한 Event Loop나 Task Queue 같은 장치도 자바스크립트 엔진 외부에 구현되어 있다. 아래 사진은 Node.js 환경이다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/nice-one-roy/post/03561b60-ae51-4db6-826a-1a9d9f9be4a9/image.png" width="90%" height="100%"></p>

<p>이 그림에서도 브라우저 환경과 비슷하다. Node.js는 비동기 IO를 지원하기 위해 libuv 라이브러리를 사용하고, libuv에서 Event Loop를 제공한다. 자바스크립트 엔진은 비동기 작업을 위해 Node.js API를 호출하고, 이때 넘겨진 콜백은 libuv의 Event Loop를 통해 스케줄되고 실행된다.</p>
<p>확실한 것은, 자바스크립트가 ‘단일 스레드’ 기반의 언어라는 것은, ‘자바스크립트 엔진이 단일 Call Stack을 사용한다’는 관점에서만 사실이다. 실제 자바스크립트가 실행되는 환경(브라우저, Node.js)에서는 주로 여러 개의 스레드가 사용되고, 이런 구동 환경이 Call Stack을 사용하는 자바스크립트 엔진과 상호 연동하기 위해 사용하는 장치가 ‘이벤트 루프’인 것이다. 즉, 브라우저, Node.js환경은 자바스크립트 엔진의 Wrapper 역할을 하고 있다.</p>
<blockquote>
<p>비동기 코드(non blocking)는 코드의 실행이 완료되지 않아도 다음 코드를 실행하는 것을 말한다. Event loop이 있어 javascript 자체는 싱글스레드임에도 불구하고 비동기 코드를 다룰 수 있고 multi threads처럼 보일 수 있다.</p>
</blockquote>
<p>다음 글에서 Event loop과 관련해서 코드 실행 순서에 대해 자세히 알아보자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UUID vs Auto increment Integer]]></title>
            <link>https://velog.io/@nice-one-roy/UUID-vs-Auto-increment-Integer</link>
            <guid>https://velog.io/@nice-one-roy/UUID-vs-Auto-increment-Integer</guid>
            <pubDate>Mon, 10 Jul 2023 01:38:28 GMT</pubDate>
            <description><![CDATA[<p>Database에서 자주 primary key로 uuid와 auto increment integer를 사용하는데 이 둘의 장단점에 대해 알아보자.</p>
<h2 id="uuid">UUID</h2>
<p>UUID는 128 bit, 32자리의 16진수로 이루어진 아래 그림과 같은 모습의 숫자이다. (종류가 몇 가지 있긴 하다) 랜덤한 숫자로 볼 수 있고 실질적으로 유일하기 때문에 primary key로 사용한다. 실질적으로 유일하다는 말은 매우 큰 수를 랜덤하게 발생시키기 때문에 중복할 확률이 극히 낮다는 뜻이다. (2^128)</p>
<p align="center"><img src="https://velog.velcdn.com/images/nice-one-roy/post/bebe77d4-b2ff-4b3f-acf9-5a7197c75234/image.png" width="40%" height="60%">
</p>


<h3 id="pros">Pros</h3>
<blockquote>
</blockquote>
<ul>
<li>Globally unique.</li>
<li>Stateless, it can be generated on the fly.</li>
<li>Secure since malicious user can&#39;t guess the ID.</li>
<li>Version 1 UUID stores timestamp info, could be useful sometimes.</li>
</ul>
<h3 id="cons">Cons</h3>
<p>Not readable.
For database like MySQL, Oracle, which uses clustered primary key, version 4 randomly generated UUID will hurt insertion performance if used as the primary key. This is because it requires reordering the rows in order to place the newly inserted row at the right position inside the clustered index. On the other hand, PostgreSQL uses heap instead of clustered primary key, thus using UUID as the PK won&#39;t impact PostgreSQL&#39;s insertion performance.</p>
<h2 id="auto-increment-integersequence">Auto Increment Integer/Sequence</h2>
<h3 id="pros-1">Pros</h3>
<blockquote>
</blockquote>
<ul>
<li>Readable. This is especially valuable if we would expose it externally. Thinking of issue id, obviously, issue-123 is much more readable than issue-b1e92c3b-a44a-4856-9fe3-925444ac4c23.</li>
</ul>
<h3 id="cons-1">Cons</h3>
<blockquote>
</blockquote>
<ul>
<li>It can&#39;t be used in the distirbuted system since it&#39;s quite likely that different hosts could produce exactly the same number.</li>
<li>It can&#39;t be generated on the fly. Instead, we must consult the database to figure out the next available PK.</li>
<li>Some business data can be exposed, since the latest ID could represent the total number of inventory. Attackers can also scan the integer range to -- explore leakage (though it shouldn&#39;t happen if ACL is implemented correctly).</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[정적 타입 vs 동적 타입]]></title>
            <link>https://velog.io/@nice-one-roy/%EC%A0%95%EC%A0%81-%ED%83%80%EC%9E%85-vs-%EB%8F%99%EC%A0%81-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@nice-one-roy/%EC%A0%95%EC%A0%81-%ED%83%80%EC%9E%85-vs-%EB%8F%99%EC%A0%81-%ED%83%80%EC%9E%85</guid>
            <pubDate>Wed, 05 Jul 2023 01:43:48 GMT</pubDate>
            <description><![CDATA[<p>변수의 타입이 결정되는 시점이 기준이다.</p>
<blockquote>
<p>동적 타입
변수의 타입이 런타임 시 정해진다. 대표적으로 Python이다. 코딩하는 입장에서 type에 크게 신경쓰지 않아도 돌아가는 코드를 만들 수 있다. python이 간단한 script를 짜는 데에 생산성이 좋은 이유 중 하나다. 하지만 복잡성이 증가하면 type 관련 에러가 발생할 수 밖에 없다. 이를 막기 위해서는 validation code들이 늘어나는 등의 어려움이 생겨 오히려 생산성이 떨어질 수 있다.</p>
</blockquote>
<blockquote>
<p>정적 타입
변수의 타입이 컴파일 시점에 정해진다. type 관련 오류가 있을 때 compile이 되지 않는다. 덕분에 잠재적인 에러를 미리 잡아낼 수 있기에 안정성이 뛰어나다. Type을 엄격하게 code level에서 다루기 때문에 간단한 script에는 귀찮게 느껴질 수 있다. 하지만 복잡한 프로그램을 만들 때는 안정성과 생산성이 향상된다.</p>
</blockquote>
<p>정적타입 언어 리스트: Ada, C, C++, C#, JADE, Java, Fortran, Haskell, ML, Pascal, Scala
동적타입 언어 리스트: Groovy, JavaScript, Lisp, Lua, Objective-C, PHP, Prolog, Python, Ruby, Smalltalk, Tcl</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Database index(1)]]></title>
            <link>https://velog.io/@nice-one-roy/Database-Index</link>
            <guid>https://velog.io/@nice-one-roy/Database-Index</guid>
            <pubDate>Fri, 30 Jun 2023 02:07:58 GMT</pubDate>
            <description><![CDATA[<p>데이터베이스 인덱스(Database Index)는 데이터베이스 내의 테이블에 대한 성능 향상을 위해 사용되는 데이터 구조이다. 인덱스는 테이블의 특정 열(또는 열의 조합)에 대한 검색 속도를 향상시키기 위해 사용된다.</p>
<p>정도만 알고는 application level에서 index를 제대로 활용하기 힘들다. 무엇이든 그렇지만 index를 잘 활용하기 위해서는 index의 원리를 이해해야 한다. 기본적인 원리를 정리해보자.</p>
<h2 id="index-scan-vs-full-scan">index scan vs full scan</h2>
<p>index가 아닌 column으로 select 쿼리를 날리면 무지성으로 Disk에 있는 해당 테이블을 뒤진다. 이것을 full scan이라고 한다. 반면 index가 설정된 column이라면 Tree로 된 index table을 통해 데이터가 있는 위치를 알아내서 찾아가게 된다. Tree로 B tree 계열의 자료구조가 많이 사용되지만 DB마다 혹은 데이터의 특성마다 다른 자료구조를 사용한다. 이에 대한 자세한 것은 다음에 다뤄보도록 하자. 아무튼 Tree 구조를 통해 위치를 알아내기 때문에 일반적으로 더 빠르다.</p>
<p>(참고) 아래 그림처럼 항상 더 빠른 것은 아니다. 데이터가 많지 않다면 Tree 자료구조에서 얻는 시간적인 효율보다 (tree를 가져오고 =&gt; tree를 뒤지고) 이 overhead가 커질 수도 있다.
<img src="https://velog.velcdn.com/images/nice-one-roy/post/3185c742-6105-4f6f-b82c-8e367b086a4c/image.png" alt=""></p>
<h2 id="clustered-index-vs-non-clustered-index">Clustered index vs Non clustered index</h2>
<p>index는 clustered index와 non clustered index로 나누어진다. </p>
<p>clustered index는 한 테이블 당 하나만 존재할 수 있다. 대부분의 db에서 default로 primary key로 설정되어 있다. 물론 use case에 따라 변경할 수 있다. table마다 하나밖에 존재할 수 없는 이유는 cluster key 기준으로 실제로(물리적으로) 데이터가 정렬되어 있기 때문이다.</p>
<p>반면 non clustered index는 한 테이블에도 여러 개를 지정할 수 있다. 하지만 clustered index보다는 성능 향상을 더 끌어내지 못한다.</p>
<p>왜 그런지 이해해보자</p>
<h2 id="clustered-index">Clustered index</h2>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/0b9cb154-d37e-45b6-9531-8d7e0e306143/image.png" alt=""></p>
<ul>
<li>Clustered index column을 key로 하는 table(tree)이 있다. 가장 끝에 있는 Leaf node는 실제 데이터가 있는 page이다. 이 데이터 page에는 data가 index column 기준으로 정렬되어 있다. 이것이 non clustered index와의 가장 큰 차이점이다.</li>
</ul>
<h2 id="non-clustered-index">Non clustered index</h2>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/0404d80f-f207-48d9-83ff-8f228861a7ce/image.png" alt=""></p>
<ul>
<li>Clustered index와는 다르게 각 데이터는 clustered index key를 갖고 있다. non clustered index table을 통해 clustered index를 찾는 것이다. clustered index를 통해 cluster index를 찾고 clustered index table을 뒤져 데이터를 찾는 것이다. 어쨌든 clustered index를 통해 찾기 떄문에 당연히 clustered index보다 느리다.</li>
<li>그림을 보면 included columns라는 것도 있는데 이는 설정하기에 따라 clustered index table을 뒤져 찾는 과정을 생략하기 위해 특정 컬럼들을 non clustered table에 함께 저장하는 것이다.</li>
</ul>
<h3 id="참고">참고</h3>
<p><a href="https://matthewmcgiffen.com/2017/06/12/what-is-an-index/">https://matthewmcgiffen.com/2017/06/12/what-is-an-index/</a>
<a href="https://logicalread.com/dont-drink-kool-aid/">https://logicalread.com/dont-drink-kool-aid/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT(Json web token)]]></title>
            <link>https://velog.io/@nice-one-roy/JWTJson-web-token</link>
            <guid>https://velog.io/@nice-one-roy/JWTJson-web-token</guid>
            <pubDate>Wed, 21 Jun 2023 02:34:53 GMT</pubDate>
            <description><![CDATA[<h2 id="jwtjson-web-token">JWT(Json web token)</h2>
<p>JWT(Json Web Token)란 Json 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token이다. JWT는 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 안전하게 전달한다.</p>
<h2 id="flow">flow</h2>
<h2 id="구성">구성</h2>
<p>JWT는 세 개의 점으로 구분된 문자열로 표현되며, 세 가지 부분으로 구성됩니다: Header (헤더), Payload (페이로드), Signature (서명). 각 부분은 Base64 인코딩되어 표현되며, 각 부분은 점(.)으로 구분됩니다.</p>
<p><img src="https://velog.velcdn.com/images/nice-one-roy/post/308b173f-3e23-4eab-80ea-2a814c88add5/image.png" alt=""></p>
<p>Header 에는 JWT 에서 사용할 타입과 해시 알고리즘의 종류가 담겨있으며, Payload 는 서버에서 첨부한 사용자 권한 정보와 데이터가 담겨있다. 마지막으로 Signature는 Header, Payload에 서버의 secret key와 Header에 명시된 해시함수를 서명해서 만든다.</p>
<h3 id="주의할-점">주의할 점</h3>
<p>payload는 인코딩 되어 있을 뿐 암호화가 되어있지 않다. 그러므로 민감한 정보는 넣으면 안된다.</p>
<h2 id="flow-1">Flow</h2>
<img src="https://velog.velcdn.com/images/nice-one-roy/post/5745661a-48b8-4bc1-9801-de414fac6933/image.png" width="100%" height="100%">

<ul>
<li>사용자가 ID, PW를 입력하여 서버에 로그인 인증을 요청한다.</li>
<li>서버에서 클라이언트로부터 인증 요청을 받으면, Header, PayLoad를 만들고 위에서 설명한 방식으로Signature를 생성한다.</li>
<li>Hedaer, PayLoad, Signature를 각각 Base64로 인코딩하여 JWT를 생성하고 이를 쿠키에 담아 클라이언트에게 발급한다.</li>
<li>클라이언트는 서버로부터 받은 JWT를 로컬 스토리지에 저장한다. (쿠키나 다른 곳에 저장할 수도 있음) API를 서버에 요청할때 Authorization header에 Access Token을 담아서 보낸다.</li>
<li>서버가 할 일은 클라이언트가 Header에 담아서 보낸 JWT의 Header와 Payload를 만들었던 방식 그대로 사용해서 Signature를 만들어본다. Header에 담겨온 Signature와 비교해서 일치 여부를 본다.</li>
<li>클라이언트가 서버에 요청을 했는데, 만일 액세스 토큰의 시간이 만료되면 클라이언트는 리프래시 토큰을 이용해서 서버로부터 새로운 엑세스 토큰을 발급 받는다.</li>
</ul>
<h2 id="토큰이-인증-신뢰성을-갖는-이유">토큰이 인증 신뢰성을 갖는 이유</h2>
<p>유저 JWT: A(Header) + B(Payload) + C(Signature) 일 때 (만일 임의의 유저가 B를 수정했다고 하면 B&#39;로 표시한다.)</p>
<ol>
<li><p>다른 유저가 B를 임의로 수정 -&gt; 유저 JWT: A + B&#39; + C
수정한 토큰을 서버에 요청을 보내면 서버는 유효성 검사 시행</p>
</li>
<li><p>유저 JWT: A + B&#39; + C
서버에서 검증 후 생성한 JWT: A + B&#39; + C&#39; =&gt; (signature) 불일치</p>
</li>
<li><p>대조 결과가 일치하지 않아 유저의 정보가 임의로 조작되었음을 알 수 있다.</p>
</li>
</ol>
<p>정리하자면, 서버는 토큰 안에 들어있는 정보가 무엇인지 아는게 중요한 것이 아니라 해당 토큰이 유효한 토큰인지 확인하는 것이 중요하기 때문에, 클라이언트로부터 받은 JWT의 헤더, 페이로드를 서버의 key값을 이용해 시그니처를 다시 만들고 이를 비교하며 일치했을 경우 인증을 통과시킨다.</p>
<h2 id="expire-time">Expire time</h2>
<p>expire time이 커지면 login timeout이 발생할 가능성이 낮아진다. 하지만 공격자가 탈취했을 때 무언가(?)를 할 시간이 많아진다.
expire time이 작아지면 보안에는 좋지만 login timeout이 빈번하게 일어날 것이다.</p>
<h2 id="refresh-token">Refresh Token</h2>
<p>처음에 JWT를 접했을 때 Refresh Token이 왜 있는건지 이해가 가지 않았다. Access token의 expire time을 짧게 가져가기 위해서라고 했다. 하지만 로그인했을 때 어차피 Access token과 Refresh token을 동시에 발급해주는거라면 access token과 마찬가지로 refresh token도 탈취당하는 것 아닌가?라는 생각이 있었다.</p>
<p>token이 위험한 경우는 위에서 말한대로 token이 공격자에게 탈취당했을 때이다. 그 탈취는 주로 web상에서 공격자가 intercept해서 발생하다. 그리고 expire time이 짧은 access token이 주로 request에 쓰이며 돌아다니게 하고 expire time이 긴 refresh token은 client의 local storage에 꽁꽁 숨겨두려고 하는 의도이다. expire time 자체를 짧게 가져가는 것도 하나의 방법이 되겠지만 위에서 말한대로 login timeout과 trade off가 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인증]]></title>
            <link>https://velog.io/@nice-one-roy/%EC%9D%B8%EC%A6%9D</link>
            <guid>https://velog.io/@nice-one-roy/%EC%9D%B8%EC%A6%9D</guid>
            <pubDate>Mon, 19 Jun 2023 08:13:46 GMT</pubDate>
            <description><![CDATA[<p>인증에 대해 공부하고 까먹고 공부하고 까먹고를 반복했다. 글이라도 쓰면 안까먹을 것 같아 작성한다.</p>
<h1 id="인증">인증</h1>
<p>인증이란 클라이언트가 서비스에 접근할 수 있는 권한이 있는 사용자인지 인증하는 과정이다.
보통 서버가 클라이언트를 인증하는 방식은 대표적으로 쿠키, 세션, 토큰 3가지 방식이 있다.
세 가지에 대해 간단하게 살펴보고 다음 글에서 가장 많이 사용되는 JWT에 대해 살펴보자. (난 이걸 졸맛탱이라고 부른다)</p>
<h2 id="cookie">Cookie</h2>
<p>쿠키는 Key-Value 형식의 문자열 덩어리이다.</p>
<p>클라이언트가 어떠한 웹사이트를 방문할 경우, 그 사이트가 사용하고 있는 서버를 통해 클라이언트의 브라우저에 설치되는 작은 기록 정보 파일이다. 각 사용자마다의 브라우저에 정보를 저장하니 고유 정보 식별이 가능한 것이다.</p>
<h3 id="과정">과정</h3>
<ul>
<li>브라우저(클라이언트)가 서버에 요청(접속)을 보낸다.</li>
<li>서버는 클라이언트의 요청에 대한 응답을 작성할 때, 클라이언트 측에 저장하고 싶은 정보를 응답 헤더의 Set-Cookie에 담는다.</li>
<li>이후 해당 클라이언트는 요청을 보낼 때마다, 매번 저장된 쿠키를 요청 헤더의 Cookie에 담아 보낸다.</li>
<li>서버는 쿠키에 담긴 정보를 바탕으로 해당 요청의 클라이언트가 누군지 식별하거나 정보를 바탕으로 추천 광고를 띄우거나 한다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>가장 큰 단점은 보안에 취약하다는 점이다. 요청 시 쿠키의 값을 그대로 보내기 때문에 유출 및 조작 당할 위험이 존재한다.</li>
<li>쿠키에는 용량 제한이 있어 많은 정보를 담을 수 없다.</li>
<li>웹 브라우저마다 쿠키에 대한 지원 형태가 다르기 때문에 브라우저간 공유가 불가능하다.</li>
<li>쿠키의 사이즈가 커질수록 네트워크에 부하가 심해진다.</li>
</ul>
<h2 id="session">Session</h2>
<p>이러한 쿠키의 보안적인 이슈 때문에, 세션은 비밀번호 등 클라이언트의 민감한 인증 정보를 브라우저가 아닌 서버 측에 저장하고 관리한다. 서버의 메모리에 저장하기도 하고, 서버의 로컬 파일이나 데이터베이스에 저장하기도 한다. (Stateful)</p>
<p>핵심 골자는 민감한 정보는 클라이언트에 보내지말고 서버에서 모두 관리한다는 점이다.</p>
<h3 id="과정-1">과정</h3>
<ul>
<li>유저가 웹사이트에서 로그인하면 세션이 서버 메모리(혹은 데이터베이스) 상에 저장된다. 이때, 세션을 식별하기 위한 Session Id를 기준으로 정보를 저장한다.</li>
<li>서버에서 브라우저에 쿠키에다가 Session Id를 저장한다.</li>
<li>쿠키에 정보가 담겨있기 때문에 브라우저는 해당 사이트에 대한 모든 Request에 Session Id를 쿠키에 담아 전송한다.</li>
<li>서버는 클라이언트가 보낸 Session Id 와 서버 메모리로 관리하고 있는 Session Id를 비교하여 인증을 수행한다.</li>
</ul>
<h3 id="단점-1">단점</h3>
<ul>
<li>쿠키를 포함한 요청이 외부에 노출되더라도 세션 ID 자체는 유의미한 개인정보를 담고 있지 않는다. 그러나 해커가 세션 ID 자체를 탈취하여 클라이언트인척 위장할 수 있다는 한계가 존재한다. (이는 서버에서 IP특정을 통해 해결 할 수 있긴 하다)</li>
<li>서버에서 세션 저장소를 사용하므로 요청이 많아지면 서버에 부하가 심해진다.</li>
</ul>
<h2 id="token">Token</h2>
<p>Stateless한 방식이다. 토큰 기반 인증 시스템은 클라이언트가 서버에 접속을 하면 서버에서 해당 클라이언트에게 인증되었다는 의미로 &#39;토큰&#39;을 부여한다. 이 토큰은 유일하며 토큰을 발급받은 클라이언트는 또 다시 서버에 요청을 보낼 때 요청 헤더에 토큰을 심어서 보낸다. 그러면 서버에서는 클라이언트로부터 받은 토큰을 서버에서 제공한 토큰과의 일치 여부를 체크하여 인증 과정을 처리하게 된다.</p>
<p>기존의 세션기반 인증은 서버가 파일이나 데이터베이스에 세션정보를 가지고 있어야 하고 이를 조회하는 과정이 필요하기 때문에 많은 오버헤드가 발생한다. 하지만 토큰은 세션과는 달리 서버가 아닌 클라이언트에 저장되기 때문에 메모리나 스토리지 등을 통해 세션을 관리했던 서버의 부담을 덜 수 있다. 토큰 자체에 데이터가 들어있기 때문에 클라이언트에서 받아 위조되었는지 판별만 하면 되기 떄문이다.</p>
<p>토큰은 앱과 서버가 통신 및 인증할때 가장 많이 사용된다. 왜냐하면 웹에는 쿠키와 세션이 있지만 앱에서는 없기 때문이다.</p>
<h3 id="과정-2">과정</h3>
<ul>
<li>사용자가 아이디와 비밀번호로 로그인을 한다.</li>
<li>서버 측에서 사용자(클라이언트)에게 유일한 토큰을 발급한다.</li>
<li>클라이언트는 서버 측에서 전달받은 토큰을 쿠키나 스토리지에 저장해 두고, 서버에 요청을 할 때마다 해당 토큰을 서HTTP 요청 헤더에 포함시켜 전달한다. </li>
<li>서버는 전달받은 토큰을 검증하고 요청에 응답한다. 토큰에는 요청한 사람의 정보가 담겨있기에 서버는 DB를 조회하지 않고 누가 요청하는지 알 수 있다.</li>
</ul>
<h3 id="단점-2">단점</h3>
<ul>
<li>쿠키/세션과 다르게 토큰 자체의 데이터 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해질수 있다.</li>
<li>Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없다.</li>
<li>토큰을 탈취당하면 대처하기 어렵다. (따라서 사용 기간 제한을 설정하는 식으로 극복한다)</li>
</ul>
<p>참고 : <a href="https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-JWTjson-web-token-%EB%9E%80-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC">https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-JWTjson-web-token-%EB%9E%80-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내가 보려고 만드는 Jetbrain 단축키 모음(Mac)]]></title>
            <link>https://velog.io/@nice-one-roy/%EB%82%B4%EA%B0%80-%EB%B3%B4%EB%A0%A4%EA%B3%A0-%EB%A7%8C%EB%93%9C%EB%8A%94-Jetbrain-%EB%8B%A8%EC%B6%95%ED%82%A4-%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@nice-one-roy/%EB%82%B4%EA%B0%80-%EB%B3%B4%EB%A0%A4%EA%B3%A0-%EB%A7%8C%EB%93%9C%EB%8A%94-Jetbrain-%EB%8B%A8%EC%B6%95%ED%82%A4-%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Tue, 13 Jun 2023 01:04:41 GMT</pubDate>
            <description><![CDATA[<p>내가 보려고 만드는 Jetbrain 단축키 모음. 단축키는 내 productivity를 향상시켜주기 때문에 항상 목말라 있다.</p>
<p>Alt + Enter : Jetbrain의 제안보기</p>
<p>⌘ + B : 심볼의 선언으로 이동. 혹은 이 심볼이 사용된 곳들을 보여준다.</p>
<p>shift 연속 두번 : 파일 찾기, 클레스 찾기, symbol 찾기 등
⌘ + Shift + F : 코드 찾기</p>
<p>F6 : 코드, class 등 다른 곳으로 옮기기</p>
<p>Shift + F6 : Rename 한번에 하기</p>
<p>⌘ + Option + M : method로 추출하기
⌘ + Option + N : 인라인 하기</p>
<p>⌘ + Delete : 한줄 지우기
⌘ + d : 문장 복사해서 바로 아래에 duplicate</p>
<p>⌘ + &lt;- Or -&gt; : 커서 라인 젤 끝으로 이동 </p>
<p>⌘ + E : 최근 연 파일 목록</p>
<p>⌘ + shift [ or ] : 탭 이동
⌘ + E : 탭 닫기</p>
<p>다중커서 Option+Option 누른채로 위아래</p>
]]></description>
        </item>
    </channel>
</rss>