<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>serious_yeon.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 05 Jun 2023 13:13:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>serious_yeon.log</title>
            <url>https://images.velog.io/images/serious_yeon/profile/87daac07-b034-412a-8a35-f3e464c1531f/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. serious_yeon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/serious_yeon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[해내기] 운영 에러 대응을 위해 GUID(Global Unique ID)를 채번하자]]></title>
            <link>https://velog.io/@serious_yeon/%ED%95%B4%EB%82%B4%EA%B8%B0-%EC%9A%B4%EC%98%81%EC%9D%84-%EC%9C%84%ED%95%B4-GUIDGlobal-Unique-ID%EB%A5%BC-%EC%B1%84%EB%B2%88%ED%95%98%EC%9E%90</link>
            <guid>https://velog.io/@serious_yeon/%ED%95%B4%EB%82%B4%EA%B8%B0-%EC%9A%B4%EC%98%81%EC%9D%84-%EC%9C%84%ED%95%B4-GUIDGlobal-Unique-ID%EB%A5%BC-%EC%B1%84%EB%B2%88%ED%95%98%EC%9E%90</guid>
            <pubDate>Mon, 05 Jun 2023 13:13:22 GMT</pubDate>
            <description><![CDATA[<pre><code>- MSA 환경에서 운영한다는 것
- GUID는 어떻게 도움이 되는가
- GUID는 어디서 채번해야 하는가
- GUID 채번 규칙은 어떠해야 하는가
- GUID 채번은 어떻게 해야하는가
- GUID를 잘 활용하기 위해 뒷받침되어야 하는 것들</code></pre><h3 id="msa-환경에서의-운영">MSA 환경에서의 운영</h3>
<p>IT 서비스가 커지면 &gt; 서버가 쪼개지고 &gt; 조직이 생겨난다. 조직 간에는 알력 다툼이 있고, 조직원은 웬만하면 일하기 싫어한다. 이러한 조직 + MSA 환경에서 에러가 터진다면 어떨까? 웬만하면 에러를 해결하려 들지 않고 책임을 떠넘기려 한다. 
그래서 조직 간에 협업을 원활하게 하고, 에러 발생 지점을 쉽게 발견하도록 IT 시스템을 설계해야 한다. 당행에서는 대표적으로 (1)메타데이터 시스템, (2) 오류메시지 시스템, (3) GUID (4) 로깅 시스템을 활용하고 있다.</p>
<h3 id="guid란">GUID란</h3>
<p>request 별로 Global하고 Unique한 ID를 채번하는 것이다. MSA 환경에서 request는 A 서버 &gt; B 서버 &gt; C 서버를 거쳐 처리된다. GUID는 request의 시작점인 A 서버에서 채번되며, B서버와 C서버에서도 동일하게 사용된다.</p>
<p>GUID를 활용하기 위해서는 여러 전제 조건이 필요하다. 먼저 A서버, B서버, C서버가 모두 동일한 GUID를 사용해야 한다. 다음으로 GUID를 전제로 로깅하는 시스템이 구축되어 있어야 한다.</p>
<p>에러가 발생하면 아래와 같이 대응한다. 먼저 A서버에서 로깅시스템을 뒤져서 문제가 되는 request의 GUID를 찾는다. 해당 request의 로직을 확인한 뒤, A 서버 문제가 아님을 확인한다. 그러면 B 서버에 GUID를 알려준다. 
B서버는 로깅시스템을 뒤져서 문제가 되는 reuqest를 찾는다. 해당 request의 로직을 확인한 뒤, B 서버 문제가 아님을 확인한다. 그러면 C 서버에 GUID를 알려준다.</p>
<h3 id="guid-채번은-어떻게-해야하는가">GUID 채번은 어떻게 해야하는가</h3>
<p>GUID 채번에 관한 문제는 기술적인 관점과, 업무적인 관점 두 가지 측면에서 볼 수 있다.</p>
<h4 id="유일한-id를-빠르게-채번해야-한다">유일한 id를 빠르게 채번해야 한다</h4>
<p>기술적으로는 (1)유일한 id를 (2)빠르게 채번해야 한다. 이와 관려해 겪은 문제들이다.</p>
<ul>
<li>유일한 id를 시분초를 활용해 was에서 채번해봤는데, 생각보다 중복 id가 많이 나왔다. 이 문제는 oracle의 sequence 기능을 활용해 해결할 수 있었다.</li>
<li>빠르게 채번하는 것도 중요했다. was 인스턴스 개수를 늘려 처리량을 늘리더라도, 채번 시스템에서 막혀서 request가 밀리곤 했다. </li>
</ul>
<h4 id="시스템을-반영한다-드러내지-않는다">시스템을 반영한다, 드러내지 않는다</h4>
<p>GUID에 업무 성격이 드러난다면, guid만 보고도 원인을 대략적으로 파악할 수 있다. 동시에 보안 취약점이 되기도 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[해내기] 인터페이스와 async 통신을 활용해 응답 시간을 단축하자]]></title>
            <link>https://velog.io/@serious_yeon/%ED%95%B4%EB%82%B4%EA%B8%B0-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EC%99%80-async</link>
            <guid>https://velog.io/@serious_yeon/%ED%95%B4%EB%82%B4%EA%B8%B0-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EC%99%80-async</guid>
            <pubDate>Sun, 02 Apr 2023 02:45:35 GMT</pubDate>
            <description><![CDATA[<pre><code>tomcat queue
인터페이스
async</code></pre><h3 id="tomcat-queue">tomcat queue</h3>
<p>was로 들어온 request는 톰캣 큐에 쌓인다. 그런데 톰캣 queue의 캐파보다 더 많은 request가 들어온다면? was가 터질 수 있다. </p>
<h3 id="인터페이스의-필요성">인터페이스의 필요성</h3>
<p>이러한 문제를 방지하기 위해 was와 request queue를 분리할 필요가 있다. 더하여, 인터페이스에 대한 요건이 추가될 수 있다. 통신 방식을 async와 sync로 정한다든지, dto 간 맵핑을 수행해준다든지 하는 것들이다.
인터페이스는 새로운 개념이 아니다. 예전부터 사용해오던 것이고. kafka는 오픈소스로 개발해 유명해진 인터페이스이다. 금융권에서는 eai, mca, fep 등의 인터페이스 제품을 써왔다.</p>
<h3 id="async">async</h3>
<p>B 서버를 호출하는 데 response가 오기까지 10초가 걸렸다. 이거 때문에 우리 서버가 맨날 터져서.. async로 호출하는 것을 고려했었다. javascript나 scala에서는 손쉽게 사용했었으니까 쉬울 줄 알았다. 그런데 java에서 async를 구현한다는 게 생각보다 복잡했다!!</p>
<p><img src="https://velog.velcdn.com/images/serious_yeon/post/71207269-58a7-4c91-8bd8-2a97aee9be78/image.png" alt=""></p>
<p><a href="https://chanyeong.com/blog/post/44">javascript 예시</a>를 살펴보자. async를 구현하려면 비동기 동작을 쌓아두는 task queue와 이 큐를 관리하는 event loop가 필요하다. 이거를 언어나 프레임워크에서 지원해준다면 땡큐지만, 그렇지 않다면 서버 단에서 구현해야 한다 ㅎㅎ</p>
<p>결국은 사내 인터페이스 시스템을 접하고, 인터페이스에서 지원하는 async를 이용해서 문제를 해결했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[해내기] MSA 환경에서는 트랜잭션 롤백을 고려해 서비스를 구현해야 한다]]></title>
            <link>https://velog.io/@serious_yeon/%ED%95%B4%EB%82%B4%EA%B8%B0-MSA-%ED%99%98%EA%B2%BD%EA%B3%BC-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@serious_yeon/%ED%95%B4%EB%82%B4%EA%B8%B0-MSA-%ED%99%98%EA%B2%BD%EA%B3%BC-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Sun, 02 Apr 2023 01:49:22 GMT</pubDate>
            <description><![CDATA[<pre><code>MSA라는 운영 정책
트랜잭션 롤백</code></pre><h3 id="msa라는-운영정책">MSA라는 운영정책</h3>
<p>운영 정책이라는 건 유행이 있다. 예전에는 통합이 유행이었고, 요즘에는 잘게 쪼개는 게 유행이다. 잘게 쪼갰을 때 장점은 프로그램 수정 시 영향도를 분리하고, 발빠르게 수정이 가능하다는 점이다. 단점은 변경점이 많고, 관리 포인트가 늘어난다. 그래서 쪼갤 때 어디까지 쪼갤 것인지에 대한 고민이 필요하다.</p>
<h3 id="트랜잭션">트랜잭션</h3>
<p>트랜잭션은 <code>DB 처리의 단위</code>이다. 쿼리문 하나를 의미하는 것은 아니고, 업무 단위로 트랜잭션을 관리한다. 트랜잭션은 <a href="https://hoon93.tistory.com/24">atomic</a>해야 한다. 이를 위해 <a href="https://hoon93.tistory.com/24">커밋, 롤백, 세이브포인트</a> 기능이 있다. </p>
<h3 id="msa-환경과-트랜잭션-롤백">MSA 환경과 트랜잭션 롤백</h3>
<p>MSA 환경에서는 하나의 기능 안에서, 여러 서버를 호출한다. 예를 들어 고객 정보를 받아서 예금을 신규하는 경우 1번의 조회, 2번의 처리가 필요하다.</p>
<pre><code>1) 당행에 존재하는 고객인지 조회한다.
2) 당행에 존재하지 않는다면 고객을 생성한다.
3) 고객의 예금을 생성한다.</code></pre><p>만약 2번을 완료한 후에 3번을 처리하는 과정에서 오류가 났다면 어떻게 해야할까? 어디까지 롤백해야 할까? 1번 이전 시점으로 돌아가는 것은, 우리 서버 입장에서는 깔끔하다. 하지만 2번을 처리한 서버에게 어떻게 alert할 것인가? 오히려 alert해주는 과정에서 더 많은 오류가 발생할 수 있다. </p>
<p>내가 내린 결론은, 이러한 기술적 한계를 인지하고 비즈니스를 이해한 채로 우리 서버의 기능을 설계해야 한다는 것이었다. <code>고객이 당행에 존재하지 않는다면</code> 분기문을 넣는 것이 한 방법이겠다.</p>
<p><img src="https://velog.velcdn.com/images/serious_yeon/post/885fd8ed-70eb-4c69-91aa-26bc51a76812/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이펙티브자바] 52강. overloading은 신중히 사용하라]]></title>
            <link>https://velog.io/@serious_yeon/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%EC%9E%90%EB%B0%94-52%EA%B0%95.-overloading%EC%9D%80-%EC%8B%A0%EC%A4%91%ED%9E%88-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@serious_yeon/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%EC%9E%90%EB%B0%94-52%EA%B0%95.-overloading%EC%9D%80-%EC%8B%A0%EC%A4%91%ED%9E%88-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sun, 26 Mar 2023 00:27:28 GMT</pubDate>
            <description><![CDATA[<pre><code>- overloading
- compile time
- 문제상황
- 해결책</code></pre><h3 id="오버로딩">오버로딩</h3>
<ul>
<li>overriding (재정의)
  부모의 메소드를 재정의한다. 
  런타임에 선택된다.</li>
<li>overloading (다중정의)
  이름은 같지만 매개변수 유형이 다르거나, 이름은 같지만 매개변수 개수가 다른 메소드이다. 매개변수 개수가 같은 경우 어떤 메소드가 호출될지 헷갈린다. 
  컴파일 타임에 선택된다.</li>
</ul>
<h3 id="컴파일타임">컴파일타임</h3>
<ul>
<li>컴파일타임 
  소스코드를 기계어로 번역하는 과정
  컴파일 오류는 ; 누락, 괄호 누락 등이 있다. ide의 도움으로 발견하기 쉽다</li>
<li>런타임
  실행 과정
  <a href="https://junboom.tistory.com/39">런타임 오류</a>는 NullPointException, InfiniteLoop 등이 있다. 에러 발생 시 역추적해야 한다</li>
</ul>
<h3 id="문제상황">문제상황</h3>
<ul>
<li>오버로딩한 메서드는 헷갈리기 쉽다.</li>
<li>원인
  컴파일 타임에 호출될 메서드가 정해진다.
  특히 매개변수 개수가 같다면 더 헷갈린다.</li>
<li>해결책
  매개변수 개수가 같으면 오버로딩을 지양한다.
  오버라이딩을 쓴다.
  런타임에 호출할 메서드가 특정되도록 코드를 작성한다.
  추가 메서드를 정의하고, 이름을 다르게 짓는다.
  매개변수 개수가 같다면, 유형이 확실하게 다르도록 코드를 작성한다.</li>
</ul>
<pre><code class="language-java">public class CollectionClassifier {
    public static String classify(Set&lt;?&gt; s) {
        return &quot;집합&quot;;
    }

    public static String classify(Set&lt;?&gt; s) {
        return &quot;리스트&quot;;
    }

    public static String classify(Set&lt;?&gt; s) {
        return &quot;그 외&quot;;
    }

    public static void main(String[] args) {
        Collection&lt;?&gt;[] collections = {
            new HashSet&lt;String&gt;(),
            new ArrayList&lt;BigInteger&gt;(),
            new HashMap&lt;String, String&gt;().values()
        };

        // 컴파일타임에 정해지므로 &quot;그 외&quot;만 세번 출력된다.
        for (Collection&lt;?&gt; c : collections)
            System.our.println(classify(c));
    }
}</code></pre>
<ul>
<li><p>오버라이딩을 쓴다.</p>
<pre><code class="language-java">public class Overriding{
  public static void main(String[] args) {
      List&lt;Wine&gt; wineList = List.of(
          new Wine(), new SparkingWine(), new Champagne());

      for (Wine wine : wineList)
          System.out.println(wine.name());
  }
}</code></pre>
</li>
<li><p>런타임에 호출할 메서드가 특정되도록 코드를 작성한다.</p>
<pre><code class="language-java">public static String classify(Collection&lt;?&gt; c) {
  return c instanceof Set ? &quot;집합&quot; :
          c instanceof List ? &quot;리스트&quot; : &quot;그 외&quot;;
}</code></pre>
</li>
<li><p>헷갈리지 않도록 추가 메서드를 정의하고, 이거는 이름을 다르게 짓는다.</p>
<pre><code class="language-java">ObjectOutputStream의 
writeBoolean, writeInt, writeLong
readBoolean, readInt, readLong
</code></pre>
</li>
</ul>
<p>// 생성자는 같지만 추가 메소드 덕분에 헷갈리지 않는다</p>
<pre><code>
- 매개변수 개수가 같다면, 유형이 확실하게 다르도록 코드를 작성한다.
    Object와 int는 형변환 가능하다. 그러므로 헷갈리기 쉽다.
    String과 Throwable은 다르다.
```java
public class SetList {
    public static void main(String[] args) {
        Set&lt;Integer&gt; set = new TreeSet&lt;&gt;();
        List&lt;Integer&gt; list = new ArrayList&lt;&gt;();

        for (int i=-3; i&lt;3; i++) {
            set.add(i);
            list.add(i);
        }

        // 잘못된 예. index와 헷갈린다
        for (int i=0; i&lt;3; i++) {
            set.remove(i);
            list.remove(i);
        }

        // 올바른 예
        for (int i=0; i&lt;3; i++) {
            set.remove(i);
            list.remove((Integer) i);
        }

        System.out.println(set + &quot; &quot; + list);
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[이펙티브자바] 50강. 가변객체의 방어적 복사본을 만들라]]></title>
            <link>https://velog.io/@serious_yeon/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%EC%9E%90%EB%B0%94-%EC%A0%81%EC%8B%9C%EC%97%90-%EB%B0%A9%EC%96%B4%EC%A0%81-%EB%B3%B5%EC%82%AC%EB%B3%B8%EC%9D%84-%EB%A7%8C%EB%93%A4%EB%9D%BC</link>
            <guid>https://velog.io/@serious_yeon/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%EC%9E%90%EB%B0%94-%EC%A0%81%EC%8B%9C%EC%97%90-%EB%B0%A9%EC%96%B4%EC%A0%81-%EB%B3%B5%EC%82%AC%EB%B3%B8%EC%9D%84-%EB%A7%8C%EB%93%A4%EB%9D%BC</guid>
            <pubDate>Sun, 19 Mar 2023 02:48:43 GMT</pubDate>
            <description><![CDATA[<pre><code>목차
- 가변객체, 불변객체
- 클라이언트에서, 객체의 허락 없이, 외부에서 내부를 수정함 
- 방어적 복사</code></pre><h3 id="가변객체-불변객체"><a href="https://steady-coding.tistory.com/559">가변객체, 불변객체</a></h3>
<ul>
<li><p>가변 객체
내부 상태가 변경 가능한 객체
멀티 스레드 환경에서 사용하려면 별도의 동기화 처리가 필요함
ex) ArrayList, HashMap, StringBuilder, StringBuffer</p>
</li>
<li><p>불변 객체
내부 상태를 변경할 수 없는 객체
멀티 스레드 환경에서도 안전하게 사용 가능
ex) String</p>
</li>
</ul>
<h3 id="목표">목표</h3>
<ul>
<li><p>문제상황
클라이언트에서, 객체의 허락 없이, 외부에서 내부를 수정함</p>
</li>
<li><p>문제해결
방법 1) 가변객체를 수정할 수 없도록 방어적 복사를 한다
방법 2) 수정 가능한 가변객체에 대해 미리 알려주는 문서화를 한다</p>
</li>
</ul>
<h3 id="방어적-복사">방어적 복사</h3>
<ul>
<li><p>생성자에서 매개변수의 유효성을 검사하기 전에 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사해야 한다.</p>
</li>
<li><p>clone을 사용해서는 안 된다. 매개변수에 clone 메서드가 없을 수도 있기 때문이다.</p>
<pre><code class="language-java">public final class Period {
  private final Date start;
  private final Date end;

  // 잘못된 예
  public Period(Date start, Date end) {
      if (start.compareTo(end) &gt; 0 )
          throw new IllegalArgumentException(start + &quot;가 &quot; + end + &quot; 보다 늦다.&quot;);
      this.start = start;
      this.end = end;
  }

  // 옳은 예
  public Period(Date start, Date end) {
      this.start = new Date(start.getTime());
      this.end = new Date(end.getTime());
      if (start.compareTo(end) &gt; 0 )
          throw new IllegalArgumentException(start + &quot;가 &quot; + end + &quot; 보다 늦다.&quot;);
  }
}
</code></pre>
</li>
</ul>
<p>// 왜 잘못되었냐? Date는 가변이라서 바꿀 수가 있다
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78);</p>
<pre><code>
- 접근자에, 방어적 복사본을 반환하도록 한다
```java
public final class Period {
    private final Date start;
    private final Date end;

    // 잘못된 예
    public Date start() {
        return start;
    }
    public Date end() {
        return end;
    }

    // 옳은 예
    public Date start() {
        return new Date(start.getTime());
    }
    public Date end() {
        return new Date(end.getTime());
    }
}

// 왜 잘못되었나? 가변필드에 접근해서 바꿀 수 있음
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78);</code></pre><ul>
<li>방어적 복사의 단점도 있다. 성능이 저하되고, 항상 쓸 수 있는 것도 아니다. 불가피한 경우 문서화를 해둬야 한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이펙티브자바] 48강. 스트림 병렬화는 주의해서 적용하라]]></title>
            <link>https://velog.io/@serious_yeon/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%EC%9E%90%EB%B0%94-48%EA%B0%95.-%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EB%B3%91%EB%A0%AC%ED%99%94%EB%8A%94-%EC%A3%BC%EC%9D%98%ED%95%B4%EC%84%9C-%EC%A0%81%EC%9A%A9%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@serious_yeon/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C%EC%9E%90%EB%B0%94-48%EA%B0%95.-%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EB%B3%91%EB%A0%AC%ED%99%94%EB%8A%94-%EC%A3%BC%EC%9D%98%ED%95%B4%EC%84%9C-%EC%A0%81%EC%9A%A9%ED%95%98%EB%9D%BC</guid>
            <pubDate>Sun, 12 Mar 2023 01:12:39 GMT</pubDate>
            <description><![CDATA[<pre><code>목차
자바의 동시성 프로그래밍이란
스트림이란
스트림 병렬처리란</code></pre><h3 id="자바의-동시성-프로그래밍">자바의 동시성 프로그래밍</h3>
<p><img src="https://velog.velcdn.com/images/serious_yeon/post/585f7c51-7879-422c-9f80-c27223a0edfd/image.png" alt=""></p>
<p><a href="https://seamless.tistory.com/42">동시성 프로그래밍</a>이란, 싱글 프로세서에서 멀티 스레드를 동작시키는 것이다. 스레드를 바꿀 때는 관련 정보를 읽어오느라 시간과 비용이 발생한다(Context Switching). 이걸 최소화하는 게 중요하다.</p>
<p>자바는 동시성 프로그래밍 관련해서 여러 기능을 지원한다. </p>
<pre><code>- 스레드, 동기화, wait/notify
- 포크 조인 패키지
- 스트림 파이프라인의 parallel</code></pre><p>동시성 프로그래밍을 적용한다고 해서 무조건 성능이 좋아지는 것은 아니다. 성능이 좋아질 수 있는 경우를 파악해야 하며, 동시성 프로그래밍을 적용한 후에도 성능 테스트를 수행해서 검사해야 한다.</p>
<h3 id="스트림이란">스트림이란</h3>
<p>스트림 예시를 보면 감이 온다.</p>
<pre><code>Stream.iterate(1L ,i -&gt; i + 1)
      .limit(n)
      .parallel()
      .reduce(0L, Long::sum);</code></pre><ul>
<li>for문으로 접근하던 것을 스트림으로 처리하면, 코드 가독성이 높아진다</li>
<li>stream.parallel()은 병렬 처리를 지원한다. 속도를 높일 수 있다. 대용량 데이터를 처리할 때 좋다</li>
</ul>
<h3 id="스트림-병렬화">스트림 병렬화</h3>
<p>스트림은 병렬화 기능을 지원한다. 앞서 말했듯이 병렬화를 적용한다고 해서 무조건 성능이 좋아지는 것은 아니다. 심지어 결과가 잘못되거나 오동작할 수도 있다(safety failure). 병렬화를 적용하기 전에 이러한 케이스에 대해 잘 알아둬야 한다.</p>
<ul>
<li><p>스트림 병렬화는 (1) 연산을 쪼개서 수행한 후 (2) 합치는 방식으로 진행된다. 그래서 동작 방식이 종단인 연산에 대해서 성능이 좋아진다. 
ex) min, max, count, sum</p>
</li>
<li><p>자료구조 중에 데이터가 메모리에 연속되어 저장된 경우 성능이 좋아진다. (참조 지역성이 뛰어난 자료구조를 써야 한다)
ex) ArrayList, HashMap, HashSet, ConcurrentHashMap
<img src="https://velog.velcdn.com/images/serious_yeon/post/35c87abf-1cec-4636-827b-c9a400151a95/image.png" alt=""></p>
</li>
</ul>
<ul>
<li>병렬화의 결과가 잘못되거나 오동작하는 것을 막기 위해서, 스트림의 reduce 연산에 건네지는 accumulator와 combiner는 결합법칙을 만족하고, 간섭받지 않고, 상태를 갖지 않아야 한다.
ex) foreach(X) forEachOrdered(O)</li>
</ul>
<h3 id="느낀-점">느낀 점</h3>
<ul>
<li>애초에 왜 parallel이 아닌 concurrent로 돌아가는 걸까 하는 근본적인 물음이 들었다. concurrent는 메모리 공유가 용이하다. 이 trade-off가 아닌지~ </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[해내기] 코드값을 관리하자]]></title>
            <link>https://velog.io/@serious_yeon/%ED%95%B4%EB%82%B4%EA%B8%B0-%EC%BD%94%EB%93%9C%EA%B0%92%EC%9D%84-%EA%B4%80%EB%A6%AC%ED%95%98%EC%9E%90</link>
            <guid>https://velog.io/@serious_yeon/%ED%95%B4%EB%82%B4%EA%B8%B0-%EC%BD%94%EB%93%9C%EA%B0%92%EC%9D%84-%EA%B4%80%EB%A6%AC%ED%95%98%EC%9E%90</guid>
            <pubDate>Sat, 04 Mar 2023 16:08:35 GMT</pubDate>
            <description><![CDATA[<pre><code>목차
1. 코드값이란
2. 규모가 작을 때
3. 규모가 클 때</code></pre><h3 id="코드값">코드값</h3>
<p>어플리케이션을 만들다보면 여러 코드값을 사용한다. 코드값의 대표적인 예시는 jwt의 만료시간, error code와 메시지 등이 있다. </p>
<h3 id="규모가-작을-때">규모가 작을 때</h3>
<p>규모가 작을 때는 static 변수에 담거나, enum 값으로 관리했었다.</p>
<p>1) JwtProperties 클래스를 만들고 static 변수에 담았다</p>
<pre><code>public class JwtProperties {
    public static int EXPIRATION_TIME = 864000000; // 10일 (1/1000초)
    public static String TOKEN_PREFIX = &quot;Bearer &quot;;
    public static String HEADER_STRING = &quot;Authorization&quot;;
}</code></pre><p>2) 관계가 있는 경우에는, enum 으로 관리했다</p>
<pre><code>public enum ErrorCode {
    private final String code;
    private final String message;

    NULL(&quot;NO_ERROR&quot;, &quot;에러 없음&quot;),
    UNKNOWN_ERROR(&quot;UNKNOWN_ERROR&quot;, &quot;예기치 못한 오류가 발생하였습니다.&quot;),
    TEAM_NOT_FOUND(&quot;TEAM_NOT_FOUND&quot;, &quot;해당 그룹(팀)을 찾을 수 없습니다.&quot;)
}</code></pre><h3 id="규모가-커질-때">규모가 커질 때</h3>
<p>하지만 코드를 소스파일로 관리하는 것은 여러모로 불편한 점이 있었다. 코드값이 무슨 클래스에 저장되어 있는지 한눈에 파악하기 어려웠다. 그리고 코드값을 수정하고 적용을 하려면 배포를 해야했다 ㅎㄷㄷ</p>
<p>그래서 &quot;코드테이블&quot;을 만들어 관리하기 시작했다. 코드테이블 DDL도 우여곡절이 있었다. </p>
<p>상위코드와 하위코드를 나누고, 하나의 row에는 하나의 code만 담도록 했다. 하지만 이러한 설계는 enum(value1, value2)으로 관리하던 사례를 담기에 어려웠다. 급기야는 value2를 code_desc에 담는 개발자가 나왔다. 이런 개발은 본인만 규칙을 알고 있기 때문에, 다음에 다른 사람이 왔을 때 에러가 나기 십상이다.</p>
<pre><code>1) 상위코드
2) 하위코드_name
3) 하위코드_value
4) 하위코드_desc</code></pre><p>그래서 아래와 같이 DDL을 추가로 적용했다.</p>
<pre><code>5) 하위코드_name_2
6) 하위코드_value_2
7) 하위코드_desc_2
8) 하위코드_name_3
9) 하위코드_value_3
10) 하위코드_desc_3
11) 하위코드_name_4
12) 하위코드_value_4
13) 하위코드_desc_4</code></pre><h3 id="느낀-점">느낀 점</h3>
<ul>
<li>소스 코드보다 DB에 저장된 값에 접근하는 것이 속도는 느리겠으나, 관리 측면에서 훨씬 용이하다. 어떤 결정이든 trade-off가 있고, 잘 선택을 해야겠다.</li>
<li>은행에서 일을 잘한다는 건.. 나 이후에 누가 와도 돌아가는 &quot;시스템&quot;을 만드는 것 같다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[해내기] CORS를 이해하고 플젝에 적용하기]]></title>
            <link>https://velog.io/@serious_yeon/CORS%EB%A5%BC-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B3%A0-%ED%94%8C%EC%A0%9D%EC%97%90-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@serious_yeon/CORS%EB%A5%BC-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B3%A0-%ED%94%8C%EC%A0%9D%EC%97%90-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 26 Feb 2023 01:03:22 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@sj950902/CORS%EC%99%80-SOP%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-1%ED%83%84">SOP의 필요성</a></p>
<pre><code>목차
Origin이란?
SOP의 필요성
CORS의 필요성</code></pre><h3 id="origin">Origin</h3>
<p>Origin = PROTOCOL + HOST + PORT</p>
<p><img src="https://velog.velcdn.com/images/serious_yeon/post/7e819b18-0a7b-4e34-b764-018803cf3fc5/image.png" alt=""></p>
<h3 id="sop의-필요성">SOP의 필요성</h3>
<p>SOP란, 브라우저는 다른 도메인의 리소스를 차단하겠다는 정책이다.</p>
<p>http는 stateless한 바보니까, 요청이 들어오면 죄다 처리한다. 그래서 (<a href="http://www.mybank.com)%EA%B0%80">www.mybank.com)가</a> (www.해커.com)에서 보낸 악성 request를 처리할 수도 있다. 이걸 방지하기 위해서 token이나 csrf를 발행하기도 하지만, token이랑 csrf가 탈취되면 어쩔겨! 결국 SOP 정책이 필요하다.</p>
<h3 id="cors의-필요성">CORS의 필요성</h3>
<p>SOP로 하니까 웹 통신이 지나치게 엄격해졌다. 특히 클라이언트 서버를 따로 두는 요즘.. ajax로 다른 도메인에 요청을 보내는 요즘.. 그래서 CORS 조항을 지킨 자원의 경우 허용을 해주겠다는 예외 사항을 뒀다.</p>
<p>CORS 조항을 지킨다는 건.. server에 allowed origin을 추가해주는 거다.</p>
<p>브라우저는 request header에 자신의 출처를 보낸다. server는 allowed origin인지 검사하고, allowed origin일 경우 자원을 전송한다. 그렇지 않으면 CORS 오류를 반환한다.</p>
<h3 id="플젝에-적용하기">플젝에 적용하기</h3>
<p>시큐리티에 corsConfiguration 객체가 있다. 여기에 추가 설정을 해주었다.
<img src="https://velog.velcdn.com/images/serious_yeon/post/db5883d5-bfc3-4c90-b28c-32341e4b7608/image.png" alt=""></p>
<h3 id="후기">후기</h3>
<ul>
<li>브라우저 자체에 내장된 기능들이 꽤 있겠구나..</li>
<li>오픈API CORS 설정은 어떻게 되어있지..</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[해내기] postman으로 테스트와 소통을 원활하게 하기]]></title>
            <link>https://velog.io/@serious_yeon/postman%EC%9C%BC%EB%A1%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-%EC%86%8C%ED%86%B5%EC%9D%84-%EC%9B%90%ED%99%9C%ED%95%98%EA%B2%8C-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@serious_yeon/postman%EC%9C%BC%EB%A1%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-%EC%86%8C%ED%86%B5%EC%9D%84-%EC%9B%90%ED%99%9C%ED%95%98%EA%B2%8C-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 20 Nov 2022 07:01:46 GMT</pubDate>
            <description><![CDATA[<h3 id="여러-사람과-테스트-공유하기">여러 사람과 테스트 공유하기</h3>
<p>API를 개발할 때에 uri, request data, response data 등을 사전에 협의해야 한다. 우리는 기획 단계에서 API 명세서를 작성했다.</p>
<p>맡은 API를 개발이 완료되면 로컬에서 테스트를 진행했다. 그런데 다른 사람이 개발한 API를 테스트하려면 어떻게 해야할까? 내가 개발하지 않은 API이기 때문에 이해도가 떨어졌고, header 값을 누락하거나 request data를 잘못 넣는 경우가 많았다. 그러다 postman의 API들을 json으로 공유할 수 있다는 것을 알게 되었다.</p>
<p>뿐만 아니라 팀으로 공유도 할 수 있었다. 이러면 동기화도 가능! 그런데 무료 버전에서는 팀당 최대 3명만 사용이 가능했다. 그래서 내가 계정을 두개 파고, BE용 &amp; FE용을 팠다. </p>
<h3 id="environment-활용하기">environment 활용하기</h3>
<p><img src="https://velog.velcdn.com/images/serious_yeon/post/f1d1bf83-a824-4942-bcd6-0da9f94b9f7b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/serious_yeon/post/be35738c-e6d6-453f-99ad-f3fbb64cdb44/image.png" alt=""></p>
<p>API에서 사용하는 전역 변수들이 있다. 예를 들어 <a href="http://localhost:8080">http://localhost:8080</a> 이나 aws ip 같은 것들. 이런 것들을 변수화해서 적용할 수 있는 기능이 environment이다. </p>
<p><strong>(step 1) 로그인 API 호출 &gt; 
(step 2) response에 들어있는 토큰값 복사 &gt; 
(step 3) 각 API의 header 값을 변경</strong>
특히 jwt 로그인을 개발한 후로 header에 ACCESS_TOKEN을 넣을 때 유용했다. 이전에는 각 API를 호출할 때마다, (step 3)번이 무한히 반복되는 구조였다.. 이제는 environment의 ACCESS_TOKEN 하나만 변경해주면 된다!</p>
<h3 id="test-기능으로-environment-업데이트-자동화하기">Test 기능으로 environment 업데이트 자동화하기</h3>
<p><img src="https://velog.velcdn.com/images/serious_yeon/post/b8fb6f1a-e713-4eed-975f-5c812d7341a6/image.png" alt=""></p>
<p>그런데 로그인 API를 이해하는 사람은, response의 어떤 값을 request의 어디에 넣어야 하는지 알지만.. 그렇지 않은 사람들은 API를 호출하는 게 어려웠다. 호출할 때마다 실수도 많고. 이걸 자동화할 수 없을까?</p>
<p>postman의 test 기능을 넣으면 가능하다! 로그인 API에 아래와 같은 스크립트를 작성하면, (1)만 하면 (2,3)을 해준다. </p>
<p>이러한 기능을 사용해서, BE 간에 테스트를 원활하게 하고 + FE가 API 테스트를 하기에도 용이했다 ^__^</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[해내기] 아키텍쳐의 중요성을 느끼다]]></title>
            <link>https://velog.io/@serious_yeon/%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1</link>
            <guid>https://velog.io/@serious_yeon/%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1</guid>
            <pubDate>Fri, 13 Aug 2021 15:18:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/serious_yeon/post/dd1aa3a1-0890-48f4-ac1d-fb1a1dba6a75/image.png" alt=""></p>
<p>이번 프로젝트에서 가장 어려웠고, 가장 많이 배운 것이다.</p>
<p>여태까지는 아키텍쳐의 중요성을 몰랐다. 웹 개발할 때는 서버, 클라이언트, 디비를 두고 AWS에 배포하여 브라우저로 접근하는 것이 숨쉬듯 당연했다.</p>
<p>그런데 배경지식이 없는 IoT 프로젝트를 하려니 막막하게 다가왔다. 안 그래도 모르겠는데 끊임없이 낯선 용어들이 등장해서 더 어렵게 느껴지고. 일단 해보자는 사람과 아키텍쳐를 정하고 가자는 사람의 대립도 있었다.</p>
<p>기업에 가면 한다는 페이퍼워크 중의 하나가 아키텍쳐를 세우는 일 아닐까. 경험해보지 않은 기술들을 가늠하고, 테스트를 해보고 경우의 수를 생각하고, 비용과 효과를 저울질하고. 과정은 힘들었지만 결과적으론 많이 배우는 계기였다.</p>
<h3 id="1-iot-센서를-어떻게-제어하나">1) IoT 센서를 어떻게 제어하나</h3>
<p><img src="https://images.velog.io/images/serious_yeon/post/4f15004e-7898-446e-b739-d2a992d47276/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-08-29%20%EC%98%A4%ED%9B%84%2012.04.49.png" alt="">
처음엔 라즈베리파이를 python으로 제어한다고 생각했다.
그러자 서버와 라즈베리파이는 어떻게 통신하는가 의문이 들었다.</p>
<p>결론적으로 라즈베리파이는 python 외에도 다양한 언어로 제어할 수 있다.
우리는 기본 서버를 Node.js로 개발했는데, 
특정 url에 요청을 보내면 센서를 조작하는 로직을 수행하도록 구성했다.</p>
<h3 id="2-배포했을-때-iot-센서를-어떻게-제어하나">2) 배포했을 때 IoT 센서를 어떻게 제어하나</h3>
<p><img src="https://images.velog.io/images/serious_yeon/post/aeb45ba5-c988-4a97-9644-1112541a8b45/architecture.jpeg" alt=""></p>
<p>로컬에 있을 때는 Node.js 서버로 라즈베리파이를 제어할 수 있다.
그런데 AWS로 서버를 배포했을 때 클라우드에 있는 서버가 로컬에 있는 라즈베리파이를 제어할 수 있는가 의문이 들었다.</p>
<p>팀원이 브라우저를 통해서 할 수 있다는 가설을 얘기했지만
그러면 모든 해커들이 브라우저로 내 컴퓨터를 헤집어 놓지 않았을까 의문이 들었다.</p>
<p>결론적으로 AWS 서버와 로컬의 라즈베리파이가 통신하려면
<strong>from</strong> 로컬 <strong>to</strong> AWS 서버에 지속적으로 신호를 보내야 한다.
그러면 AWS 서버가 로컬의 ip를 확인하고, 정확한 제어를 할 수 있다.
하지만 이를 구현하기에는 시간적 여유가 부족했다.</p>
<p>우리가 택한 방법은 라즈베리파이에 모든 환경(Server + Cloud + DB)를 구성해놓고
클라우드와 통신하지 않은 채로 라즈베리파이 내에서만 작동하도록 하는 것이었다.
애초에 웹과 통신하는 기능이 적었기에 가능했다.</p>
<h3 id="3-pm2를-활용한-자동-시작">3) pm2를 활용한 자동 시작</h3>
<p>UX를 고려할 때 사용자가 기기를 켰을 때 앱이 자동으로 시작되도록 설정하는 것이 자연스럽다.
pm2는 이것을 설정해주는 프로그램이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[해내기] MySQL에서 MariaDB로 바꾸기]]></title>
            <link>https://velog.io/@serious_yeon/SQL-MySQL%EA%B3%BC-MariaDB</link>
            <guid>https://velog.io/@serious_yeon/SQL-MySQL%EA%B3%BC-MariaDB</guid>
            <pubDate>Fri, 13 Aug 2021 14:45:15 GMT</pubDate>
            <description><![CDATA[<p>라즈베리파이에서 제일 힘들었던 것 두개 중 하나가 DB 설치였다.
리눅스 명령어도 익숙하지 않은데다, 
설치가 안되면 프로젝트 후반부에 DB를 교체해야 한다는 심적 부담감이 컸다.</p>
<h3 id="1-mysql-대신-mariadb를-사용했다">1) MySQL 대신 MariaDB를 사용했다.</h3>
<p>MySQL 5.7을 설치하기가 정말 어려웠다. 
MySQL 8.x 버전으로 업그레이드 하자니 인증 방식이 달라서 오류가 뜨고. 
구글링해보니 MariaDB로 교체하는 걸 추천하는 글이 많았다.</p>
<p>그래서 프로젝트 후반부에 DB를 교체하기로 결정했다. 
교체에 앞서 테스트를 맡았는데, MySQL과 MariaDB는 놀랍도록 유사했다. 
터미널 창에 입력하는 mysql, DB의 포트번호 (3306) ..
뿐만 아니라 Node.js 상의 라이브러리도, 커넥션도, 쿼리문도 수정할 것이 하나도 없었다. </p>
<p>Workbench도 돌고래가 그려진 MySQL 제품을 사용했다. 
커넥션을 생성할 때 mysql에 최적화되어 있다는 경고창이 뜨기는 했으나,
가볍게 무시하고 continue anyway 눌러주니 정상 작동했다.</p>
<h3 id="2-라즈베리파이-mariadb에-원격-접속하기">2) 라즈베리파이 MariaDB에 원격 접속하기</h3>
<p>라즈베리파이에서 사용할 수 있는 Workbench가 없었다.
처음에는 터미널 창에서 진행했는데, json 파일을 import 하는 것이 어려웠다.
구글링해도 table의 특정 column에 json 파일 집어 넣는 것만 나오고.
Workbench의 import Wizard 사용하면 쉬운데.</p>
<p>그래서 원격 접속으로 노트북에 있는 Workbench와 라즈베리파이의 MariaDB를 연결했다.
import 하는 데에 시간은 걸리는 편이었지만 비교적 쉽게 해결되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[M1과 intel은 homebrew 저장소 위치가 다르다 ]]></title>
            <link>https://velog.io/@serious_yeon/M1-homebrew-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%9C%84%EC%B9%98</link>
            <guid>https://velog.io/@serious_yeon/M1-homebrew-%EC%A0%80%EC%9E%A5%EC%86%8C-%EC%9C%84%EC%B9%98</guid>
            <pubDate>Fri, 13 Aug 2021 14:27:49 GMT</pubDate>
            <description><![CDATA[<p>homebrew로 설치한 mysql을 완전히 삭제할 때 아래 코드를 터미널 창에 입력하라고 한다.</p>
<pre><code>rm -rf /usr/local/var/mysql</code></pre><p>M1 homebrew는 기존 맥북과 파일트리가 조금 다르다.
<strong>/usr/local</strong> 를 <strong>/opt/homebrew/</strong> 로 대치해주면 된다. </p>
<pre><code>rm -rf /opt/homebrew/var/mysql</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[28000 Access denied for user ''@'localhost]]></title>
            <link>https://velog.io/@serious_yeon/SQL-28000-Access-denied-for-user-localhost</link>
            <guid>https://velog.io/@serious_yeon/SQL-28000-Access-denied-for-user-localhost</guid>
            <pubDate>Fri, 13 Aug 2021 14:19:12 GMT</pubDate>
            <description><![CDATA[<p>node.js에 db connection을 하려는데 28000 에러가 떴다.</p>
<pre><code>  code: &#39;ER_ACCESS_DENIED_ERROR&#39;,
  errno: 1045,
  sqlMessage: &quot;Access denied for user &#39;&#39;@&#39;localhost&#39; (using password: NO)&quot;,
  sqlState: &#39;28000&#39;,
  fatal: true</code></pre><h3 id="1-env-파일의-위치가-알맞은지-보자">1) .env 파일의 위치가 알맞은지 보자</h3>
<p>.env 위치는 상대적이다. 
형제에 있을 땐 .env 겠지만 삼촌이면 ../env 이렇게</p>
<pre><code>// 
dotenv.config({
  path:path.join(__dirname, &#39;.env&#39;)
});</code></pre><h3 id="2-env의-변수명-앞에-db_를-붙여보자">2) .env의 변수명 앞에 DB_를 붙여보자</h3>
<p>USER는 Window의 예약어, USERNAME은 Mac의 예약어다. 
Mac에선 잘 연결되던 것이 Window에 가니 먹통이라 고민했는데 이런 함정이..</p>
<pre><code>DB_HOST = localhost
DB_USERNAME = 
DB_PASSWORD = 
DB_DATABASE = </code></pre>]]></description>
        </item>
    </channel>
</rss>