<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>liini_coder.log</title>
        <link>https://velog.io/</link>
        <description>보안을 겸비하고픈 풀스택개발자</description>
        <lastBuildDate>Mon, 20 Oct 2025 12:19:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>liini_coder.log</title>
            <url>https://velog.velcdn.com/images/liini_coder/profile/4f760611-6287-4199-9ef6-352d02296f24/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. liini_coder.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/liini_coder" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[서킷 브레이커 개념 및 실습]]></title>
            <link>https://velog.io/@liini_coder/%EC%84%9C%ED%82%B7-%EB%B8%8C%EB%A0%88%EC%9D%B4%EC%BB%A4-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@liini_coder/%EC%84%9C%ED%82%B7-%EB%B8%8C%EB%A0%88%EC%9D%B4%EC%BB%A4-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Mon, 20 Oct 2025 12:19:11 GMT</pubDate>
            <description><![CDATA[<h1 id="서킷-브레이커">서킷 브레이커</h1>
<p>MSA환경에서 장애 전파를 방지하고 부분장애를 격리 하기위한 안정성 패턴</p>
<ul>
<li>전기 회로의 차단기와 같은 원리(닫혀있다면 원래 회로가 잘 작동중(정상 운영중))
open상태에선 아무리 메소드를 호출해도 무조건 실패하지만
half-open에선 단 한번의 성공이 일어나면 closed로 바뀜</li>
</ul>
<p>평소엔 closed상태였다가 임계값을 넘을 만큼의 실패가 일어나게 된다면 open상 태가 되었다가, 타임아웃이 지나면 half open으로 된다.</p>
<h2 id="왜하는걸까">왜하는걸까?</h2>
<p>커스텀에러를 던지도록 그냥 우리가 구현하면, 어쨌든 그 실패할 요청에 대해서도 매번 db 커넥션 풀같은 자원을 소비하게 된다. 이를 막기 위함</p>
<h2 id="사용자-a가-open상태로-만들어버리면-아에-서비스가-open이-되는것이라서-다른-사용자-b도-해당-서비스를-이용못하게됨">사용자 A가 open상태로 만들어버리면 아에 서비스가 open이 되는것이라서 다른 사용자 B도 해당 서비스를 이용못하게됨</h2>
<ul>
<li>따라서 DDos방어를 해줘야한다.<ul>
<li>nginx.conf</li>
<li>window size, 실패 확률 조정</li>
</ul>
</li>
</ul>
<h2 id="resilience4j">Resilience4j</h2>
<ul>
<li>서킷브레이커를 라이브러리로 쉽게 적용할수있는것</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[부하테스트 개념 및 실습]]></title>
            <link>https://velog.io/@liini_coder/%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@liini_coder/%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Mon, 01 Sep 2025 12:52:53 GMT</pubDate>
            <description><![CDATA[<h1 id="부하테스트">부하테스트</h1>
<p>서비스의 한계를 실험해봐야 추후 프리즈 되는 상황을 대처한 개발을 가능</p>
<h2 id="부하테스트-1">부하테스트</h2>
<ul>
<li>Load &amp; Stress 테스트<ul>
<li>load: 시스템이 예상하는 일반 트래픽을 처리하는지</li>
<li>stress: 시스템에 고의로 점진적으로 과부하를 가하여 한계점을 찾고 장애상황에서 안정적 복구(회복)되는지 테스트</li>
</ul>
</li>
<li>spike 테스트<ul>
<li>spike : 순간적인 과부하를 가하여 그 회복력 테스트</li>
</ul>
</li>
<li>Soak 테스트<ul>
<li>메모리 누수나 리소스 고갈같은 장기적 성능문제를 발견<h2 id="지표">지표</h2>
</li>
</ul>
</li>
<li>응답시간: 사용자 요청 보내고 받는데 까지의 전체 시간<ul>
<li>응답시간 = 지연시간 + 연산시간(처리)</li>
</ul>
</li>
<li>지연시간: 데이터가 전송되는 데 걸리는 시간 또는 요청이 처리되길 기다리는 시간을 의미</li>
<li>처리량(Throughput): 초당 최대요청 처리량<ul>
<li>TPS: 총 트랜젝션수 / 총 소요시간</li>
<li>RPS: 총 요청수 / 총 소요시간</li>
</ul>
</li>
<li>MTBF(Mean time betrewwn 실패): 얼마나 오랫동안 문제없이 동작하였는가</li>
<li>MTTR(Mean time to recovery) : 문제가 생겼을때 얼마나 빨리 작동하는가제가 생겼을때 얼마나 빨리 작동하는가<h1 id="부하테스트-모니터링">부하테스트 모니터링</h1>
<h2 id="프로메테우스">프로메테우스</h2>
</li>
<li>메트릭을 수집하는 모니터링 툴</li>
<li>서버의 상태를 계속 감시하고 CPU사용량, 메모리, 네트워크 트래픽 등과 같은 데이터를 기록하는 수집하고 저장<h2 id="그라파나">그라파나</h2>
</li>
<li>프로메테우스의 숫자 데이터(프로메테우스 외에도 엘라스틱 서치등)를 시각화 하는 툴<h2 id="k6">K6</h2>
</li>
<li>시스템에 부하를 발생시켜 성능을 테스트하는 부하 테스트 툴</li>
<li>js기반으로 테스트 스크립트를 작성하여 자동화 가능<h1 id="실습">실습</h1>
<h3 id="환경">환경</h3>
</li>
<li>두개의 ec2에 프로메테우스, 그라파나, k6 를 한데 실행.<ul>
<li>1st<ul>
<li>t2-micro</li>
<li>인터넷에서 http 허용 체크</li>
<li>원래 각각마다 ec2를 두는 게 현업에 맞지만 자원상 한계</li>
<li>키페어 없이</li>
<li>보안그룹 8080 허용</li>
<li>보안그룹 8081 허용: spring actuator를 위함</li>
</ul>
</li>
<li>나머진 그대로 하여 인스턴스 하나 생성</li>
<li>2nd 모니터링 ec2</li>
<li>t2-micro<ul>
<li>보안그룹<ul>
<li>3000 : 그라파나 위함</li>
<li>9090 : 프로메테우스가 이걸 씀</li>
<li>5665 : 그라파나에서 웹모니터링을 제공은 하나, k6 자체로도 대시보드가 있어서 그걸 확인하기 위함<h3 id="인스턴스-쉘-진입-후">인스턴스 쉘 진입 후</h3>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<ol>
<li>두 ec2모두 적용<pre><code class="language-shell">export API_SERVER_IP=&quot;[본인의_API_SERVER_퍼블릭_IP]&quot;
export MONITORING_SERVER_IP=&quot;[본인의_MONITORING_SERVER_퍼블릭_IP]&quot;</code></pre>
</li>
<li>편집기로 setting.sh로 다음내용 생성<pre><code>#!/bin/bash
</code></pre></li>
</ol>
<p>set -e</p>
<p>echo &quot;=== Spring Boot API Server Setup (병렬 처리) ===&quot;</p>
<h1 id="기본-패키지-설치">기본 패키지 설치</h1>
<p>sudo apt update -y
sudo apt install -y openjdk-17-jdk maven git curl</p>
<h1 id="프로젝트-구조-생성">프로젝트 구조 생성</h1>
<p>mkdir -p ~/loadtest-app &amp;&amp; cd ~/loadtest-app
mkdir -p loadtest-api/src/main/java/com/loadtest
mkdir -p loadtest-api/src/main/resources
cd loadtest-api</p>
<h1 id="pomxml-생성">pom.xml 생성</h1>
<p>cat &gt; pom.xml &lt;&lt; &#39;EOF&#39;
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    <groupId>com.loadtest</groupId>
    <artifactId>loadtest-api</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    <properties>
        &lt;maven.compiler.source&gt;17&lt;/maven.compiler.source&gt;
        &lt;maven.compiler.target&gt;17&lt;/maven.compiler.target&gt;
        &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
EOF</p>
<h1 id="application-클래스-생성">Application 클래스 생성</h1>
<p>cat &gt; src/main/java/com/loadtest/LoadTestApplication.java &lt;&lt; &#39;EOF&#39;
package com.loadtest;</p>
<p>import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;</p>
<p>@SpringBootApplication
public class LoadTestApplication {
    public static void main(String[] args) {
        SpringApplication.run(LoadTestApplication.class, args);
    }
}
EOF</p>
<h1 id="controller-생성-병렬-처리--병목-시나리오">Controller 생성 (병렬 처리 + 병목 시나리오)</h1>
<p>cat &gt; src/main/java/com/loadtest/LoadTestController.java &lt;&lt; &#39;EOF&#39;
package com.loadtest;</p>
<p>import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;</p>
<p>@RestController
public class LoadTestController {</p>
<pre><code>@Autowired
private RestTemplate restTemplate;

@Autowired
private Executor taskExecutor;

// API 1 - 병목 지점 (50ms)
@GetMapping(&quot;/api/1&quot;)
public Map&lt;String, Object&gt; api1() throws InterruptedException {
    Thread.sleep(50);  // 50ms - 심각한 병목
    Map&lt;String, Object&gt; response = new HashMap&lt;&gt;();
    response.put(&quot;endpoint&quot;, &quot;api1&quot;);
    response.put(&quot;processingTime&quot;, &quot;50ms&quot;);
    response.put(&quot;timestamp&quot;, System.currentTimeMillis());
    response.put(&quot;role&quot;, &quot;심각한 병목 - 50ms&quot;);
    return response;
}

// API 2 - 중간 성능 (2ms)
@GetMapping(&quot;/api/2&quot;)
public Map&lt;String, Object&gt; api2() throws InterruptedException {
    Thread.sleep(2);  // 2ms
    Map&lt;String, Object&gt; response = new HashMap&lt;&gt;();
    response.put(&quot;endpoint&quot;, &quot;api2&quot;);
    response.put(&quot;processingTime&quot;, &quot;2ms&quot;);
    response.put(&quot;timestamp&quot;, System.currentTimeMillis());
    return response;
}

// API 3 - 빠른 성능 (1ms)
@GetMapping(&quot;/api/3&quot;)
public Map&lt;String, Object&gt; api3() throws InterruptedException {
    Thread.sleep(1);  // 1ms
    Map&lt;String, Object&gt; response = new HashMap&lt;&gt;();
    response.put(&quot;endpoint&quot;, &quot;api3&quot;);
    response.put(&quot;processingTime&quot;, &quot;1ms&quot;);
    response.put(&quot;timestamp&quot;, System.currentTimeMillis());
    return response;
}

// 비즈니스 플로우 - 병렬 호출
@GetMapping(&quot;/api/business-flow&quot;)
public Map&lt;String, Object&gt; businessFlow() {
    long startTime = System.currentTimeMillis();
    String baseUrl = &quot;http://localhost:8080&quot;;

    try {
        // 3개 API를 병렬로 동시 호출
        CompletableFuture&lt;Map&gt; future1 = CompletableFuture.supplyAsync(() -&gt; 
            restTemplate.getForObject(baseUrl + &quot;/api/1&quot;, Map.class), taskExecutor);
        CompletableFuture&lt;Map&gt; future2 = CompletableFuture.supplyAsync(() -&gt; 
            restTemplate.getForObject(baseUrl + &quot;/api/2&quot;, Map.class), taskExecutor);
        CompletableFuture&lt;Map&gt; future3 = CompletableFuture.supplyAsync(() -&gt; 
            restTemplate.getForObject(baseUrl + &quot;/api/3&quot;, Map.class), taskExecutor);

        // 가장 오래 걸리는 API까지 대기 (api/1의 50ms)
        CompletableFuture.allOf(future1, future2, future3).join();

    } catch (Exception e) {
        System.err.println(&quot;Parallel API call failed: &quot; + e.getMessage());
    }

    long endTime = System.currentTimeMillis();
    Map&lt;String, Object&gt; response = new HashMap&lt;&gt;();
    response.put(&quot;endpoint&quot;, &quot;business-flow&quot;);
    response.put(&quot;executionType&quot;, &quot;병렬 처리&quot;);
    response.put(&quot;description&quot;, &quot;api/1 || api/2 || api/3 동시 실행&quot;);
    response.put(&quot;totalProcessingTime&quot;, (endTime - startTime) + &quot;ms&quot;);
    response.put(&quot;timestamp&quot;, endTime);
    response.put(&quot;bottleneck&quot;, &quot;api/1 (50ms) - 심각한 성능 저하&quot;);
    response.put(&quot;theoreticalMaxTPS&quot;, &quot;약 20 TPS (1000ms / 50ms)&quot;);
    response.put(&quot;improvementPotential&quot;, &quot;api/1을 2ms로 개선 시 500 TPS 달성 (25배 향상!)&quot;);
    return response;
}

@GetMapping(&quot;/health&quot;)
public Map&lt;String, String&gt; health() {
    Map&lt;String, String&gt; response = new HashMap&lt;&gt;();
    response.put(&quot;status&quot;, &quot;UP&quot;);
    return response;
}</code></pre><p>}</p>
<p>@Configuration
@EnableAsync
class RestConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }</p>
<pre><code>@Bean
public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(20);
    executor.setMaxPoolSize(100);
    executor.setQueueCapacity(200);
    executor.setThreadNamePrefix(&quot;parallel-api-&quot;);
    executor.initialize();
    return executor;
}</code></pre><p>}
EOF</p>
<h1 id="applicationyml-설정">application.yml 설정</h1>
<p>cat &gt; src/main/resources/application.yml &lt;&lt; &#39;EOF&#39;
server:
  port: 8080
  tomcat:
    threads:
      max: 300
      min-spare: 20</p>
<p>management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
      base-path: /actuator
  endpoint:
    health:
      show-details: always
    metrics:
      enabled: true
    prometheus:
      enabled: true
  server:
    port: 8081</p>
<p>logging:
  level:
    com.loadtest: INFO
    org.springframework: WARN
    org.springframework.web: DEBUG
EOF</p>
<h1 id="빌드-및-실행">빌드 및 실행</h1>
<p>echo &quot;=== Building Application ===&quot;
mvn clean package -DskipTests</p>
<p>if [ $? -eq 0 ]; then
    echo &quot;Build SUCCESS&quot;</p>
<pre><code>nohup java -Xmx512m -Xms256m -jar target/loadtest-api-1.0.0.jar &gt; app.log 2&gt;&amp;1 &amp;
echo $! &gt; app.pid

echo &quot;=== Waiting for Application Startup ===&quot;
sleep 15

echo &quot;=== Testing Endpoints ===&quot;
if curl -s http://localhost:8080/health | grep -q &quot;UP&quot;; then
    echo &quot;Health Check OK&quot;

    # 각 API 개별 테스트
    echo &quot;개별 API 테스트:&quot;
    echo -n &quot;api/1: &quot;; curl -s http://localhost:8080/api/1 | jq -r &#39;.processingTime // &quot;OK&quot;&#39;
    echo -n &quot;api/2: &quot;; curl -s http://localhost:8080/api/2 | jq -r &#39;.processingTime // &quot;OK&quot;&#39;
    echo -n &quot;api/3: &quot;; curl -s http://localhost:8080/api/3 | jq -r &#39;.processingTime // &quot;OK&quot;&#39;

    echo &quot;&quot;
    echo &quot;Spring Boot API Server Ready!&quot;
    echo &quot;&quot;
    echo &quot;병렬 처리 시나리오: 가장 느린 api/1이 전체 성능 결정&quot;
else
    echo &quot;Application failed to start&quot;
    cat app.log
    exit 1
fi</code></pre><p>else
    echo &quot;Build FAILED&quot;
    exit 1
fi</p>
<h1 id="성능-개선-시뮬레이션-스크립트-생성">성능 개선 시뮬레이션 스크립트 생성</h1>
<p>cat &gt; improve-api1.sh &lt;&lt; &#39;EOF&#39;
#!/bin/bash
echo &quot;=== API/1 성능 개선 시뮬레이션 ===&quot;</p>
<p>./stop.sh</p>
<h1 id="api1의-성능을-50ms에서-2ms로-개선">api/1의 성능을 50ms에서 2ms로 개선</h1>
<p>sed -i &#39;s/Thread.sleep(50);  // 50ms - 심각한 병목/Thread.sleep(2);  // 2ms - 개선완료/g&#39; src/main/java/com/loadtest/LoadTestController.java
sed -i &#39;s/&quot;processingTime&quot;, &quot;50ms&quot;/&quot;processingTime&quot;, &quot;2ms&quot;/g&#39; src/main/java/com/loadtest/LoadTestController.java
sed -i &#39;s/&quot;role&quot;, &quot;심각한 병목 - 50ms&quot;/&quot;role&quot;, &quot;개선완료 - 최적화됨&quot;/g&#39; src/main/java/com/loadtest/LoadTestController.java
sed -i &#39;s/&quot;bottleneck&quot;, &quot;api/1 (50ms) - 심각한 성능 저하&quot;/&quot;bottleneck&quot;, &quot;없음 - 모든 API 최적화됨&quot;/g&#39; src/main/java/com/loadtest/LoadTestController.java
sed -i &#39;s/&quot;theoreticalMaxTPS&quot;, &quot;약 20 TPS (1000ms / 50ms)&quot;/&quot;theoreticalMaxTPS&quot;, &quot;약 500 TPS (1000ms / 2ms)&quot;/g&#39; src/main/java/com/loadtest/LoadTestController.java
sed -i &#39;s/&quot;improvementPotential&quot;, &quot;api/1을 2ms로 개선 시 500 TPS 달성 (25배 향상!)&quot;/&quot;improvement&quot;, &quot;api/1 최적화 완료 - 25배 성능 향상 달성&quot;/g&#39; src/main/java/com/loadtest/LoadTestController.java</p>
<p>mvn clean package -DskipTests
nohup java -Xmx512m -Xms256m -jar target/loadtest-api-1.0.0.jar &gt; app.log 2&gt;&amp;1 &amp;
echo $! &gt; app.pid</p>
<p>sleep 10
EOF</p>
<h1 id="관리-스크립트들-생성">관리 스크립트들 생성</h1>
<p>cat &gt; stop.sh &lt;&lt; &#39;EOF&#39;
#!/bin/bash
if [ -f app.pid ]; then
    sudo kill $(cat app.pid) 2&gt;/dev/null
    rm app.pid
    echo &quot;Application stopped&quot;
else
    echo &quot;No application running&quot;
fi
EOF</p>
<p>cat &gt; status.sh &lt;&lt; &#39;EOF&#39;
#!/bin/bash
echo &quot;=== Application Status ===&quot;
if [ -f app.pid ]; then
    PID=$(cat app.pid)
    if ps -p $PID &gt; /dev/null; then
        echo &quot;Application running (PID: $PID)&quot;
        echo &quot;Health: $(curl -s <a href="http://localhost:8080/health)&quot;">http://localhost:8080/health)&quot;</a>
    else
        echo &quot;PID file exists but process not running&quot;
    fi
else
    echo &quot;Application not running&quot;
fi
EOF</p>
<p>chmod +x stop.sh improve-api1.sh status.sh</p>
<p>echo &quot;&quot;
echo &quot;Management Commands:&quot;
echo &quot;- Status: ./status.sh&quot;
echo &quot;- Stop: ./stop.sh&quot;
echo &quot;- Improve API1: ./improve-api1.sh (333→500 TPS)&quot;
echo &quot;- Logs: tail -f app.log&quot;</p>
<pre><code>3. chmod +x setting.sh
4. 실행하면 쭈욱 깔림
5. 컨트롤러에 api 3개가 있고, 1은 50ms, 2는 2ms, 3은 1ms가 걸림. 그리고 이를 병렬로 실행시키는 시나리오
6. 다른 ec2에 setting.sh 생성 후 복붙(내용은 부하 테스트 및 모니터링을 위한 것)
```shell
#!/bin/bash

set -e

echo &quot;=== Monitoring Server Setup ===&quot;

# 기본 패키지 설치
sudo apt update -y
sudo apt install -y wget curl apt-transport-https ca-certificates gnupg lsb-release gettext-base jq

# 작업 디렉토리 생성
mkdir -p ~/monitoring &amp;&amp; cd ~/monitoring

# Docker 설치
echo &quot;=== Installing Docker ===&quot;
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo &quot;deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot; | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null
sudo apt update -y
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker ubuntu

# k6 설치
echo &quot;=== Installing k6 ===&quot;
sudo snap install k6
k6 version

# API 서버 연결성 확인
echo &quot;=== Testing API Server Connection ===&quot;
if curl -s --connect-timeout 10 http://${API_SERVER_IP}:8080/health | grep -q &quot;UP&quot;; then
    echo &quot;API Server is accessible&quot;

    # 각 API 응답시간 확인
    echo &quot;API 응답시간 확인:&quot;
    curl -s http://${API_SERVER_IP}:8080/api/1 | jq -r &#39;&quot;api/1: &quot; + .processingTime&#39;
    curl -s http://${API_SERVER_IP}:8080/api/2 | jq -r &#39;&quot;api/2: &quot; + .processingTime&#39;  
    curl -s http://${API_SERVER_IP}:8080/api/3 | jq -r &#39;&quot;api/3: &quot; + .processingTime&#39;

else
    echo &quot;API Server not accessible - check security group&quot;
    exit 1
fi

# Prometheus 설정
cat &gt; prometheus.yml.template &lt;&lt; &#39;EOF&#39;
global:
  scrape_interval: 5s
  evaluation_interval: 5s

scrape_configs:
  - job_name: &#39;spring-boot-app&#39;
    static_configs:
      - targets: [&#39;${API_SERVER_IP}:8081&#39;]
    metrics_path: /actuator/prometheus
    scrape_interval: 2s
EOF

envsubst &lt; prometheus.yml.template &gt; prometheus.yml

# Docker Compose 설정
cat &gt; docker-compose.yml &lt;&lt; &#39;EOF&#39;
version: &#39;3.8&#39;

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - &quot;9090:9090&quot;
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-storage:/prometheus
    command:
      - &#39;--config.file=/etc/prometheus/prometheus.yml&#39;
      - &#39;--storage.tsdb.path=/prometheus&#39;
      - &#39;--storage.tsdb.retention.time=200h&#39;
      - &#39;--web.enable-lifecycle&#39;
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - &quot;3000:3000&quot;
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-storage:/var/lib/grafana
    depends_on:
      - prometheus
    restart: unless-stopped

volumes:
  prometheus-storage:
  grafana-storage:
EOF

# k6 테스트 스크립트 생성
cat &gt; business-flow-test.js &lt;&lt; &#39;EOF&#39;
import http from &#39;k6/http&#39;;
import { check, sleep } from &#39;k6&#39;;

export const options = {
  stages: [
    { duration: &#39;1m&#39;, target: 50 },    // 워밍업
    { duration: &#39;2m&#39;, target: 100 },   // 점진적 증가
    { duration: &#39;3m&#39;, target: 200 },   // 높은 부하
    { duration: &#39;2m&#39;, target: 300 },   // 피크 부하 (병목 확인)
    { duration: &#39;1m&#39;, target: 400 },   // 한계 테스트
    { duration: &#39;1m&#39;, target: 0 },     // 종료
  ],
  thresholds: {
    http_req_duration: [&#39;p(95)&lt;100&#39;],  // 엄격한 기준
    http_req_failed: [&#39;rate&lt;0.01&#39;],
  },
};

const API_BASE_URL = `http://${__ENV.API_SERVER_IP}:8080`;

export default function () {
  const response = http.get(`${API_BASE_URL}/api/business-flow`);

  check(response, {
    &#39;business-flow status is 200&#39;: (r) =&gt; r.status === 200,
    &#39;business-flow response time &lt; 50ms&#39;: (r) =&gt; r.timings.duration &lt; 50,
  });

  sleep(0.01); // 매우 짧은 think time으로 높은 부하 생성
}
EOF

# 실행 스크립트들 생성
cat &gt; run-business-flow-test.sh &lt;&lt; &#39;EOF&#39;
#!/bin/bash
echo &quot;======================================================&quot;
echo &quot;병렬 처리 병목 분석 부하테스트&quot;
echo &quot;======================================================&quot;

echo &quot;Target: http://${API_SERVER_IP}:8080/api/business-flow&quot;
echo &quot;Monitor: http://${MONITORING_SERVER_IP}:3000&quot;

read -p &quot;Press Enter to start load test...&quot;

API_SERVER_IP=${API_SERVER_IP} k6 run business-flow-test.js
EOF

cat &gt; run-with-dashboard.sh &lt;&lt; &#39;EOF&#39;
#!/bin/bash
echo &quot;======================================================&quot;
echo &quot;k6 실시간 대시보드 포함 테스트&quot;
echo &quot;======================================================&quot;
echo &quot;&quot;
echo &quot;k6 Dashboard: http://${MONITORING_SERVER_IP}:5665&quot;
echo &quot;Grafana: http://${MONITORING_SERVER_IP}:3000&quot;

API_SERVER_IP=${API_SERVER_IP} k6 run --out web-dashboard=port=5665 business-flow-test.js
EOF

chmod +x run-business-flow-test.sh run-with-dashboard.sh

# 모니터링 스택 시작
echo &quot;=== Starting Monitoring Stack ===&quot;
sudo docker compose up -d

echo &quot;=== Waiting for services to start ===&quot;
sleep 30

echo &quot;=== Service Status ===&quot;
sudo docker ps

# 초기 메트릭 생성
echo &quot;&quot;
echo &quot;=== Generating Initial Metrics ===&quot;
echo &quot;API 호출로 Grafana 메트릭 생성...&quot;
# 초기 메트릭 생성
echo &quot;&quot;
echo &quot;=== Generating Initial Metrics ===&quot;
echo &quot;API 호출로 Grafana 메트릭 생성...&quot;
for i in {1..10}; do
  curl -s http://${API_SERVER_IP}:8080/api/business-flow &gt; /dev/null
  echo &quot;API call $i completed&quot;
  sleep 1
done

echo &quot;&quot;
echo &quot;======================================================&quot;
echo &quot;Monitoring Server Setup Complete!&quot;
echo &quot;======================================================&quot;
echo &quot;&quot;
echo &quot;Access URLs:&quot;
echo &quot;- Prometheus: http://${MONITORING_SERVER_IP}:9090&quot;
echo &quot;- Grafana: http://${MONITORING_SERVER_IP}:3000 (admin/admin)&quot;
echo &quot;&quot;
echo &quot;Load Test Commands:&quot;
echo &quot;- ./run-with-dashboard.sh&quot;
echo &quot;&quot;</code></pre><ol start="7">
<li>똑같이 권한 주고 실행</li>
<li>이 상태 이후에 api호출이 이뤄져야 실제 그라파나에서 매트릭으로 나타난다해서 그것을 호출하는 내용도 위에 있음. 그것이 진행됨</li>
<li>이제 부하 테스트 진행 완료</li>
<li>이제 외부 브라우저에서 shell의 url이 가리키는 그라파나 페이지로 접근</li>
<li>설정의 connection으로 접근</li>
<li>prometheus 검색 후 add new data source.</li>
<li>커넥션을 입력해야하는데, 모니터링 ec2의 프로메테우스 컨테이너의 이름:9090을 그대로 적어야함<ul>
<li><img src="https://velog.velcdn.com/images/liini_coder/post/dbd33ac2-bcf7-45a8-9630-a3cfa0d538dd/image.png" alt=""></li>
</ul>
</li>
<li>이제 대시보드 탭을 가서 대시보드 생성을 함</li>
<li>visuallaction을 추가</li>
<li>RPS기반 쿼리를 밑에 Code탭으로 가서 쿼리를 넣는 공간에 넣고 Run Queiries를 실행<ul>
<li><code>rate(http_server_requests_seconds_count{uri=&quot;/api/business-flow&quot;}[1m])</code></li>
</ul>
</li>
<li>또 add visuallation을 하여서 Responsetime을 가져오는 쿼리를 설정<ul>
<li><code>rate(http_server_requests_seconds_sum{uri=&quot;/api/business-flow&quot;}[1m]) / rate(http_server_requests_seconds_count{uri=&quot;/api/business-flow&quot;}[1m]) * 1000</code></li>
</ul>
</li>
<li>이제까지 하면 기본 쿼리 및 모니터링 성공<h3 id="부하테스트-진행">부하테스트 진행</h3>
</li>
<li>모니터링ec2에 가서, <code>/monitoring/run-with-dashboard.sh</code> 를 실행</li>
<li>그럼이제 다양한 유저가 점점 올리면서 검사하는 스트레스 테스트가 진행됨</li>
<li>그라파나 가서 refresh를 5s로 진행</li>
<li>응답 시간과 RPS가 계속 늘어남</li>
<li>프로메테우스에서 메트릭을 볼수있음. ec2 쉘에 가서 프로메테우스 링크를 복사하여 다른 웹브라우저에 붙여넣기후 진입</li>
<li>query탭에가서 쿼리에 이걸 실행<ul>
<li><code>rate(http_server_requests_seconds_sum[1m]) / rate(http_server_requests_seconds_count[1m]) * 1000</code></li>
</ul>
</li>
<li>api마다 걸리는 딜레이 시간을 확인 가능</li>
<li>그런데 계속 진행 후에 갑자기 응답시간이 쭉 떨어지는 기간이있는데, 이 이유는 JIT 성능 최적화때문이라함</li>
<li>k6자체에서도 대시보드가 있는데, 해당 부하테스트 쿼리문에 한해서만 자세하게 볼 수 있음<ul>
<li><img src="https://velog.velcdn.com/images/liini_coder/post/5e432be2-ad88-49ca-ab43-b6473702a30b/image.png" alt=""><h2 id="의의">의의</h2>
<img src="https://velog.velcdn.com/images/liini_coder/post/d4664b72-a3a8-489e-b343-0b253710d1ca/image.png" alt=""></li>
</ul>
</li>
</ol>
<p>API마다 몇ms걸렸는지 부하테스트를 걸어서, 특정 api에서 어떤 상황에서 오래걸리는지를 미리 파악하여 성능을 개선할 수 있음</p>
<h1 id="그라파나-대시보드-종류">그라파나 대시보드 종류</h1>
<p>대시보드에서 import dashboard, 대시보드로 가면 사람들이 만든 그라파나 대시보드를 다양하게 있음. 이걸 다운 가능</p>
<ul>
<li>mysql, mongodb, jira 등등 모두 가능</li>
<li>spring boot 3.x 로 가서 copy id to clipboard하여서, 그걸 import dashboard 창에 기입하고 import하면 대시보드가 나옴<h2 id="의미">의미</h2>
<img src="https://velog.velcdn.com/images/liini_coder/post/a0ecf430-ae93-4d2f-9eb0-2365c01f9b85/image.png" alt=""></li>
<li>uptime : 실행시간</li>
<li>heap used: jvm의 힙 사이즈(배열, 객체...)</li>
<li>Non-heap used: 메타데이터 지역변수 등등 힙 제외 모든것</li>
<li>process open files: 현재 프로세스가 fp를 열고있는 개수</li>
<li>cpu 사용량: 초록은 system cpu(운영체제와 커널을 사용하는 비율), 노랑은 process cpu(프로세스가 사용하는 비율)</li>
<li>Load average: 프로세스 상태 중에서 R(Running)과 D(Disk Wait) 상태에 있는 프로세스들의 개수를 측정. 이는 CPU 코어가 처리해야 할 작업의 양을 나타내며, 시스템의 부하 상태를 파악하는 핵심 지표</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[같은 것을 포함한 순열]]></title>
            <link>https://velog.io/@liini_coder/%EA%B0%99%EC%9D%80-%EA%B2%83%EC%9D%84-%ED%8F%AC%ED%95%A8%ED%95%9C-%EC%88%9C%EC%97%B4</link>
            <guid>https://velog.io/@liini_coder/%EA%B0%99%EC%9D%80-%EA%B2%83%EC%9D%84-%ED%8F%AC%ED%95%A8%ED%95%9C-%EC%88%9C%EC%97%B4</guid>
            <pubDate>Thu, 27 Feb 2025 03:55:10 GMT</pubDate>
            <description><![CDATA[<h1 id="중복순열과-뭐가-다르지">중복순열과 뭐가 다르지?</h1>
<p>중복순열은 서로다른 n개에서 중복을 허용하여 r개를 뽑는것이다. 따라서 n^r 의 경우가 나온다.
 예를 들어 500원, 100원, 50원, 10원 동전이 충분히 많이 있고, 10개를 뽑아서 만들수있는 총금액의 경우의 수는 4^10이다.
 하지만, 같은 것을 포함한 순열은 그냥 <code>[1, 1, 2, 3, 5, 5, 5]</code> 를 순열하는 것이다. 따라서 경우의 수는 (7!)/(2! * 3!) 이다.</p>
<h1 id="구현-방법-2가지">구현 방법 2가지</h1>
<h2 id="next-permutaion-이용">Next Permutaion 이용</h2>
<p> next Permutation은 어떠한 순열의 결과(그냥 수열)에서 다름의 순열 경우를 딱 하나 배출하는 것을 말한다.</p>
<pre><code class="language-java"> package ssafy.agorithm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

public class NextPermutationTest {
    static int N;
    static int R;
    static int[] input;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        N = sc.nextInt();
        input = new int[N];

        for (int i = 0; i &lt; N; i++) {
            input[i] = sc.nextInt();
        }
        R = sc.nextInt();
        //오름차순정렬 필요
        Arrays.sort(input);

        do {
            System.out.println(Arrays.toString(input));
        }while(nextPermutation(input));
    }

    static boolean nextPermutation(int[] arr) {//현상태의 순열에서 사전식 다음 순열 생성후 다음순열 존재하면 true
        //step1 뒤쪽부터 탐색하며 꼭대기(i) 찾기 -&gt; 교환위치(i-1)찾기 위함
        int i = N-1;
        while(i&gt;0 &amp;&amp; arr[i-1]&gt;=arr[i]) --i;
        if(i==0) {
            //가장 큰 순열이라 더이상 다음 순열이 없다
            return false;
        }
        //step2 교환자리인 i-1의 값과 교환할 한단계 큰 수를 뒤쪽부터 찾기
        int j = N-1;
        while(arr[i-1] &gt;= arr[j]) --j;

        //step3 i-1과 j의 값 교환
        swap(arr, i-1, j);

        //step4 i-1자리의 한단계 큰 수로 변화를 줬으니, i꼭대기 위치부터 맨 뒤까지 가장 작은 수를 만듬(오름차순정렬)
        int k = N-1;
        while(i&lt;k) swap(arr, i++, k--);
        return true;
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}</code></pre>
<h2 id="number_counts를-이용한-dfs">number_counts를 이용한 dfs</h2>
<p> <code>[1, 1, 2, 3, 5, 5, 5]</code>를 number_counts하면 <code>{1:2, 2:1, 3:1, 5:3}</code> 형태가 되고, dfs에서 for(number_counts.keys())를 한다. 그리고 다음 dfs를 가기전에 해당하는 key의 value를 하나 줄이고, dfs다음엔 다시 value를 원상복귀시킨다.</p>
<pre><code class="language-java"> package ssafy.agorithm;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.StringTokenizer;

public class PermutationWithSameThing {

    private static BufferedReader br;
    private static LinkedHashMap&lt;Integer, Integer&gt; number_counts;
    private static int n;
    private static int[] perm;
    private static int perm_count;

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        System.out.println(&quot;같은것을 포함한 수열을 적어주세요. 수열의 결과를 출력할게요.&quot;);
        number_counts = new LinkedHashMap&lt;Integer, Integer&gt;();
        n = 0;
        StringTokenizer st = new StringTokenizer(br.readLine());
        while(st.hasMoreTokens()) {
            n++;
            int num = Integer.parseInt(st.nextToken());
            if(!number_counts.containsKey(num))
                number_counts.put(num, 0);
            number_counts.replace(num, number_counts.get(num)+1);
        }

        //필요시 정렬을 하고 dfs돌림
        perm = new int[n];
        dfs(0);
        System.out.println(perm_count);
    }

    private static void dfs(int count) {
        if(count == n) {
            System.out.println(Arrays.toString(perm));
            perm_count++;

            return;
        }

        Iterator&lt;Map.Entry&lt;Integer, Integer&gt;&gt; it = number_counts.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry&lt;java.lang.Integer, java.lang.Integer&gt; entry = (Map.Entry&lt;java.lang.Integer, java.lang.Integer&gt;) it
                    .next();
            if(entry.getValue() != 0) {
                entry.setValue(entry.getValue() - 1);
                perm[count] = entry.getKey();
                dfs(count+1);
                entry.setValue(entry.getValue() + 1);
            }
        }

    }
}
/*입력
 * 1 2 2 5
 * 출력
[1, 2, 2, 5]
[1, 2, 5, 2]
[1, 5, 2, 2]
[2, 1, 2, 5]
[2, 1, 5, 2]
[2, 2, 1, 5]
[2, 2, 5, 1]
[2, 5, 1, 2]
[2, 5, 2, 1]
[5, 1, 2, 2]
[5, 2, 1, 2]
[5, 2, 2, 1]
12
*/
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 함수형 인터페이스로 클로저로직 구현하기]]></title>
            <link>https://velog.io/@liini_coder/%EC%9E%90%EB%B0%94-%ED%95%A8%EC%88%98%ED%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A1%9C-%ED%81%B4%EB%A1%9C%EC%A0%80%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@liini_coder/%EC%9E%90%EB%B0%94-%ED%95%A8%EC%88%98%ED%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A1%9C-%ED%81%B4%EB%A1%9C%EC%A0%80%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 21 Feb 2025 01:37:04 GMT</pubDate>
            <description><![CDATA[<h1 id="계기">계기</h1>
<p>순열, 조합, 부분집합을 자바로 구현할 때 여러 마음에 들지 않는 부분이 더러 있었다.</p>
<ol>
<li><p>N, R, ARR등 모든 변수를 static으로 전역변수로 둬야한다</p>
</li>
<li><p>is_selected, visited같이 순조부만을 위한 변수도 static으로 전역변수로 두니 다른 변수들하고 용도가 헷갈려짐</p>
</li>
<li><p>순조부의 결과가 보통 재구함수의 기저부분에 완성이  되니, 이때 바로 그 결과를 가지고 로직을 실행하고싶음.(결과값을 모조리 하나의 Array에 넣는건 비효율적)</p>
<pre><code class="language-java">public class SubSetTest2 {
 static int[] arr;

 //subSet에 관한 변수도 추가... 여기에 적고 싶지않음..
 static boolean[] is_selected;

 public static void main(String[] args) throws IOException {
     BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
     arr = Arrays.stream(br.readLine().split(&quot; &quot;)).mapToInt(Integer::parseInt).toArray();
     is_selected = new boolean[arr.length];
     subSet(0);
 }
 private static void subSet(int cnt) {
     if(cnt == arr.length) {
         //subSet결과인 is_selected를 사용한 로직을 다변화하고시싶다.
         for (int i = 0; i &lt; is_selected.length; i++) {
             System.out.print(((is_selected[i])? arr[i] : &quot;X&quot;) + &quot; &quot;);
         }
         System.out.println();
         return;
     }

     is_selected[cnt] = true;
     subSet(cnt+1);
     is_selected[cnt] = false;
     subSet(cnt+1);
 }
}
/* 입력
* 1 2 3
출력
1 2 3 
1 2 X 
1 X 3 
1 X X 
X 2 3 
X 2 X 
X X 3 
X X X 
* 
* */</code></pre>
<h1 id="구현">구현</h1>
<h2 id="1-클래스를-이용하여-1-2해결">1. 클래스를 이용하여 1, 2해결</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
</code></pre>
</li>
</ol>
<p>class SubSet{
    int[] arr;
    boolean[] is_selected;
    public SubSet(int[] arr) {
        this.arr = arr;
        is_selected = new boolean[arr.length];
        createSubSet(0);
    }
    private void createSubSet(int cnt) {
        if(cnt == arr.length) {
            //순조부 결과인 is_selected를 이용한 로직을 콜백형태로 전달하고 싶어...
            for (int i = 0; i &lt; is_selected.length; i++) {
                System.out.print(((is_selected[i])? arr[i] : &quot;X&quot;) + &quot; &quot;);
            }
            System.out.println();
            return;
        }</p>
<pre><code>    is_selected[cnt] = true;
    createSubSet(cnt+1);
    is_selected[cnt] = false;
    createSubSet(cnt+1);
}</code></pre><p>}</p>
<p>public class SubSetTest {</p>
<pre><code>public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int[] arr = Arrays.stream(br.readLine().split(&quot; &quot;)).mapToInt(Integer::parseInt).toArray();

    new SubSet(arr);
}</code></pre><p>}
/* 입력</p>
<ul>
<li>1 2 3
출력
1 2 3 
1 2 X 
1 X 3 
1 X X 
X 2 3 
X 2 X 
X X 3 
X X X </li>
<li></li>
<li>*/<pre><code>하지만, 콜백을 넘겨줘서 내가 하고싶은 로직을 순조부의 결과가 나온 곳에서 바로 실행시키고 싶다. 그래서 `함수형 인터페이스`를 사용하여 고쳤다.
## 2. 함수형 인터페이스 Consumer계열을 사용
```java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
</code></pre></li>
</ul>
<p>class SubSet{
    int[] arr;
    boolean[] is_selected;
    private BiConsumer&lt;boolean[], SubSet&gt; callback;
    public SubSet(int[] arr, BiConsumer&lt;boolean[], SubSet&gt; print) {
        this.arr = arr;
        is_selected = new boolean[arr.length];
        this.callback = print;
        createSubSet(0);
    }
    private void createSubSet(int cnt) {
        if(cnt == arr.length) {
        //콜백함수를 js처럼 바로 사용하진못하고, 이렇게 .accept()를 활용해야함
            this.callback.accept(is_selected, this);
            return;
        }</p>
<pre><code>    is_selected[cnt] = true;
    createSubSet(cnt+1);
    is_selected[cnt] = false;
    createSubSet(cnt+1);
}</code></pre><p>}</p>
<p>public class SubSetTest {</p>
<pre><code>public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int[] arr = Arrays.stream(br.readLine().split(&quot; &quot;)).mapToInt(Integer::parseInt).toArray();

    BiConsumer&lt;boolean[], SubSet&gt; print = (is_selected, object)-&gt;{
        for (int i = 0; i &lt; is_selected.length; i++) {
            System.out.print(((is_selected[i])? object.arr[i] : &quot;X&quot;) + &quot; &quot;);
        }
        System.out.println();
    };
    //콜백함수와 같이 전달
    new SubSet(arr, print);
}</code></pre><p>}</p>
<p>```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바로 파이썬의 다중데이터 키를가지는 딕셔너리 구현하기]]></title>
            <link>https://velog.io/@liini_coder/%EC%9E%90%EB%B0%94%EB%A1%9C-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9D%98-%EB%8B%A4%EC%A4%91%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%82%A4%EB%A5%BC%EA%B0%80%EC%A7%80%EB%8A%94-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@liini_coder/%EC%9E%90%EB%B0%94%EB%A1%9C-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9D%98-%EB%8B%A4%EC%A4%91%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%82%A4%EB%A5%BC%EA%B0%80%EC%A7%80%EB%8A%94-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 06 Feb 2025 08:20:55 GMT</pubDate>
            <description><![CDATA[<h1 id="코드">코드</h1>
<pre><code class="language-java">public class Omok {
    class Key{
        private final int part1;
        private final int part2;

        public Key(int a, int b) {
            part1 = a;
            part2 = b;
        }
        //HashMap의 동일연산을 처리하기 위해선 equals()와 HashCode()를 재정의해줘야한다.
        @Override
        public boolean equals(Object obj) {
            if(this == obj) return true;
            //obj의 실제 메모리상의 클래스가 같은지도 판단해야한다(다음라인부터 안전한 캐스팅이 가능해짐)
            if (obj == null || getClass() != obj.getClass()) {
                return false;
            }
            Key target = (Key)obj;
            return (target.part1 == part1 &amp;&amp; target.part2 == part2);
        }
        //Objects.hash(T ...)는 내부적으로 Arrays.hash()를 활용한다
        @Override
        public int hashCode() {
            return Objects.hash(part1, part2);
        }
        @Override
        public String toString() {
            return &quot;Key [part1=&quot; + part1 + &quot;, part2=&quot; + part2 + &quot;]&quot;;
        }

    }
    public static void main(String[] args) {
        Omok o = new Omok(); // 아래 new Key()를 하기 위해서 필요하다
        Map&lt;Key, Integer&gt; dp = new HashMap&lt;&gt;();
        dp.put(o.new Key(1, 3), 4); // o.을 안쓰면 에러난다
        dp.put(o.new Key(1, 3), 5);
        dp.put(o.new Key(1, 10), 10);
        for (Map.Entry&lt;Key, Integer&gt; entry : dp.entrySet()) {
            Key key = entry.getKey();
            Integer val = entry.getValue();
            System.out.println(key + &quot; / &quot;+val);
        }
    }
}
/*
출력결과
Key [part1=1, part2=3] / 5
Key [part1=1, part2=10] / 10
*/</code></pre>
<h1 id="방법">방법</h1>
<ol>
<li>Key에 넣을 사용자정의 클래스 생성</li>
<li>1의 클래스에 <code>equals()</code>와 <code>hashCode()</code> 오버라이딩
 i. <code>equals()</code>엔 같은 객체인지 / null인지 / 메모리상 클래스가 같은지(<code>getClass()</code>활용) 를 확인
 ii. 모두 확인하면 이제야 멤버변수 각각 비교
 iii. <code>hashCode()</code>는 <code>Objects.hash(T ...)</code>를 활용한다</li>
<li>1의 클래스를 토대로 <code>Map&lt;K, V&gt;</code>를 선언 후 사용한다.</li>
<li><code>Map</code>을 순회하기 위해선 <code>Map.Entry</code>를 활용한다.(이클립스에서 ctrl+space로 <code>formap</code>줄임말을 활용하면 자동완성된다.)<h1 id="여담">여담</h1>
<h2 id="계기">계기</h2>
<a href="https://www.acmicpc.net/submit/2615/77672670">백준2615. 오목</a>을 풀때, 2차원 좌표를 key로 하는 딕셔너리를 만들고싶었다. 파이썬에선 매우 쉽게 튜플화하여 구현하였지만, 자바에선 이 방식이 매우 힘들었다.<h2 id="다짐">다짐</h2>
앞으로 이렇게 다중 데이터를 Map의 Key로 넣는 로직은 코테에서 많이 나올것이다. 그러니 이를 꼭 숙지해두자.<h2 id="의문점">의문점</h2>
</li>
</ol>
<ul>
<li><code>getClass() == obj.getClass()</code> 는 메모리상의 객체의 클래스가 같은지 비교하는 것같은데, Class를 반환하는데에 왜 ==을 사용해도 되는거야? <code>getClass().getName().equals(obj.getClass().getName())</code> 으로 써야 안전한거 아닌가?<ul>
<li><code>참조 타입에 대해 == 연산자는 메모리 주소를 비교합니다. Class 객체의 경우, 같은 클래스에 대해서는 항상 같은 메모리 주소를 가지므로 == 연산자로 비교해도 정확합니다</code></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 여담]]></title>
            <link>https://velog.io/@liini_coder/%EC%9E%90%EB%B0%94-%EC%97%AC%EB%8B%B4</link>
            <guid>https://velog.io/@liini_coder/%EC%9E%90%EB%B0%94-%EC%97%AC%EB%8B%B4</guid>
            <pubDate>Thu, 16 Jan 2025 08:09:51 GMT</pubDate>
            <description><![CDATA[<h2 id="한-파일-안에-public-class는-한개만-허용">한 파일 안에 public class는 한개만 허용</h2>
<h3 id="그-public-class-이름으로-파일이름이-정해져야함">그 public class 이름으로 파일이름이 정해져야함</h3>
<h2 id="float-f--123-은-불가능">float f = 1.23 은 불가능</h2>
<p>실수형 리터럴은 무조건 double이고, downcasting은 암시적으로 되지 않음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프롬프팅 공부]]></title>
            <link>https://velog.io/@liini_coder/%ED%94%84%EB%A1%AC%ED%94%84%ED%8C%85-%EA%B3%B5%EB%B6%80</link>
            <guid>https://velog.io/@liini_coder/%ED%94%84%EB%A1%AC%ED%94%84%ED%8C%85-%EA%B3%B5%EB%B6%80</guid>
            <pubDate>Tue, 14 Jan 2025 01:36:18 GMT</pubDate>
            <description><![CDATA[<h1 id="개념">개념</h1>
<h2 id="foundation-model">Foundation Model</h2>
<ul>
<li>초거대 AI모델</li>
<li>엄청난 양의 HW와 개발자 필요</li>
<li>회사마다 공통된 LLM을 사용<h2 id="llm의-파라미터">LLM의 파라미터</h2>
</li>
<li>비유상 AI의 뇌세포에 해당<h2 id="프롬프트-엔지니어링-rag-sllm">프롬프트 엔지니어링, RAG, sLLM</h2>
</li>
<li>회사마다 자체적인 파운데이션 모델을 가지기 어려워서, 이러한 개념들이 등장함<h1 id="프롬프트-엔지니어링">프롬프트 엔지니어링</h1>
</li>
<li>생성형 AI를 잘 다루는 기법들을 의미<h2 id="역할-설정해-질문하기">역할 설정해 질문하기</h2>
개발회사입장에선 LLM 사용자의 지식수준을 모르니 범용적인 대답을 하도록 세팅해둠
따라서 역할을 부여하면 범접할 수 없던 정보에 접근 가능해지는 경우가 많아짐<h2 id="명확한-아웃풋을-요청하기">명확한 아웃풋을 요청하기</h2>
<h2 id="추가적인-정보를-제공하기">추가적인 정보를 제공하기</h2>
</li>
<li>할루시네이션을 막기위한 막강한 방법</li>
<li>이는 <strong>RAG의 발전</strong>으로 이어짐<h1 id="프롬프트-엔지니어링심화">프롬프트 엔지니어링(심화)</h1>
<h2 id="생각의-사슬">생각의 사슬</h2>
</li>
</ul>
<ol>
<li>수학문제와도 같은 답이 명확한 대답을 요구하면 대부분 틀린다</li>
<li>이때 <strong>풀이같은 중간과정을 넣어주면</strong> 답을 잘 찾는다고 논문으로 증명되어있다.<h1 id="요즘의-ai검색-활용">요즘의 AI검색 활용</h1>
챗GPT 기준</li>
</ol>
<ul>
<li>pdf넣어서 기반 질문 가능</li>
<li>video summerizer라고 해서, 유튜브 링크를 주면 이를 요약해줌</li>
<li>비즈니스에서 문서 자동화에 획기적인 활용<ul>
<li>구글 스프레드 함수를 이용하여, 해당 댓글들이 부정인지 긍정인지 판단<ul>
<li><code>=GPT_CLASSFY(A12, &quot;긍정, 부정&quot;)</code></li>
</ul>
</li>
<li>키워드 추출, 요약, 번역, 다른형식으로 개편 등<ul>
<li><code>=GPT(A12, &quot;이를 다른 형식으로 개편해줘&quot;)</code></li>
</ul>
</li>
<li>마케팅 활용<ul>
<li><code>=GPT(A12-14, &quot;이 셀들을 참고하여 홍보문구를 만들어줘&quot;)</code></li>
</ul>
</li>
<li>데이터 정리<ul>
<li>사용자 폼을 받은 전화번호를 010-xxxx-xxxx 형식으로 바꿔주기<ul>
<li><code>GPT_FILL(A12-13, B16-20)</code> // A12-13의 형태대로 B16-20의 형태를 바꿔줘<h1 id="요즘의-llm트렌드">요즘의 LLM트렌드</h1>
<h2 id="rag">RAG</h2>
Retrieval-Augmented Generation</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>용도 : 내부문서를 참고한 AI를 만들고 싶을때</li>
<li>구성 : 주로 내부 문서들의 집합, 사용자, 챗봇, LLM모델로 이뤄져있음</li>
<li>원리<ol>
<li>사용자의 프롬프팅</li>
<li>챗봇이 이를 받아, 내부문서DB에서 정보를 가져오고, 사용자의 프롬프팅과 정제화한 내부문서 정보를 한꺼번에 LLM에 전송</li>
<li>LLM의 결과를 챗봇이 받아서 이를 사용자에게 전달</li>
</ol>
</li>
<li>챌린지 : 어떻게 내부문서들을 정제화하여 LLM에게 질문해야 결과가 좋을까?<h2 id="agent">Agent</h2>
특정 과업을 하나의 AI가 전체적으로 처리하는 것이 아니라, 과업의 세부요소를 적합한 AI에게 나눠서 질문하는 것<h2 id="sllm">sLLM</h2>
경량화된 LLM모델</li>
<li>주로 보안을 위해 사용</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 순열, 조합, 중복순열, 중복조합]]></title>
            <link>https://velog.io/@liini_coder/JS-%EC%88%9C%EC%97%B4-%EC%A1%B0%ED%95%A9-%EC%A4%91%EB%B3%B5%EC%88%9C%EC%97%B4-%EC%A4%91%EB%B3%B5%EC%A1%B0%ED%95%A9</link>
            <guid>https://velog.io/@liini_coder/JS-%EC%88%9C%EC%97%B4-%EC%A1%B0%ED%95%A9-%EC%A4%91%EB%B3%B5%EC%88%9C%EC%97%B4-%EC%A4%91%EB%B3%B5%EC%A1%B0%ED%95%A9</guid>
            <pubDate>Tue, 05 Nov 2024 08:28:06 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>JS엔 파이썬과 달리 itertools이 없다. 그래서 직접 구현해야하는데, 수많은 방법중에 이 방법을 택했다.</p>
<h1 id="방향">방향</h1>
<ol>
<li><p>시간이 적은 코테에서 바로 사용할 수 있도록 간단해야할것</p>
</li>
<li><p>하나의 Base코드에서 순열, 조합, 중복순열, 중복조합 모든 것을 쉽게 구현할 수 있어야할 것</p>
<h1 id="구현">구현</h1>
<p>기본적으로 visited를 사용한다.</p>
</li>
<li><p>재귀를 할 때 inorder형식을 사용</p>
</li>
<li><p>for문의 초기값이 0(순열일 때) 혹은 이전재귀idx(조합일 때)</p>
</li>
<li><p>중복을 구현할 땐, visited형식을 뺀다</p>
<h2 id="순열base">순열(Base)</h2>
<pre><code class="language-js">function permutations(arr, r){
 if(r === 0)
     return [];
 const result = [];
 const visited = new Array(arr.length).fill(false);
 function recur(sub_arr){
     if(sub_arr.length === r){
         result.push(sub_arr);
         return;
     }
     for (let i = 0; i &lt; arr.length; i++) {
         if(visited[i])
             continue;
         visited[i] = true;
         recur([...sub_arr, arr[i]]);
         visited[i] = false;
     }
 }
 recur([]);
 return result;
}</code></pre>
<h2 id="중복순열">중복순열</h2>
<p>단순한 N의 r제곱</p>
<pre><code class="language-js">function permutations_w(arr, r){
 if(r === 0)
     return [];
 const result = [];
 //const visited = new Array(arr.length).fill(false);
 function recur(sub_arr){
     if(sub_arr.length === r){
         result.push(sub_arr);
         return;
     }
     for (let i = 0; i &lt; arr.length; i++) {
         //if(visited[i])
         //    continue;
         visited[i] = true;
         recur([...sub_arr, arr[i]]);
         visited[i] = false;
     }
 }
 recur([]);
 return result;
}</code></pre>
<h2 id="조합">조합</h2>
<pre><code class="language-js">function combinations(arr, r){
 if(r === 0)
     return [];
 const result = [];
 const visited = new Array(arr.length).fill(false);
 function recur(sub_arr, idx){ // idx 매개변수 추가
     if(sub_arr.length === r){
         result.push(sub_arr);
         return;
     }
     for (let i = idx; i &lt; arr.length; i++) { // i초기값 0-&gt;idx
         if(visited[i])
             continue;
         visited[i] = true;
         recur([...sub_arr, arr[i]], i); // i 매개변수 추가
         visited[i] = false;
     }
 }
 recur([], 0);
 return result;
}</code></pre>
<h2 id="중복조합">중복조합</h2>
<p>조합코드에서 <code>if(visited[i])</code>만 제거</p>
<pre><code class="language-js">function combinations_w(arr, r){
 if(r === 0)
     return [];
 const result = [];
 //const visited = new Array(arr.length).fill(false);
 function recur(sub_arr, idx){
     if(sub_arr.length === r){
         result.push(sub_arr);
         return;
     }
     for (let i = idx; i &lt; arr.length; i++) {
         //if(visited[i])
             //continue;
         //visited[i] = true;
         recur([...sub_arr, arr[i]], i);
         //visited[i] = false;
     }
 }
 recur([], 0);
 return result;
}</code></pre>
<h2 id="참고-하나의-함수로-구현">참고: 하나의 함수로 구현</h2>
<p>조합, 중복조합, 순열, 중복순열을 하나의 함수로 구현하면 다음과 같다.</p>
<pre><code class="language-js">function generateSequences(arr, r, is_repetition, is_combination) {
 if (r === 0) return [];

 const result = [];
 const visited = new Array(arr.length).fill(false);

 function recur(sub_arr, idx) {
     if (sub_arr.length === r) {
         result.push(sub_arr);
         return;
     }

     for (let i = is_combination ? idx : 0; i &lt; arr.length; i++) {
         if (is_repetition || !visited[i]) {
             visited[i] = true;
             recur([...sub_arr, arr[i]], i);
             visited[i] = false;
         }
     }
 }

 recur([], 0);
 return result;
}
</code></pre>
</li>
</ol>
<p>// 사용 예시
const arr = [1, 2, 3, 4, 5];</p>
<p>// 순열
let permutations = generateSequences(arr, 2, false, false);
console.log(<code>Permutations Length: ${permutations.length}</code>);
console.log(permutations);
console.log(<code>-</code>.repeat(10));</p>
<p>// 조합
let combinations = generateSequences(arr, 2, false, true);
console.log(<code>Combinations Length: ${combinations.length}</code>);
console.log(combinations);
console.log(<code>-</code>.repeat(10));
// 중복 순열
let permutationsWithRepetition = generateSequences(arr, 2, true, false);
console.log(<code>Permutations with Repetition Length: ${permutationsWithRepetition.length}</code>);
console.log(permutationsWithRepetition);
console.log(<code>-</code>.repeat(10));
// 중복 조합
let combinationsWithRepetition = generateSequences(arr, 2, true, true);
console.log(<code>Combinations with Repetition Length: ${combinationsWithRepetition.length}</code>);
console.log(combinationsWithRepetition);
console.log(<code>-</code>.repeat(10));</p>
<pre><code>```bash
$ node &quot;p:\CodingTest\temp.js&quot;
Permutations Length: 20
[
  [ 1, 2 ], [ 1, 3 ], [ 1, 4 ],
  [ 1, 5 ], [ 2, 1 ], [ 2, 3 ],
  [ 2, 4 ], [ 2, 5 ], [ 3, 1 ],
  [ 3, 2 ], [ 3, 4 ], [ 3, 5 ],
  [ 4, 1 ], [ 4, 2 ], [ 4, 3 ],
  [ 4, 5 ], [ 5, 1 ], [ 5, 2 ],
  [ 5, 3 ], [ 5, 4 ]
]
----------
Combinations Length: 10
[
  [ 1, 2 ], [ 1, 3 ],
  [ 1, 4 ], [ 1, 5 ],
  [ 2, 3 ], [ 2, 4 ],
  [ 2, 5 ], [ 3, 4 ],
  [ 3, 5 ], [ 4, 5 ]
]
----------
Permutations with Repetition Length: 25
[
  [ 1, 1 ], [ 1, 2 ], [ 1, 3 ],
  [ 1, 4 ], [ 1, 5 ], [ 2, 1 ],
  [ 2, 2 ], [ 2, 3 ], [ 2, 4 ],
  [ 2, 5 ], [ 3, 1 ], [ 3, 2 ],
  [ 3, 3 ], [ 3, 4 ], [ 3, 5 ],
  [ 4, 1 ], [ 4, 2 ], [ 4, 3 ],
  [ 4, 4 ], [ 4, 5 ], [ 5, 1 ],
  [ 5, 2 ], [ 5, 3 ], [ 5, 4 ],
  [ 5, 5 ]
]
----------
Combinations with Repetition Length: 15
[
  [ 1, 1 ], [ 1, 2 ],
  [ 1, 3 ], [ 1, 4 ],
  [ 1, 5 ], [ 2, 2 ],
  [ 2, 3 ], [ 2, 4 ],
  [ 2, 5 ], [ 3, 3 ],
  [ 3, 4 ], [ 3, 5 ],
  [ 4, 4 ], [ 4, 5 ],
  [ 5, 5 ]
]
----------</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[JS deque 구현하기]]></title>
            <link>https://velog.io/@liini_coder/JS-deque-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@liini_coder/JS-deque-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 05 Nov 2024 06:33:04 GMT</pubDate>
            <description><![CDATA[<h1 id="js에서-deque는-구현되어있지않다">JS에서 deque는 구현되어있지않다</h1>
<p>python에선 <code>from collections import deque</code>로 사용할 수 있지만, js에선 없다.  </p>
<h2 id="그냥-shift쓰면-되지않나">그냥 <code>shift</code>쓰면 되지않나?</h2>
<p>deque의 장점은 왼쪽에서 아이템을 꺼내도 O(1)이 걸리도로 설계되어있다.(by 더블링크드리스트) 그런데 <code>shift</code>를 써버리면 O(리스트 길이)가 되어버린다.</p>
<h1 id="구현">구현</h1>
<h2 id="방향">방향</h2>
<ol>
<li><p>시간이 촉박한 코테에서 바로 구현할 수 있도록 쉽게</p>
</li>
<li><p>기존 파이썬의 명령어 <code>append</code>, <code>popleft</code>를 사용하도록</p>
<h2 id="코드">코드</h2>
<pre><code class="language-js">class deque{
 constructor(){
     this.storage = {};
     this.front = 0;
     this.rear = 0;
 }
 size(){
     return this.rear - this.front;
 }
 append(element){
     this.storage[this.rear++] = element;
 }
 popleft(){
     if(this.front === this.rear){
         this.front = this.rear = 0;
         return null;
     }
     let popped_element = this.storage[this.front];
     delete this.storage[this.front];
     this.front++;

       if(this.front === this.rear){
         this.front = this.rear = 0;
     }
     return popped_element;
 }
}</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python -> JS 코테 주의점]]></title>
            <link>https://velog.io/@liini_coder/Python-JS-%EC%BD%94%ED%85%8C-%EC%A3%BC%EC%9D%98%EC%A0%90</link>
            <guid>https://velog.io/@liini_coder/Python-JS-%EC%BD%94%ED%85%8C-%EC%A3%BC%EC%9D%98%EC%A0%90</guid>
            <pubDate>Tue, 05 Nov 2024 06:06:10 GMT</pubDate>
            <description><![CDATA[<h1 id="range10">range(10)</h1>
<p><strong><code>...Array(10).keys()</code>를 쓰거나 그냥 for문으로 만들자</strong></p>
<h1 id="103-몫구하기">10//3 몫구하기</h1>
<p><strong><code>Math.floor(10/3)</code>으로 찐나누기를 쓴다음에 소수점을 버리자</strong></p>
<h1 id="insertidx-element">[].insert(idx, element)</h1>
<p><strong><code>[].splice(start_idx: number, deleteCount: number, ...추가할items: number[]): number[]</code></strong>
아래는 0번째에 2개를 insert하는 예
<img src="https://velog.velcdn.com/images/liini_coder/post/b3d5f60f-9eac-4575-9689-a785a831db63/image.png" alt="">
아래는 ...연산자를 쓰지 않고 넣은 예. 의도한대로 동작하지않는다.
<img src="https://velog.velcdn.com/images/liini_coder/post/05842d70-fb1e-4d40-855a-21a13f35c084/image.png" alt="">
이렇게 ...을 써야 동작한다.
<img src="https://velog.velcdn.com/images/liini_coder/post/719a6857-a47d-42db-b6f6-af144d5b31dc/image.png" alt="">
아래는 2개를 지우는 예시
<img src="https://velog.velcdn.com/images/liini_coder/post/04e68dcc-5b90-498a-bf18-29fc278cb1b6/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 슬라이싱 함수]]></title>
            <link>https://velog.io/@liini_coder/JS-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EC%8B%B1-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@liini_coder/JS-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EC%8B%B1-%ED%95%A8%EC%88%98</guid>
            <pubDate>Wed, 26 Jun 2024 06:31:49 GMT</pubDate>
            <description><![CDATA[<h1 id="결론--slice함수-쓰자">결론 : .slice함수 쓰자</h1>
<h2 id="slice">.slice</h2>
<p>파이썬의 슬라이싱과 똑같다.  </p>
<ol>
<li>음수 인덱스를 쓸 수 있다</li>
<li>배열, 문자열 모두 가능하다</li>
</ol>
<pre><code class="language-javascript">const a = &quot;ABCDEFG&quot;;
const list = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;];

const raw_data = a;
for(let raw_data of [a, list]){
    let sliced = raw_data.slice(1, 3);
    console.log(sliced);
    sliced = raw_data.slice(-2, raw_data.length);
    console.log(sliced);
    sliced = raw_data.slice(-2);
}</code></pre>
<p><img src="https://velog.velcdn.com/images/liini_coder/post/142c8cfc-8244-4129-b19f-2c3d53ec6933/image.png" alt=""></p>
<h2 id="substring">.substring</h2>
<p>위의 slice와 두가지가 다르다.</p>
<ol>
<li>문자열에만 사용가능</li>
<li>음수 인덱스를 아무리 넣어도 0으로 인자 넣은 것과 똑같아진다.</li>
</ol>
<p>또한 .substring(data1) 인자 한개일 땐, data1은 startIndex로, 맨 끝까지 슬라이싱하게 된다.</p>
<h2 id="substr">.substr</h2>
<p>쓰지 말자. js에서 공식적으로 deprecated되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 로직 정리]]></title>
            <link>https://velog.io/@liini_coder/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%A1%9C%EC%A7%81-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@liini_coder/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%A1%9C%EC%A7%81-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 21 Jun 2024 15:22:58 GMT</pubDate>
            <description><![CDATA[<h1 id="1-객체-및-리터럴">1. 객체 및 리터럴</h1>
<h2 id="숫자는-오직-number타입으로-모든-수를-실수-처리64bit부동소수">숫자는 오직 Number타입으로 모든 수를 실수 처리(64bit부동소수)</h2>
<p><img src="https://velog.velcdn.com/images/liini_coder/post/12c6e610-0dac-456a-baf2-b1044506b244/image.png" alt=""></p>
<h3 id="특별-숫자로-3가지-존재">특별 숫자로 3가지 존재</h3>
<p><code>Infinity</code> : 양의 무한대
<code>-Infinity</code> : 음의 무한대
<code>NaN</code> : 숫자가 아님(단 타입은 Number에 속함)
<img src="https://velog.velcdn.com/images/liini_coder/post/8ab2f47c-22a5-4fc8-ae82-589fd46f9c23/image.png" alt=""></p>
<h2 id="문자열은-변경불가능immutable-값">문자열은 변경불가능(immutable) 값</h2>
<p>이 점은 파이썬과 같다.</p>
<h2 id="undefined는-js엔진이-변수-초기화시사용개발자-입장에선-의도x">undefined는 js엔진이 변수 초기화시사용(개발자 입장에선 의도X)</h2>
<h2 id="null은-변수에-값이-없다는-것을-명시개발자-입장에서-의도함-또는-유효한-값을-반환할-수-없는-경우">null은 변수에 값이 없다는 것을 명시(개발자 입장에서 의도함) 또는 유효한 값을 반환할 수 없는 경우</h2>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;body&gt;
    &lt;script&gt;
      var element = document.querySelector(&#39;.myClass&#39;);
      console.log(element); // null
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<h1 id="2-var-let-const비교">2. var, let, const비교</h1>
<p>var  : 재할당 가능.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[sort() 다중 정렬 방법]]></title>
            <link>https://velog.io/@liini_coder/sort-%EB%8B%A4%EC%A4%91-%EC%A0%95%EB%A0%AC-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@liini_coder/sort-%EB%8B%A4%EC%A4%91-%EC%A0%95%EB%A0%AC-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Fri, 19 Apr 2024 10:25:24 GMT</pubDate>
            <description><![CDATA[<h1 id="sortkeylambda-x-튜플">sort(key=lambda x: <strong>튜플!</strong>)</h1>
<p><img src="https://velog.velcdn.com/images/liini_coder/post/06258e79-0071-4670-aa4f-e236e4569525/image.png" alt="">  </p>
<pre><code class="language-python">a1 = [(2, 5), (5, 5), (4, 5), (3, 5), (0, 3)]
a2 = [(2, 5), (5, 5), (4, 5), (3, 5), (0, 3)]
#sort()는 stable하지만, 애초에 input순서가 순서대로일거라는 보장이없어서 의미없음
a1.sort(key=lambda x: x[1]) #x[1]기준으로 오름차순, (단, x[0]은 신경 안씀)
a2.sort(key=lambda x: (x[1], -x[0])) #x[1]기준으로 오름차순, x[1]이 같을 경우엔 x[0]기준으로 내림차순
print(f&quot;a1 : {a1}&quot;)
print(f&quot;a2 : {a2}&quot;)</code></pre>
<p>보통 리스트안에 컨테이너 변수들로 이뤄질때 sort를 이용하면 <code>key=lambda x: x[?]</code> 꼴로 많이 쓴다. 그렇다면 x[?]이 서로 같은 컨테이너 변수끼리라면 어떻게 정렬될까?  </p>
<p>사실 python의 <strong>sort()는 stable</strong>하다. 따라서 list 순서가 그대로 보장되긴할텐데...<br><strong>코테에선 input 순서에 어떠한 기대를 해선 안된다!</strong>
코테 문제에서 제공되는 TC(Test Case)들은 보통 우리의 상식대로의 순서로 주어진다. 하지만, 그렇지 않은 경우도 반드시 Custom한 TC로 따져봐야한다!</p>
<p>파이썬에선 다중 정렬(하나를 기준으로 정렬할때, 하나가 같은 경우라면 그 다음 key를 지정)하는 방법은 lambda x의 반환값을 튜플로한다! ex. (k1, k2, -k3)...</p>
<h2 id="배경">배경</h2>
<p><a href="https://www.acmicpc.net/problem/1931">회의실 배정</a><br>해당 문제에서 end_time의 오름차순으로 정렬을 하고, start_time을 k2로 정렬을 명시하지 않고 풀었다.<br>따라서 이땐 <strong>[(0, 5), (5, 5), (4, 5), (2, 5), (3, 5)]</strong> 를 input 했을 땐 기댓값 3(이유 : (3, 5) -&gt; (5, 5)가 가능해서 2번으로 침)과 달리 결과는 2로 나온다.  </p>
<p>아래는 수정 전 코드</p>
<pre><code class="language-python">n = int(input())
times = []
for _ in range(n):
    times.append(tuple(map(int, input().split())))
times.sort(key=lambda x: x[1]) #&lt;- 문제!
#print(times)
answer = 0
iter_time = 0
for start_time, end_time in times:
    if start_time &lt; iter_time:
        continue
    iter_time = end_time
    answer+=1
print(answer)</code></pre>
<p>아래는 수정 후 코드</p>
<pre><code class="language-python">n = int(input())
times = []
for _ in range(n):
    times.append(tuple(map(int, input().split())))
times.sort(key=lambda x: (x[1], x[0]))
#print(times)
answer = 0
iter_time = 0
for start_time, end_time in times:
    if start_time &lt; iter_time:
        continue
    iter_time = end_time
    answer+=1
print(answer)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 그래프 관련 실수모음]]></title>
            <link>https://velog.io/@liini_coder/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B7%B8%EB%9E%98%ED%94%84-%EA%B4%80%EB%A0%A8-%EC%8B%A4%EC%88%98%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@liini_coder/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B7%B8%EB%9E%98%ED%94%84-%EA%B4%80%EB%A0%A8-%EC%8B%A4%EC%88%98%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Sun, 25 Feb 2024 07:20:10 GMT</pubDate>
            <description><![CDATA[<h2 id="실수1-s-v-w주어졌을-때-v-s고려-안함">실수1. (s, v, w)주어졌을 때 v-&gt;s고려 안함</h2>
<h3 id="배경">배경</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/72413">프로그래머스 : 합승 택시 요금</a>
<code>fares</code>로 (시작노드, 도착노드, 가중치)의 리스트가 들어올때(ex. fares:     [[4, 1, 10], [3, 5, 24], [5, 6, 2], [3, 1, 41], [5, 1, 24], [4, 6, 50], [2, 4, 66], [2, 3, 22], [1, 6, 25]]), 이렇게 그래프를 만들었다.
<img src="https://velog.velcdn.com/images/liini_coder/post/720321e8-ce90-4ea0-8727-57611e67766d/image.png" alt="">
그러더니 <code>KeyError</code>가 난다...</p>
<h4 id="뭐가-문제일까">뭐가 문제일까?</h4>
<h3 id="풀이">풀이</h3>
<p>해당 문제는 <code>fares</code>가 무방향그래프라서 s-&gt;v 뿐만아니라 v-&gt;s도 고려해야하는데, 위 코드는 s-&gt;v만을 신경써서 그렇다.</p>
<h4 id="따라서-그래프가-무방향인지-방향인지-꼭-먼저-따지는-습관을-가져야한다">따라서 그래프가 무방향인지 방향인지 꼭 먼저 따지는 습관을 가져야한다.</h4>
<p>아래는 고친 코드의 예시이다.
<img src="https://velog.velcdn.com/images/liini_coder/post/e09cd1fc-90be-4a89-84d0-ca83d0d027de/image.png" alt="">
<img src="https://velog.velcdn.com/images/liini_coder/post/e95ee105-0c44-46e8-a09b-054a3caf912e/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 딕셔너리 정렬]]></title>
            <link>https://velog.io/@liini_coder/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@liini_coder/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Sun, 25 Feb 2024 05:41:11 GMT</pubDate>
            <description><![CDATA[<h3 id="딕셔너리-순서-정책-하지만">딕셔너리 순서 정책... 하지만</h3>
<p>파이썬 3.6부턴 dict가 순서를 보장한다고 하나, 모든 방식에서의 순서가 보장되는 것은 아니다.
<strong>그래서 dict는 순서를 보장하지 않는다고 상정해서 코딩하자!</strong></p>
<p>그렇다면 dict의 원소들을 정렬하려면 어떻게 할까?</p>
<h3 id="딕셔너리-정렬-방법">딕셔너리 정렬 방법</h3>
<p>딕셔너리 자체 내부에서 정렬할 생각하지말고, sorted함수를 이용해 튜플원소인 리스트를 반환하여 이를 사용하자</p>
<pre><code class="language-python">#key기준 오름차순 정렬
sorted( dic.items() )</code></pre>
<p>sorted 함수에 정렬 대상을 넣어야하는데, listable한 객체를 넣어야한다. dic의 원소를 listable한 객체로 래핑해주는 <code>items()</code>함수를 쓰면된다.</p>
<p>만약, 내림차순을 원해서 <code>key=lambda</code>에 반환에 -를 붙이면 가능할때도 있지만 아래 그림과 같은 에러가 날수 있다.
<img src="https://velog.velcdn.com/images/liini_coder/post/c3a45209-d11b-41c2-ae65-7071836b1a01/image.png" alt=""></p>
<h3 id="딕셔너리의-value가-객체이고-객체내부의-데이터기준으로-딕셔너리-정렬하고-싶으면">딕셔너리의 value가 객체이고, 객체내부의 데이터기준으로 딕셔너리 정렬하고 싶으면?</h3>
<p>위 그림 참고하자.</p>
<h3 id="데이터를-내림차순-정렬하고싶으면-reverse옵션을-활용하자">데이터를 내림차순 정렬하고싶으면 reverse옵션을 활용하자!</h3>
<p>웬만하면 sort, sorted의 옵션인 <code>reverse=True</code> 를 사용하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA["|a|b|c|".split("|")의 결과는?]]></title>
            <link>https://velog.io/@liini_coder/abc.split%EC%9D%98-%EA%B2%B0%EA%B3%BC%EB%8A%94</link>
            <guid>https://velog.io/@liini_coder/abc.split%EC%9D%98-%EA%B2%B0%EA%B3%BC%EB%8A%94</guid>
            <pubDate>Thu, 15 Feb 2024 18:45:54 GMT</pubDate>
            <description><![CDATA[<h3 id="a-b-c가-아니다">[&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]가 아니다!!!!</h3>
<p>정답은 <code>[&quot;&quot;,&quot;a&quot;,&quot;b&quot;,&quot;c&quot;,&quot;&quot;]</code>이다.
구분자를 통해 쪼갠다고 생각하면, 맨 양 끝에는 비어있는 문자열도 문자열이다. 그래서 len이 3이 아니라 5다.
이는 TS,JS에서도 동일하다!
<img src="https://velog.velcdn.com/images/liini_coder/post/cdbe2fba-88ec-4847-90dd-3f539b128759/image.png" alt="">
<img src="https://velog.velcdn.com/images/liini_coder/post/d97f0c3e-bcc0-46da-beba-bc8707293408/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TS+Electron+React 세팅법]]></title>
            <link>https://velog.io/@liini_coder/TSElectronReact-%EC%84%B8%ED%8C%85%EB%B2%95</link>
            <guid>https://velog.io/@liini_coder/TSElectronReact-%EC%84%B8%ED%8C%85%EB%B2%95</guid>
            <pubDate>Tue, 13 Feb 2024 15:36:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/liini_coder/post/2820c294-f8e5-4bf2-ad4c-60829ee0dbb7/image.png" alt=""></p>
<h2 id="방법">방법</h2>
<h3 id="electron-forge이용"><a href="https://www.electronforge.io/guides/framework-integration/react-with-typescript">Electron-Forge</a>이용</h3>
<ol>
<li><p><code>$npm init electron-app@latest &lt;프로젝트명(새폴더생성됨)&gt; -- --template=webpack-typescript</code>
를 치면 webpack이 설정된 ts전용 일렉트론까지 완료됨.</p>
</li>
<li><p><code>tsconfig.json</code>파일에 <code>compilerOptions</code>섹션에 <code>&quot;jsx&quot;: &quot;react-jsx&quot;</code> 를 추가</p>
</li>
<li><p>아래 종속성 추가</p>
<pre><code class="language-bash"> npm install --save react react-dom
 npm install --save-dev @types/react @types/react-dom</code></pre>
</li>
<li><p><code>src/app.tsx</code>에 다음코드로 확인</p>
<pre><code class="language-typescript"> import { createRoot } from &#39;react-dom/client&#39;;

 const root = createRoot(document.body);
 root.render(&lt;h2&gt;Hello from React!&lt;/h2&gt;);</code></pre>
<p> <code>src/renderer.ts</code>에 다음코드로 확인</p>
<pre><code class="language-typescript"> // Add this to the end of the existing file
 import &#39;./app&#39;;</code></pre>
</li>
</ol>
<p><a href="https://www.electronforge.io/">Electron-Forge</a>라는 일렉트론을 간단하게 배포 및 실행을 할 수 있게 하는 all-in-one-tool이다.</p>
<h2 id="깨달은점">깨달은점</h2>
<ul>
<li>webpack으로 인해 ts를 js로 번역한 결과를 파일로 저장하지 않고 핫리로딩까지 가능하다. 그러한 설정을 한번에 해줌</li>
<li>ipcRenderer 모듈을 tsx파일에서 사용할때는 반드시 window의 require를 쓴<br><code>const { ipcRenderer } = window.require(&quot;electron&quot;);</code><br>로 해야한다. <del><code>const { ipcRenderer } = require(&quot;electron&quot;);</code></del> 나 <del><code>import {ipcRenderer} from &quot;electron&quot;</code></del>은 안된다.</li>
<li>ipcMain부분에서 <code>console.log</code>를 하면 터미널에 출력, ipcRenderer부분에서 <code>console.log</code>를 하면 일렉트론어플리케이션 개발자도구 콘솔에 출력된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[try except를 쓰면 느릴까]]></title>
            <link>https://velog.io/@liini_coder/try-except%EB%A5%BC-%EC%93%B0%EB%A9%B4-%EB%8A%90%EB%A6%B4%EA%B9%8C</link>
            <guid>https://velog.io/@liini_coder/try-except%EB%A5%BC-%EC%93%B0%EB%A9%B4-%EB%8A%90%EB%A6%B4%EA%B9%8C</guid>
            <pubDate>Sat, 03 Feb 2024 17:57:24 GMT</pubDate>
            <description><![CDATA[<h1 id="문제제기">문제제기</h1>
<blockquote>
<p>[상황]빈리스트부터 시작해 리스트의 맨 마지막을 참고하고 조건이 맞으면 리스트에 append하는 로직을 짤때
[문제]맨 처음에 빈리스트인데 [-1]로 참조하면 에러나니까, for문에</p>
<ol>
<li><code>if len(arr) == 0</code>를 추가할까</li>
<li><code>try: except</code>문으로 에러를 처리할까
이 둘중에 뭐가더 빠를까?</li>
</ol>
</blockquote>
<h1 id="코테문제">코테문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12906">같은 숫자는 싫어</a></p>
<h1 id="1번-if문-추가-코드">1번 if문 추가 코드</h1>
<pre><code class="language-python">def solution(arr):
    answer = [arr[0]]
    for iv in arr:
        if len(arr) == 0:
            answer.append(iv)
            continue
        if answer[-1] != iv:
            answer.append(iv)
    return answer</code></pre>
<h1 id="2번--try-except문-추가-코드">2번  try except문 추가 코드</h1>
<pre><code class="language-python">def solution(arr):
    answer = [arr[0]]
    for iv in arr:
        try:
            if answer[-1] != iv:
                answer.append(iv)
        except:
            answer.append(iv)

    return answer</code></pre>
<h1 id="실행시간-비교">실행시간 비교</h1>
<p><img src="https://velog.velcdn.com/images/liini_coder/post/7bad01e6-f769-4f1e-9903-01c741907008/image.png" alt="">
<img src="https://velog.velcdn.com/images/liini_coder/post/e06ed5ad-0d26-487b-8fdc-508bb0a783a4/image.png" alt=""></p>
<h1 id="결론">결론</h1>
<h3 id="try문이-if문보다-빠르다단-indexerror시에만">try문이 if문보다 빠르다!(단, indexError시에만)</h3>
<p>하지만, 이는 index Error에만 검증된것이다. 만약 오류가 나기까지 엄청나게 많은 스택콜을 가진 함수를 쓴다면? if가 더 빠를 것이다.
그러니까 앞으로 코테에서 index참조를 굳이 if문으로 빼지말고 try except를 이용해서 모든케이스가 되도록 범용적으로 설계하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[약수 빨리 구하기]]></title>
            <link>https://velog.io/@liini_coder/%EC%95%BD%EC%88%98-%EB%B9%A8%EB%A6%AC-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@liini_coder/%EC%95%BD%EC%88%98-%EB%B9%A8%EB%A6%AC-%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 03 Feb 2024 17:13:55 GMT</pubDate>
            <description><![CDATA[<h1 id="기존-약수구하기">기존 약수구하기</h1>
<p>N의 약수를 구한다면 단순하게</p>
<pre><code class="language-python">for i in range(1, n):
    if n%i == 0:
        ~~~</code></pre>
<p>이런식으로 구할 수 있겠지만 <code>O(N)</code>의 시간복잡도이다. N이 엄청 크다면 그만큼 느릴것이다.</p>
<h1 id="수정된-약수-구하기">수정된 약수 구하기</h1>
<blockquote>
<ol>
<li><code>N의 제곱근</code>구하기</li>
<li>range(1, <code>N의 제곱근</code>)으로 <code>제곱근까지의 약수들</code>구하기</li>
<li><code>제곱근까지의 약수들</code>을 순회참조해서 N으로 나눈 몫을 약수에 추가</li>
</ol>
</blockquote>
<p>코드로 보면 이렇다.</p>
<pre><code class="language-python">from math import *
#n의 약수들을 set으로 반환
def getDivisor(n:int)-&gt;set:
    root = isqrt(n) #math의 반환형int인 함수로, 제곱근의 floor를 반환
    divisors = set() #만약 리스트로 아래 알고리즘을 실행하면 약수에 제곱근 2개가 똑같이 들어간다. 중복을 피하기 위해 set 사용
    for i in range(1, root+1):
        if n%i == 0:
            divisors.add(i)
    for divisor in divisors.copy():#순회탐색알 제곱근까지의 약수들과 추가당하는 제곱근까지의 약수들을 서로 구분하기 위해 copy사용
        divisors.add(n//divisor)
    return divisors</code></pre>
<p>이는 <code>O(루트N)</code>의 시간복잡도를 가지니 더욱 빠르다.</p>
<h1 id="참고문제">참고문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/77884">프로그래머스 : 약수의 개수 구하기</a>
<img src="https://velog.velcdn.com/images/liini_coder/post/299480cb-9fb6-4505-83cb-1ed191d2b9be/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hello, velog!]]></title>
            <link>https://velog.io/@liini_coder/Hello-velog</link>
            <guid>https://velog.io/@liini_coder/Hello-velog</guid>
            <pubDate>Sat, 03 Feb 2024 15:54:21 GMT</pubDate>
            <description><![CDATA[<h1 id="블로그-시작-계기">블로그 시작 계기</h1>
<p>필자는 현재 대학교 4학년으로 졸업예정이다. 대학교 초반에는 그저 <strong>우물안 개구리</strong>였다. 학부과정만 잘 따라온다면 취업은 금방된다는 안일한 생각을 가지고 있었다.</p>
<h2 id="대학교-3학년">대학교 3학년</h2>
<p>그러다 문득 외부활동이란 것을 해보고 싶어서, 3학년엔 <em>멋쟁이사자처럼</em> 이라는 전국연합 동아리에 들어갔다.
 이 동아리는 기획디자인, 백엔드, 프론트엔드로 인원을 뽑고 팀을 여러번 구성하여 그때마다 아이디어 도출 및 서비스 개발까지 단기간에 SDLC(Software Develope Life Cycle)를 해볼 수 있는 좋은 기회가 되었다.</p>
<p> 이곳을 통해 얻은 점은</p>
<blockquote>
<p>&quot;아 내가 우물안 개구리 였구나&quot;</p>
</blockquote>
<p> 라는 것을 깨달았다는 것이다. 웹서비스라는 하나의 서비스에도 이렇게 체계적으로 분업이 되어있고, 각 세션마다 엄청나게 많은 기술들이 있다는 것을 연달아 깨닫고, 학부과정 이외의 다른 무언가가 필요하단 것을 절실하게 느꼈다.</p>
<p> 그렇지만 정작 내가 향후 30년동안 어디방향의 개발자가 될지는 정하지 못하였다. 그러다 문득 친구가 정보보호대학원을 가서 나에게 정보보안을 어필하는데, 상당히 멋있어 보였다. 애초에 어렸을 때 부터 화이트해커라는 직종이 굉장히 멋있어 보였던것도 있어서 나는 보안을 이때 공부를 시작해보았다.</p>
<p> 해킹을 공부하게 잘 나와있는 <a href="https://dreamhack.io/">드림핵</a>의 도움을 빌려서 웹해킹 여러 CTF를 풀어보니 학교에서 배운  CS지식을 다시 공부하게 해주는데에 큰 도움을 주었다. 그래서 이것을 살려보고자 처음으로 <strong>부트캠프</strong>라는 대외활동의 한 종류를 접하게 되었고, <a href="https://www.kitribob.kr/">BestOfBest</a>라는 정보보안 인재양성 프로그램에 지원을 하기로 마음먹었다.</p>
<h2 id="대학교-4학년">대학교 4학년</h2>
<p> BestOfBest(통칭 BOB)는 상당한 입시절차를 거친다.</p>
<blockquote>
<p>자소서 -&gt; 인적성, 필기 -&gt; 면접</p>
</blockquote>
<p> 솔직히 부트캠프를 처음해보는 입장에서 이정도까지 필요한가? 라는 생각은 가졌지만, BOB에서 정말 보안을 제대로 배울 수 있다고 생각해 중간고사 시즌이 겹치는 와중에도 열심히 준비했었다. 이때 밤도 많이새곤했다...
 그래도 경쟁률 10:1을 자랑하던 BOB 12기 보안제품개발에 최종합격을 하여서 이때 당시 정말 기뻤다.
  지방에서 짐을 부랴부랴 싸들고 7월부터 1단계 교육을 받았는데, 솔직히 보안에 대한 지식이 없던 상태로 갔어서 그런지 거의 두들겨 맞았다. 멘토님들의 수업내용은 상당한 도메인을 요구하고 있었고(그런데 솔직히 내가 너무 보안에 대해 무지한 탓이 큰것같다) 주위의 멘티들도 진짜 Geek한 해커들이 많았다. 그냥 컴퓨터학과인 나와 정보보호학과 친구들은 서로 도메인부터가 다르단걸 그 당시에 깨달았다.
  따라서 BOB에서 정말 많은 것을 배웠지만, 오히려 내가 30년동안 보안만을 할 수 있을까라는 의문감이 커져갔다.</p>
<h2 id="진로-선택">진로 선택</h2>
<p>  이렇게 대학3학년, 대학4학년에 걸쳐 개발과 보안을 모두 배워본 결과, 과연 내가 무엇을 더 좋아하는지 고민을 하였고, 정말 스스로에게 많이 되물어본것 같다.
   결과 역시 <code>개발</code>인것 같다.(<del>멘토님 죄송해요...</del>) 이 글을 작성하는 지금은 현재 BOB의 Top30 경연교육을 받고 있지만, 안타깝게도 남은 교육은 보안을 키워나간다기보다는 보안도메인을 지닌 개발을 하기로 마음억었다.</p>
<h2 id="그래서-이-블로그-왜하는데">그래서 이 블로그 왜하는데</h2>
<p>솔직히 tistory로 대학 3학년때 잠시 끄적여는 보았는데, 그냥 나만 알아보도록 적은 것도 있고, 내가 공부한 것을 문서화 하는게 너무나도 귀찮았다.
 하지만, 이렇게 하니 2가지 문제가 있었다.</p>
<blockquote>
<ol>
<li>면접자가 내가 어떤사람인지 평가하기 힘듬</li>
<li>내가 공부하면서 깨달은 점을 나중가선 까먹음</li>
</ol>
</blockquote>
<p> 이 문제들을 위해서라도 기존의 tistory에서 New Start하는 기분으로 velog로 포스팅을 하기로 마음먹었다. tistory에서 마크다운으로 블로그를 작성하는 것은 조금 귀찮았기 때문에, 기본지원인 <code>velog</code>를 선택하였다.
 <del>사실 이 게시글도 마크다운을 연습하기 위한 페이지이다.</del></p>
<h1 id="포스팅-계획">포스팅 계획</h1>
<h2 id="룰">룰</h2>
<blockquote>
<ol>
<li>깨달은 점 있으면 바로 카테고리 만들고 블로그 포스팅하기</li>
<li>공부하는 내용을 모두 기록으로 남기기</li>
</ol>
</blockquote>
<h2 id="포스팅-주제">포스팅 주제</h2>
<blockquote>
<ol>
<li>TypeScript를 메인으로 공부를 해서 프론트(React까지), 백엔드(NestJS)를 공부한 내용<ol start="2">
<li>Kotlin으로 Java Spring 공부한 내용</li>
</ol>
</li>
</ol>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>