<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Jaime's 기술 블로그</title>
    <link>https://jaime-note.tistory.com/</link>
    <description>  Steady and consistent, whatever it is.</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 12:32:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Jaime.Lee</managingEditor>
    <item>
      <title>애플리케이션 메트릭: 미터와 메트릭명</title>
      <link>https://jaime-note.tistory.com/512</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;미터 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로미터에서 &lt;b&gt;Meter&lt;/b&gt;를 생성하는 두 가지 방법이 있습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;플루언트 빌더&lt;/b&gt;: 상세한 설정이 필요할 때&lt;/li&gt;
&lt;li&gt;&lt;b&gt;축약 구문&lt;/b&gt;: 간단하게 생성할 때&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;플루언트 빌더 vs 축약 구문&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;플루언트 빌더&lt;/b&gt;는 라이브러리에서 사용하기 좋고, &lt;b&gt;축약 구문&lt;/b&gt;은 마이크로서비스에서 간결한 코드를 유지할 때 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 단위 정보는 모니터링 차트에서 가독성을 높이는 데 도움이 됩니다. 예를 들어 '2147483648 바이트'보다 '2GB'가 훨씬 보기 좋죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링 시스템이 기본 단위 정보를 지원하지 않더라도 그라파나(grafana) 같은 차트 도구를 이용하면 사용자 인터페이스를 통해 수동으로 단위를 선택할 수 있고, 단위에 따라 자동 조절 기능을 수행합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 예제&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;플루언트 빌더 방식&lt;/h4&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;Counter counter = Counter.builder(&quot;requests&quot;)
    .tag(&quot;status&quot;, &quot;200&quot;)
    .tags(&quot;method&quot;, &quot;GET&quot;, &quot;outcome&quot;, &quot;SUCCESS&quot;)
    .description(&quot;http requests&quot;)
    .baseUnit(&quot;requests&quot;)
    .register(registry);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;축약 구문 방식&lt;/h4&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Counter counter = registry.counter(&quot;requests&quot;,
    &quot;status&quot;, &quot;200&quot;, &quot;method&quot;, &quot;GET&quot;, &quot;outcome&quot;, &quot;SUCCESS&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미터를 생성하려면 두 방식 모두 이름과 태그를 반드시 지정해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메트릭명&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메트릭명 작성 원칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메트릭을 최대한 활용하려면 &lt;b&gt;메트릭명을 적절히 선정하고 태그를 취합&lt;/b&gt;해야 합니다. 상시 혹은 특정 시기에 의미 있는 집계 수치를 나타내는 모든 태그가 취합 대상입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;좋은 메트릭명 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메트릭명은 &lt;b&gt;영숫자 단어들로 구성&lt;/b&gt;하며 마침표로 구분합니다. 좋은 메트릭명은 &lt;b&gt;백과 정보를 충분히 제공하며 메트릭명만 선택해도 의미 있는 값을 얻을 수 있어야&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;올바른 예시&lt;/h4&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 명확하고 구체적인 메트릭명 사용
registry.counter(&quot;database.queries&quot;, &quot;db&quot;, &quot;users&quot;);
registry.counter(&quot;http.requests&quot;, &quot;uri&quot;, &quot;/api/users&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;code&gt;http.server.requests&lt;/code&gt; 메트릭은 애플리케이션, 클라우드 서비스 리전, API 엔드포인트, HTTP 메서드, 응답 상태 코드 등의 태그를 포함합니다. 이 모든 태그를 고유하게 조합해 측정 결과를 집계하면 애플리케이션 스택 전반에 걸쳐 이뤄지는 모든 상호작용의 처리 결과를 산출할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;피해야 할 메트릭명 설계&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;잘못된 예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시처럼 모호한 메트릭명을 사용하면 문제가 발생합니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 나쁜 예시 - 모호한 메트릭명
registry.counter(&quot;calls&quot;, &quot;type&quot;, &quot;database&quot;, &quot;db&quot;, &quot;users&quot;);
registry.counter(&quot;calls&quot;, &quot;type&quot;, &quot;http&quot;, &quot;uri&quot;, &quot;/api/users&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;calls&lt;/code&gt; 메트릭을 선택할 때 데이터베이스 호출과 HTTP 요청 수가 합산된 값이 조회됩니다. 이 메트릭은 태그 차원에서 세부적으로 조회하지 않으면 쓸모가 없습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네이밍 컨벤션과 필터링&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MeterFilter 활용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네임스페이스를 통일하면 모니터링 시스템의 UI와 대시보드 도구에서 메트릭을 알맞게 순서대로 볼 수 있습니다. 또한 &lt;code&gt;MeterFilter&lt;/code&gt;를 이용해 메트릭을 그룹 단위로 제어할 때 편리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시처럼 &lt;code&gt;jvm.gc&lt;/code&gt;로 시작하는 모든 메트릭을 비활성화시키려면 다음과 같이 사용할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;MeterRegistry registry = ...;
registry.config().meterFilter(MeterFilter.denyNameStartsWith(&quot;jvm.gc&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링 시스템마다 권장 명명 규칙은 다르고, 일부 규칙은 시스템 간에 서로 호환되지 않습니다. 마이크로미터는 영숫자 단어를 마침표로 연결하는 명명 규칙을 따르며, 구현 시 대상 모니터링 시스템에 맞는 명명 규칙이 함께 제공됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명명 규칙은 메트릭명과 태그에 포함된 특수문자를 확인하고 대상 시스템에 따라 적절히 처리합니다. 명명 규칙의 역할은 단순히 관용적인 외형을 지정하는 것에 그치지 않고, 엘라스틱서치는 마침표를 인덱스의 계층 구조로 인식합니다. 만일 &lt;code&gt;http.server.requests&lt;/code&gt;와 &lt;code&gt;http.client.requests&lt;/code&gt; 두 메트릭을 그대로 사용한다면, 엘라스틱서치 인덱싱을 처리할 때 문제가 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용자 정의 명명 규칙&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;MeterRegistry&lt;/code&gt;의 기본 명명 규칙은 사용자가 원하는 방식으로 오버라이드할 수 있습니다. 아래 예시는 &lt;code&gt;NamingConvention&lt;/code&gt; 인터페이스를 이용해 사용자 명명 규칙을 설정합니다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;registry.config()
    .namingConvention(new NamingConvention() {
        @Override
        public String name(String name, Meter.Type type, String baseUnit) {
            String camelCased = NamingConvention.snakeCase.name(name, type, baseUnit);
            return baseUnit == null ? camelCased : camelCased + &quot;_&quot; + baseUnit;
        }
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시는 메트릭명 뒤에 단위를 붙이는 사용자 정의 명명 규칙을 보여줍니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;고유 태그와 비용 고려사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고유 태그가 늘어날 때마다 스토리지 비용뿐만 아니라 쿼리 수행 비용도 시간적, 자원적인 면에서 함께 증가합니다. 쿼리 결과를 집계할 때 데이터가 늘어나기 때문입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;태그 설계 시 주의사항&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;태그값은 &lt;b&gt;null&lt;/b&gt;이나 공백이 아니어야 합니다. 마이크로미터는 일부 상황에서 빈 태그값을 지원하기도 합니다(예: 데이터로그 레지스트리). 하지만 모든 모니터링 시스템이 빈 태그값을 지원하는 것은 아니므로, 다른 시스템으로 이전할 때 문제가 될 수 있습니다.&lt;/p&gt;</description>
      <category>SRE</category>
      <category>네이밍컨벤션</category>
      <category>마이크로미터</category>
      <category>메트릭</category>
      <category>모니터링</category>
      <category>미터</category>
      <category>미터 필터</category>
      <author>Jaime.Lee</author>
      <guid isPermaLink="true">https://jaime-note.tistory.com/512</guid>
      <comments>https://jaime-note.tistory.com/512#entry512comment</comments>
      <pubDate>Tue, 17 Jun 2025 19:29:42 +0900</pubDate>
    </item>
    <item>
      <title>애플리케이션 메트릭: 미터 레지스트리</title>
      <link>https://jaime-note.tistory.com/511</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;마이크로미터의 미터 레지스트리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Micrometer&lt;/code&gt;는 자바로 제작된 차원형 메트릭 수집 라이브러리로, 대부분의 주요 &lt;code&gt;모니터링 시스템&lt;/code&gt;을 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Micrometer에서 &lt;code&gt;Meter&lt;/code&gt;는 애플리케이션의 측정 결과를 수집하는 인터페이스이며, 이 측정 결과 각각이 &lt;code&gt;메트릭&lt;/code&gt;이 됩니다. &lt;code&gt;MeterRegistry&lt;/code&gt;가 이 미터들을 생성하고 보유하며, Micrometer는 다양한 모니터링 시스템에 맞춰 &lt;code&gt;MeterRegistry&lt;/code&gt;를 각각 구현하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Micrometer가 지원하는 레지스트리 구현 라이브러리는 &lt;code&gt;Prometheus&lt;/code&gt;, &lt;code&gt;Atlas&lt;/code&gt; 등을 포함하며, 메이븐 센트럴과 제이센터에서 다음과 같은 방식으로 사용할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;io.micrometer:micrometer-registry-&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prometheus용 레지스트리 생성 예시&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;MeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플루언트(fleunt) 빌더를 사용하면 좀 더 세부적인 설정도 가능합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;MeterRegistry registry = InfluxMeterRegistry.builder(InfluxConfig.DEFAULT)
    .httpClient(customHttpClient)
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;여러 모니터링 시스템을 사용하는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 모니터링 시스템에 메트릭을 동시에 전송하고 싶다면 &lt;code&gt;CompositeMeterRegistry&lt;/code&gt;를 사용하면 됩니다:&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;MeterRegistry prometheusRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
MeterRegistry atlasRegistry = new AtlasMeterRegistry(AtlasConfig.DEFAULT);
MeterRegistry compositeRegistry = new CompositeMeterRegistry()
    .add(prometheusRegistry)
    .add(atlasRegistry);
compositeRegistry.counter(&quot;my.counter&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레지스트리마다 별도로 미터를 정의할 필요 없이, 하나의 &lt;code&gt;CompositeMeterRegistry&lt;/code&gt;에 등록된 모든 레지스트리에 메트릭이 전파됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Micrometer&lt;/code&gt;의 &lt;code&gt;MeterRegistry&lt;/code&gt;는 &lt;code&gt;SLF4J&lt;/code&gt;의 &lt;code&gt;LoggerFactory&lt;/code&gt;와 비슷한 구조를 가지고 있습니다. &lt;code&gt;Logger&lt;/code&gt; 인스턴스를 통해 로그를 남기듯, &lt;code&gt;Timer&lt;/code&gt;, &lt;code&gt;Counter&lt;/code&gt; 같은 측정기를 만들어 메트릭을 기록합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, &lt;code&gt;SLF4J&lt;/code&gt;에서 &lt;code&gt;LoggerFactory.getLogger(...)&lt;/code&gt;로 로그 객체를 가져오듯, &lt;code&gt;Micrometer&lt;/code&gt;에서도 &lt;code&gt;Metrics.globalRegistry&lt;/code&gt;를 통해 전역 레지스트리에 메트릭을 등록할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Timer timer = Timer.builder(&quot;time.something&quot;)
    .description(&quot;time some operation&quot;)
    .register(Metrics.globalRegistry);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 등록한 &lt;code&gt;Timer&lt;/code&gt;는 코드 블록의 실행 시간을 마치 로그를 남기듯 자연스럽게 기록할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;timer.record(() -&amp;gt; {
    logger.info(&quot;I did something&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입 없이도 손쉽게 사용할 수 있어 간단한 컴포넌트나 유틸성 클래스에서 유용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Micrometer의 레지스트리 구조 이해하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Micrometer&lt;/code&gt;의 &lt;code&gt;MeterRegistry&lt;/code&gt;는 복수의 레지스트리를 조합한 &lt;code&gt;CompositeMeterRegistry&lt;/code&gt;로 구성됩니다. 이 구조는 메트릭 데이터를 여러 백엔드(예: &lt;code&gt;Prometheus&lt;/code&gt;, &lt;code&gt;Stackdriver&lt;/code&gt; 등)로 동시에 전송하기 위해 설계된 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Micrometer&lt;/code&gt;에서의 레지스트리 흐름을 요약하자면 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;라이브러리에서 정의된 메트릭은 전역 레지스트리에 먼저 등록됩니다.&lt;/li&gt;
&lt;li&gt;애플리케이션에서는 &lt;code&gt;CompositeMeterRegistry&lt;/code&gt;를 생성하여 이 전역 레지스트리를 다시 포함합니다.&lt;/li&gt;
&lt;li&gt;이후 &lt;code&gt;Prometheus&lt;/code&gt;나 &lt;code&gt;Stackdriver&lt;/code&gt;와 같은 개별 구현체 레지스트리를 애플리케이션 레지스트리에 추가합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 흐름은 다음과 같은 코드 및 다이어그램을 통해 이해할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;CompositeMeterRegistry composite = new CompositeMeterRegistry();
composite.add(new PrometheusMeterRegistry(...));
composite.add(new StackdriverMeterRegistry(...));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/lcalmsky/lcalmsky/master/docs/blog/sre/registry-relation.puml&quot; alt=&quot;registry-relation&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조 덕분에 애플리케이션 내에서 정의한 메트릭은 &lt;code&gt;Prometheus&lt;/code&gt;와 &lt;code&gt;Stackdriver&lt;/code&gt; 등 여러 모니터링 시스템에 동시에 전달될 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스프링부트에서의 자동 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트를 사용하는 경우에는 &lt;code&gt;MeterRegistry&lt;/code&gt; 설정이 훨씬 더 간편해집니다. &lt;code&gt;micrometer-registry-{system}&lt;/code&gt; 형태의 의존성을 클래스패스에 추가하기만 하면, 스프링부트는 해당 레지스트리를 자동으로 감지하고 애플리케이션에 통합해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;code&gt;micrometer-registry-prometheus&lt;/code&gt; 의존성을 추가하면, 스프링은 자동으로 &lt;code&gt;Prometheus&lt;/code&gt;용 레지스트리를 생성하고, 이를 전역 레지스트리에 추가해줍니다. 또한 &lt;code&gt;@Bean&lt;/code&gt;으로 설정된 커스텀 레지스트리 역시 전역 레지스트리에 자동 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 자동 설정 덕분에 개발자는 별도의 수동 등록 없이도 애플리케이션에서 메트릭을 바로 사용할 수 있으며, 설정만으로 &lt;code&gt;Prometheus&lt;/code&gt;나 &lt;code&gt;Datadog&lt;/code&gt; 등의 백엔드로 데이터를 전송할 수 있습니다.&lt;/p&gt;</description>
      <category>SRE</category>
      <category>meter registry</category>
      <category>Prometheus</category>
      <category>spring boot micrometer</category>
      <category>마이크로미터</category>
      <category>메트릭</category>
      <category>미터 레지스트리</category>
      <category>애플리케이션 모니터링</category>
      <author>Jaime.Lee</author>
      <guid isPermaLink="true">https://jaime-note.tistory.com/511</guid>
      <comments>https://jaime-note.tistory.com/511#entry511comment</comments>
      <pubDate>Mon, 16 Jun 2025 15:52:58 +0900</pubDate>
    </item>
    <item>
      <title>애플리케이션 메트릭: 모니터링 관점과 메트릭 구조로 이해하기</title>
      <link>https://jaime-note.tistory.com/510</link>
      <description>&lt;h1&gt;애플리케이션 메트릭&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 시스템은 서로 상호작용하는 수많은 마이크로 서비스로 구성됩니다. 시스템 복잡도가 높아질 수록 시스템을 관찰하는 역량의 중요성이 더욱 커집니다. 애플리케이션 메트릭 관련 포스팅에서는 분산 시스템의 성능을 측정하고 사전에 위험을 알리는 방법에 대해 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 조직은 반드시 모니터링 솔루션을 하나 이 상 선정해야 하는데 규모나 복잡도에 구애받지 않고 조직에 맞는 솔루션을 찾을 수 있을 정도로 시장은 충분히 성숙해졌습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;블랙박스 vs 화이트박스 모니터링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메트릭 수집 방식은 관찰 가능한 요소가 무엇인지에 따라 두 가지로 분류합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;블랙박스&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수집기가 입출력을 관찰할 수 있으나 내부 메커니즘은 알 수 없음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) HTTP 요청/응답&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프로세스를 가로채거나 감싸는 방식으로 대상을 측정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;화이트 박스&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수집기가 입출력 관찰 뿐만 아니라 내부 작동 메커니즘 관찰 가능&lt;/li&gt;
&lt;li&gt;애플리케이션 코드 내부에서 작동&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 솔루션이 에이전트 형태로 블랙박스 모니터링 시스템을 제공합니다. 에이전트는 애플리케이션 프로세스와 같이 동작하며 상태를 관찰하는데, 스프링 부트처럼 많이 사용되는 프레임워크의 프로세스는 상당히 깊은 수준까지 관찰할 수 있어서 화이트박스 수집기처럼 보이기도 합니다.&lt;br /&gt;블랙박스 모니터링은 에이전트가 일반화시킨 정보만 얻을 수 있는데 이는 전적으로 에이전트에 종속적입니다. 예를 들어, 스프링부트의 데이터베이스 트랜잭션 메커니즘을 가로채 작동 시간을 측정할 수 있으나 특정 클래스 내부에서 Map이 수행하는 캐싱 기능을 측정할 순 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 메시를 이용한 측정은 내부 로직을 알지 못하고 외부에서 네트워크 요청만 들여다보는 블랙박스 방식입니다.&lt;br /&gt;이런 방식은 애플리케이션 내부까지 들어가서 함수 호출 하나하나를 추적할 수 있는 에이전트 방식보다 정밀도나 활용성이 떨어질 수 있습니다.&lt;br /&gt;서비스 메시로 볼 수 있는 최대 수준은 &quot;A 서비스가 B 서비스에게 요청을 보냈다&quot;는 RPC 수준의 정보 정도입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화이트박스 방식은 뭔가 복잡하고 손이 많이 갈 것처럼 느껴지지만, 사실 HTTP 요청 시간, CPU 사용률 같은 기본적인 지표들은 블랙박스로도 충분히 잘 수집됩니다.&lt;br /&gt;게다가 요즘의 화이트박스 측정 도구들은 내부 설정을 자동화해주기 때문에, 오히려 블랙박스처럼 간편하게 사용할 수 있고, 개발자가 직접 들여야 하는 노력의 차이도 거의 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화이트박스 방식은 블랙박스보다 더 많은 정보, 특히 내부의 세부 정보까지 수집할 수 있어 정밀한 모니터링이 가능합니다.&lt;br /&gt;작업량 측면에서는 큰 차이가 없는데, 블랙박스는 에이전트 설정이나 전달 방식 수정 같은 작업이 필요하고, 화이트박스는 자동 설정 기능을 쓰면 비슷한 수준으로 수집을 자동화할 수 있습니다.&lt;br /&gt;다만 화이트박스 방식은 수집 기능을 애플리케이션에 미리 넣어야 해서, 빌드 시점에 코드에 포함시키는 작업이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 화이트박스 측정 방식은 블랙박스처럼 동작하지만, 특정 모니터링 시스템에 맞춘 전용 라이브러리는 이런 방식을 잘 사용하지 않습니다.&lt;br /&gt;왜냐하면 특정 프레임워크나 클라이언트에 종속되지 않도록 설계되기 때문입니다.&lt;br /&gt;또한, 같은 코드를 여러 측정 라이브러리가 중복해서 측정하는 것도 피하고 싶어합니다.&lt;br /&gt;그래서 마이크로미터처럼 하나의 측정 포인트를 작성하면 여러 곳에 쓸 수 있는 방식이 프레임워크나 라이브러리를 만드는 개발자 입장에서 훨씬 실용적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 방식은 서로 기능이 겹치는 경우에도 보완적 관계를 형성할 수 있어 어느 한 가지만 사용을 제한할 필요는 없습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;차원형 메트릭&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 모니터링 시스템은 대부분 하나의 메트릭명에 여러 키-값 태그로 구성된 차원형 명명 스키마를 채택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링 시스템마다 저장 방식은 다르지만, 일반적으로 메트릭 이름과 태그(tag)의 조합 별로 데이터를 별도로 저장합니다.&lt;br /&gt;따라서 저장 비용은 모든 태그 조합의 개수에 비례하게 됩니다.&lt;br /&gt;예를 들어, &lt;code&gt;http.server.requests&lt;/code&gt; 메트릭이 다음과 같은 태그를 가진다고 가정해보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;method: GET, POST (2종류)&lt;/li&gt;
&lt;li&gt;status: 200, 400, 500 (3종류)&lt;/li&gt;
&lt;li&gt;uri: /a, /b (2종류)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우, 이 메트릭의 최대 고유 시계열 개수는&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;2 &amp;times; 3 &amp;times; 2 = 12&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 됩니다. 즉, 12개의 서로 다른 태그 조합에 대해 각각 데이터가 저장되고 전송됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 만약 &lt;code&gt;/a&lt;/code&gt;는 GET 방식만, &lt;code&gt;/b&lt;/code&gt;는 POST 방식만 사용하는 경우라면, 가능한 조합 수는&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;1 &amp;times; 3 &amp;times; 2 = 6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 줄어듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 고유한 메트릭명 + 태그 조합 하나하나가 독립된 시계열(series)로 관리됩니다.&lt;br /&gt;각 시계열은 일정 기간 동안 데이터를 원형 버퍼(circular buffer) 형태로 저장합니다.&lt;br /&gt;따라서, 메트릭이 소모하는 총 저장 비용은 다음과 같이 표현할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;총 비용 = 고유 메트릭명 / 태그 조합 수 &amp;times; 원형 버퍼 크기&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;계층형 메트릭&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차원형 메트릭이 대중화되기 전에 주로 사용되었던 계층형 메트릭은 키-값 태그 없이 이름으로만 메트릭을 정의하는 구조입니다. 메트릭명과 태그를 결합하면 간단하게 계층형 이름으로 변환할 수 있습니다.&lt;br /&gt;HTTP method가 GET인 차원형 메트릭 httpServerRequests는 계층형 메트릭으로는 &lt;code&gt;httpServerRequests.method.GET&lt;/code&gt;이 됩니다. 와일드 카드를 이용해 &lt;code&gt;httpServerRequests.method.*&lt;/code&gt;로 모든 메트릭을 조회할 수 있습니다.&lt;br /&gt;하지만 계층형 모니터링 시스템에서는 태그를 동적으로 해석하거나 변환하는 기능이 부족합니다.&lt;br /&gt;특히 와일드카드(*)를 포함한 쿼리는 매우 제한적으로 동작하며, 예측 불가능한 결과를 낳을 수 있습니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;메트릭 쿼리&lt;/th&gt;
&lt;th&gt;값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;httpServerRequests.method.GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;httpServerRequests.method.POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;httpServerRequests.status.200.method.GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;httpServerRequests.method.*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30 (!!)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시는 httpServerRequests.method.* 와일드카드로 모든 method 값을 집계하려 하지만, status 태그가 추가된 시계열은 제외되어 총합이 실제보다 낮게 나올 수 있음을 보여줍니다.&lt;br /&gt;따라서 계층형 메트릭 시스템은 독립적인 키-값 태그들을 메트릭명 안에서 인위적으로 정렬해야한다는 번거로움이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 초기부터 차원형 시스템을 선택하고, 태그 기반 메트릭 기록 방식을 최대한 활용하는 것이 best practice라고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 포스팅 되는 내용은 차원형 메트릭 측정을 중심적으로 다룰 예정입니다.&lt;/p&gt;</description>
      <category>SRE</category>
      <category>계층형</category>
      <category>메트릭</category>
      <category>모니터링</category>
      <category>블랙박스</category>
      <category>애플리케이션 메트릭</category>
      <category>차원형</category>
      <category>화이트박스</category>
      <author>Jaime.Lee</author>
      <guid isPermaLink="true">https://jaime-note.tistory.com/510</guid>
      <comments>https://jaime-note.tistory.com/510#entry510comment</comments>
      <pubDate>Sun, 15 Jun 2025 10:30:58 +0900</pubDate>
    </item>
    <item>
      <title>애플리케이션 플랫폼: 전달, 트래픽 관리, 캡슐화 등</title>
      <link>https://jaime-note.tistory.com/509</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;전달 (Delivery)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;전달 파이프라인 개선&lt;/b&gt;은 기존 시스템 장애 가능성을 줄이고, 문제 발생 시 &lt;b&gt;빠른 롤백&lt;/b&gt;이 가능하게 함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CI(지속적 통합)&lt;/b&gt; 와 &lt;b&gt;CD(지속적 전달)&lt;/b&gt; 의 경계는 흐릿하지만, &lt;b&gt;개념적으로 명확히 구분&lt;/b&gt;해야 함:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CI는 &lt;i&gt;산출물을 저장소에 저장&lt;/i&gt;할 때 종료&lt;/li&gt;
&lt;li&gt;CD는 그 시점부터 시작&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;목적의 차이:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;CI&lt;/b&gt;: 빠른 피드백, 자동 테스트, 병합 장려&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CD&lt;/b&gt;: 릴리스 주기 단축, 보안 준수, 유연한 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트래픽 관리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;복원력&lt;/b&gt;은 장애를 예측하고 보상하는 구조에서 나옴
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i&gt;가용성 모니터링&lt;/i&gt;: 장애 발생 지점 파악&lt;/li&gt;
&lt;li&gt;&lt;i&gt;디버깅 가능성 모니터링&lt;/i&gt;: 장애 원인 분석&lt;/li&gt;
&lt;li&gt;&lt;i&gt;전달 자동화&lt;/i&gt;: 증분 릴리스에 대한 장애 억제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트래픽 관리 패턴&lt;/b&gt;은 실시간으로 장애에 대응할 수 있게 돕는 기술&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모니터링 시스템 &amp;gt; 테스트 자동화&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트는 &lt;b&gt;완전하지 않으며&lt;/b&gt;, 커버리지 100%는 실현 불가능에 가까움&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로덕션 환경&lt;/b&gt;은 항상 다르게 행동함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모니터링 시스템&lt;/b&gt;은 예상 못한 장애까지도 포착 가능&lt;/li&gt;
&lt;li&gt;테스트는 &lt;i&gt;우리가 아는 것&lt;/i&gt;을 증명, 모니터링은 &lt;i&gt;실제로 벌어지는 일&lt;/i&gt;을 보여줌&lt;/li&gt;
&lt;li&gt;따라서 &lt;b&gt;테스트보다 모니터링이 더 필수적&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 테스트 커버리지 100%를 지향하지만, 실무에서 프로덕션 환경에서만 드러나는 문제들을 경험하며 한계를 체감함. 성능 문제는 특히 코드만으로는 검증이 어려움.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;캡슐화&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;캡슐화&lt;/b&gt;: 상태와 동작을 하나로 묶고 외부에 감추는 구조 (ex. 자바 클래스)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플랫폼 엔지니어링&lt;/b&gt;도 이와 유사:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발팀(고객)의 책임을 줄이기 위해 &lt;b&gt;정보를 은닉&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;최고의 칭찬: &amp;ldquo;그쪽 일은 신경 쓸 필요 없어요&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명시적 종속성 관리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;런타임 종속성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주요 라이브러리는 메트릭, 태그, 트래픽 관리 패턴을 함께 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서비스 클라이언트 종속성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장애 대응 로직(fallback, retry 등)은 서비스 개발팀이 책임지는 것이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;런타임 종속성 주입&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 표준화가 전제&lt;/li&gt;
&lt;li&gt;예: 스프링부트 앱에 플랫폼 메트릭 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;도입 전략&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일럿 팀/앱 선정 &amp;rarr; 원칙 적용 및 연습 &amp;rarr; 일반화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서비스 메시&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;제어 플레인 + 사이드카&lt;/b&gt;를 아우르는 인프라 계층&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트래픽 관리, 서비스 탐색, 모니터링&lt;/b&gt;을 담당하여, 애플리케이션은 &lt;b&gt;비즈니스 로직에 집중&lt;/b&gt; 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복잡도와 운영 비용&lt;/b&gt;을 줄여줌&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗ 사이드카에는 도메인 지식을 넣지 말자&lt;br /&gt;복잡성 증가 및 유지보수 어려움 발생&lt;br /&gt;➝ 공통 기능만 최소한으로 구현&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;플랫폼 엔지니어링&lt;/b&gt;은 개발팀을 통제하는 것이 아님&lt;/li&gt;
&lt;li&gt;고객 중심으로 &lt;b&gt;도움을 주는 역할&lt;/b&gt;이어야 하며, 모든 도구와 프로세스는 &lt;b&gt;관문이 아닌 가드레일&lt;/b&gt;이 되어야 함&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SRE</category>
      <author>Jaime.Lee</author>
      <guid isPermaLink="true">https://jaime-note.tistory.com/509</guid>
      <comments>https://jaime-note.tistory.com/509#entry509comment</comments>
      <pubDate>Sat, 14 Jun 2025 10:30:44 +0900</pubDate>
    </item>
    <item>
      <title>애플리케이션 플랫폼: 모니터링</title>
      <link>https://jaime-note.tistory.com/508</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;모니터링&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 서비스 가용성 측정 수단&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메트릭&lt;/b&gt;: 수치 기반, 시계열 데이터&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로그&lt;/b&gt;: 세부 이벤트 기록&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분산 추적&lt;/b&gt;: 요청 흐름 추적&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 가용성 모니터링&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;개념&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;가용성 신호&lt;/b&gt;: 시스템 상태를 거시적으로 판단 (ex. 자원 소비량, 판매량, 스트리밍량 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SLI (서비스 수준 지표)&lt;/b&gt;: 측정 지표&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SLO (서비스 수준 목표)&lt;/b&gt;: SLI의 허용 범위&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SLA (서비스 수준 협약)&lt;/b&gt;: 계약 수준으로, 보통 SLO보다 완화된 기준&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SLA 위반 가능성 감지 및 선제적 경고가 중요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메트릭은 값싸고 전체적인 활동 파악에 유리&lt;/b&gt; (전수 데이터도 가능)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;: 개별 요청 추적에는 부적합 &amp;rarr; 전체 흐름 파악용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. L-USE 모델 (핵심 가용성 신호)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Latency (지연 시간)&lt;/b&gt;: REST 엔드포인트, 최대 레이턴시 중요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Utilization (사용률)&lt;/b&gt;: CPU, 메모리, 네트워크&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Saturation (포화도)&lt;/b&gt;: 대기 중 작업량 (커넥션 풀, 요청 풀 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Errors (오류율)&lt;/b&gt;: 에러 발생 비율 (서킷 브레이커 발동률 등 포함)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  사용률과 포화도는 구분 필요. 예: 메모리는 사용률과 포화도 모두로 모니터링 가능&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시 (SLI-SLO-L-USE 매핑)&lt;/h4&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SLI&lt;/th&gt;
&lt;th&gt;SLO 기준&lt;/th&gt;
&lt;th&gt;L-USE 구분&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU 사용률&lt;/td&gt;
&lt;td&gt;80% 미만&lt;/td&gt;
&lt;td&gt;포화도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;힙 사용률&lt;/td&gt;
&lt;td&gt;80% 미만&lt;/td&gt;
&lt;td&gt;포화도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에러율&lt;/td&gt;
&lt;td&gt;1% 미만&lt;/td&gt;
&lt;td&gt;오류&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최대 레이턴시&lt;/td&gt;
&lt;td&gt;100ms 미만&lt;/td&gt;
&lt;td&gt;레이턴시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 디버깅 도구의 역할&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;APM&lt;/b&gt;: 추적, 로그, 가용성 신호 통합&lt;/li&gt;
&lt;li&gt;&lt;b&gt;샘플링 방식&lt;/b&gt;으로 비용 절감 가능 (모든 데이터 수집은 비효율적)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메트릭 &amp;lt; 로그 &amp;lt; 추적&lt;/b&gt; 순으로 정밀하지만 비용 상승&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 실패 예측과 수용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템은 항상 실패 가능성을 가짐&lt;/li&gt;
&lt;li&gt;완벽함보다 &lt;b&gt;신속한 복구와 대응&lt;/b&gt;이 중요&lt;/li&gt;
&lt;li&gt;마이크로서비스 특성상 통제 불가능 요소 다수 (네트워크, 서드파티 등)&lt;/li&gt;
&lt;li&gt;사용자도 &lt;b&gt;즉각적 복구&lt;/b&gt;를 더 중요하게 여김 &amp;rarr; &amp;lsquo;서비스 회복의 역설&amp;rsquo;&lt;/li&gt;
&lt;li&gt;SLA 목표 중심의 &lt;b&gt;복원력 강화 및 장애 최소화 필요&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 신뢰 관계 구축을 위한 모니터링 전략&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일부 기업은 엔지니어링을 &lt;b&gt;서비스 조직&lt;/b&gt;으로 간주&lt;/li&gt;
&lt;li&gt;&lt;b&gt;콜센터 기반 일방향 소통&lt;/b&gt;에 의존 &amp;rarr; 문제 은폐 및 전달 지연&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 중심 모니터링 도입&lt;/b&gt; 필요 (양방향 소통 기반)&lt;/li&gt;
&lt;li&gt;장애 분석을 통해 &lt;b&gt;맥락 파악 및 사전 대응 가능&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SRE</category>
      <category>Monitoring</category>
      <category>SRE</category>
      <category>모니터링</category>
      <author>Jaime.Lee</author>
      <guid isPermaLink="true">https://jaime-note.tistory.com/508</guid>
      <comments>https://jaime-note.tistory.com/508#entry508comment</comments>
      <pubDate>Fri, 13 Jun 2025 10:30:32 +0900</pubDate>
    </item>
    <item>
      <title>애플리케이션 플랫폼: 플랫폼 엔지니어링 문화</title>
      <link>https://jaime-note.tistory.com/507</link>
      <description>&lt;h1&gt;애플리케이션 플랫폼&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스는,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션이 여러 컴포넌트로 분리&lt;/li&gt;
&lt;li&gt;각기 다른 팀이 독립적으로 개발 및 배포&lt;/li&gt;
&lt;li&gt;소프트웨어 개발 속도가 빨라지고 대규모 릴리스 일정을 수립하고 조율할 필요성 감소&lt;/li&gt;
&lt;li&gt;각 서비스를 담당하는 팀은 독립적이며 자신의 고객(내/외부)에게 필요한 비즈니스 요건에 대응 가능&lt;/li&gt;
&lt;li&gt;각기 다른 클라우드 리소스에 수평적으로 조절된 규모로 다중 배포되어 네트워크상의 다양한 프로토콜을 이용해 서로 통신&lt;/li&gt;
&lt;li&gt;데이터 전송과 통신량으로 추가 비용과 레이턴시 발생 -&amp;gt; 사용자 경험을 저하시킬 수 있음&lt;/li&gt;
&lt;li&gt;서로 독립적으로 릴리즈되지만 의도치 않게 서로 영향을 미칠 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 특징을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 분산된 시스템 관리를 위해 새로운 관행(convention), 도구, 엔지니어링 문화가 필요합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;플랫폼 엔지니어링 문화&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 표준화의 필요성과 역할 분담&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조직 내 통신 규약 및 프레임워크는 &lt;b&gt;통일되고 표준화되어야&lt;/b&gt; 함&lt;/li&gt;
&lt;li&gt;각 팀이 독자적으로 개발 스택을 관리하는 방식은 &lt;b&gt;비효율적이며 중복된 노력&lt;/b&gt;을 유발함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플랫폼 팀&lt;/b&gt;은 표준화를 책임지고,&lt;br /&gt;&lt;b&gt;프로덕션 팀&lt;/b&gt;은 비즈니스 요구사항 개발에 집중할 수 있도록 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;우리가 제공하는 것은 관문이 아니라 가드레일이다.&amp;rdquo;&lt;br /&gt;&amp;mdash; 다이앤 마시, 넷플릭스 엔지니어링 도구 디렉터&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. '관문'이 아닌 '가드레일'로서의 플랫폼 팀&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀마다 다양한 방식을 실험하고 적용할 수 있도록 &lt;b&gt;유연성 보장&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;플랫폼 팀은 개별 팀의 방식을 &lt;b&gt;배우고 관찰&lt;/b&gt;한 뒤, 이를 &lt;b&gt;조직 전체에 일반화&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;시스템의 구조는 필연적으로 그 시스템을 설계하는 조직의 커뮤니케이션 구조를 닮는다.&amp;rdquo;&lt;br /&gt;&amp;mdash; 콘웨이의 법칙&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플랫폼 엔지니어링을 잘하면 각 팀에 &lt;b&gt;전문가가 따로 없어도&lt;/b&gt; 고품질 시스템 운영 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 전사적 도구 도입 예시: 모니터링&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플랫폼 팀이 &lt;b&gt;공통 라이브러리&lt;/b&gt; 형태로 모니터링 기능 제공&lt;/li&gt;
&lt;li&gt;모든 서비스에 &lt;b&gt;기본적인 가용성 척도&lt;/b&gt;가 자동 반영&lt;/li&gt;
&lt;li&gt;프로덕션 팀은 &lt;b&gt;자신의 영역에 특화된 지표만 추가&lt;/b&gt;하면 됨 (약간의 시간 소요)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 개발자 경험에 집중하는 플랫폼 팀의 태도&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플랫폼 팀은 프로덕션 팀의 &lt;b&gt;개발자 경험(Developer Experience)&lt;/b&gt;에 깊이 몰입해야 함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복잡한 요구사항을 제거&lt;/b&gt;하여 진입 장벽을 낮추는 데 많은 공감과 노력 필요&lt;/li&gt;
&lt;li&gt;다양한 팀의 반복적 문제에서 &lt;b&gt;패턴을 추출&lt;/b&gt;하고, &lt;b&gt;도구나 가드레일로 대응&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;예시) 반복되는 스크립트 실행, 바이너리 종속성 충돌, 플러그인 버전 문제, 릴리스 파이프라인 결함 등&lt;/li&gt;
&lt;li&gt;도구는 기존 기능을 &lt;b&gt;해치지 않아야&lt;/b&gt; 하며, &lt;b&gt;경고 메시지 제공만으로는 충분하지 않음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&quot;성공만 하면 되니까 경고는 무시하자&quot;는 인식이 생기기 쉬움&lt;/li&gt;
&lt;li&gt;플랫폼 팀은 &lt;b&gt;프로덕션 팀의 시각에서 문제를 바라보고&lt;/b&gt;, 그들에게 &lt;b&gt;익숙한 방식으로 지식을 전달&lt;/b&gt;하려는 노력이 필요함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 수정 권고 수용률과 롱테일 단계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간이 지나면서 &lt;b&gt;문제 패턴의 영향력은 감소&lt;/b&gt;하고 대부분의 팀이 권장안을 수용하게 됨&lt;/li&gt;
&lt;li&gt;결국 &lt;b&gt;비협조적인 일부 팀만 남는 롱테일 단계&lt;/b&gt;에 도달
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때는 해당 팀들을 &lt;b&gt;직접 찾아가 1:1 협업&lt;/b&gt; 필요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;적극적인 소통을 통한 신뢰 구축&lt;/b&gt;으로, 향후 권장안 수용률도 높일 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 권장안의 가시성과 플랫폼 팀의 자세&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;권장안은 &lt;b&gt;쉽게 찾고 이해할 수 있어야&lt;/b&gt; 하며,&lt;br /&gt;&lt;b&gt;지속적으로 가시성을 높이기 위한 노력&lt;/b&gt;이 필요&lt;/li&gt;
&lt;li&gt;유능한 플랫폼 팀일수록 &lt;b&gt;개발자 경험에 집중&lt;/b&gt;하며,&lt;br /&gt;그들이 &lt;b&gt;혜택을 체감할 수 있도록 설계&lt;/b&gt;해야 함&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SRE</category>
      <category>platform engineering</category>
      <category>SRE</category>
      <category>플랫폼 엔지니어링</category>
      <category>플랫폼 엔지니어링 문화</category>
      <author>Jaime.Lee</author>
      <guid isPermaLink="true">https://jaime-note.tistory.com/507</guid>
      <comments>https://jaime-note.tistory.com/507#entry507comment</comments>
      <pubDate>Thu, 12 Jun 2025 10:30:58 +0900</pubDate>
    </item>
    <item>
      <title>자바 마이크로서비스와 함께 살펴보는 SRE 실전 적용기</title>
      <link>https://jaime-note.tistory.com/506</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 들어 &lt;b&gt;서비스의 안정성&lt;/b&gt;에 대해 깊이 고민하게 되었습니다. 기능 개발뿐 아니라 장애 대응, 운영 효율성, 시스템 복원력과 같은 문제들이 점점 더 중요해지고 있기 때문입니다. 특히 팀과 시스템의 규모가 커질수록 '어떻게 하면 더 안정적으로 서비스를 운영할 수 있을까?'라는 질문은 피할 수 없는 과제가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;516&quot; data-start=&quot;292&quot; data-ke-size=&quot;size16&quot;&gt;이러한 고민 속에서 팀 리더가 추천해 준 &lt;b&gt;자바 마이크로서비스를 활용한 SRE&lt;/b&gt;라는 책을 접하게 되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;516&quot; data-start=&quot;292&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 책은 단순한 SRE 이론서가 아니라, &lt;b&gt;실제 자바 마이크로서비스 환경에서 SRE 개념을 어떻게 적용할 수 있을지&lt;/b&gt;에 대해 다양한 사례와 함께 소개하고 있습니다. 내용을 읽다 보니 저희 조직에도 충분히 참고할 만한 지점이 많았고(이미 많이 적용되어 있다는 것도 깨달았고), 책에서 얻은 인사이트를 정리해 블로그에 기록해보기로 마음먹었습니다.&lt;/p&gt;
&lt;p data-end=&quot;516&quot; data-start=&quot;292&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;554&quot; data-start=&quot;518&quot; data-ke-size=&quot;size16&quot;&gt;이 시리즈는 다음과 같은 분들께 도움이 될 수 있을 것 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;657&quot; data-start=&quot;556&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;589&quot; data-start=&quot;556&quot;&gt;자바 기반 마이크로서비스를 운영하고 계신 백엔드 엔지니어&lt;/li&gt;
&lt;li data-end=&quot;620&quot; data-start=&quot;590&quot;&gt;시스템의 신뢰성과 가용성에 대해 고민 중이신 개발자&lt;/li&gt;
&lt;li data-end=&quot;657&quot; data-start=&quot;621&quot;&gt;SRE를 처음 접하거나 실무에 어떻게 녹여야 할지 막막하신 분&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;761&quot; data-start=&quot;659&quot; data-ke-size=&quot;size16&quot;&gt;책을 읽으며 인상 깊었던 내용과 저희 서비스에 적용해볼 수 있는 아이디어, 그리고 직접 실험한 사례까지 함께 정리해볼 예정입니다.&lt;/p&gt;
&lt;p data-end=&quot;761&quot; data-start=&quot;659&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;761&quot; data-start=&quot;659&quot; data-ke-size=&quot;size16&quot;&gt;관심 있으신 분들께 도움이 되었으면 좋겠습니다.  &lt;/p&gt;</description>
      <category>SRE</category>
      <category>java sre</category>
      <category>SRE</category>
      <author>Jaime.Lee</author>
      <guid isPermaLink="true">https://jaime-note.tistory.com/506</guid>
      <comments>https://jaime-note.tistory.com/506#entry506comment</comments>
      <pubDate>Wed, 11 Jun 2025 18:29:23 +0900</pubDate>
    </item>
    <item>
      <title>삼쩜삼에서 부동소수점을 다루는 방법</title>
      <link>https://jaime-note.tistory.com/505</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 블로그에 작성한 글을 제 개인 블로그에도 함께 올립니다.&lt;br /&gt;원본은 &lt;a href=&quot;https://blog.3o3.co.kr/tech-floating-point/&quot;&gt;여기&lt;/a&gt;서 확인하실 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삼쩜삼은 자비스앤빌런즈에서 제공하는 서비스로, 사용자들이 간편하게 세금을 환급받을 수 있도록 돕습니다. 이를 위해 먼저 결정 세액을 계산하고, 최종 환급액을 산출하는 과정이 필요한데, 이 때, 가장 중요한 요소 중 하나는 정확한 계산입니다. 특히, 세금과 관련된 모든 금액을 처리하는 과정에서 발생하는 작은 오차조차 법적 문제나 금전적 손실로 이어질 수 있어, 높은 정밀도가 요구됩니다. 한화(원)와 같은 통화는 소수점이 필요 없지만, 세율 계산 등 소수점 이하의 복잡한 연산을 처리할 때는 부동소수점(Floating Point) 방식을 사용하게 됩니다. 그러나 이 방식은 소수점 이하의 값을 정확히 표현하지 못해 예상치 못한 문제가 발생할 수 있으며, 이는 개발자가 간과하기 쉬운 부분입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;부동소수점 이슈란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동소수점은 컴퓨터가 소수점을 포함한 실수를 표현하는 일반적인 방법입니다. 하지만, 10진수를 2진수로 변환하는 과정에서 정확히 표현되지 못하는 수가 발생하며, 그 결과로 예상과 다른 계산 결과가 나올 수 있습니다. 예를 들어, 0.1과 같은 단순한 수조차도 이진수로는 무한 소수로 표현되기 때문에, 컴퓨터는 이를 근사치로 저장합니다. 이 작은 차이가 반복된 계산에서 점점 더 큰 오차로 누적될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 간단한 자바 코드로 확인해보면,&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import static org.junit.jupiter.api.Assertions.assertEquals;

import java.math.BigDecimal;
import org.junit.jupiter.api.Test;

class BigDecimalTest {

  @Test
  void testFloatingPointIssue() {
    double sum = 0.0;

    for (int i = 0; i &amp;lt; 10; i++) {
      sum += 0.1;
    }

    double expected = 1.0;
    assertEquals(expected, sum, &quot;The sum should be exactly 1.0&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0.1을 10번 더하는 코드이므로 결과가 1일 거 같지만 실제로 수행해보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog-01.png&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1FZvz/btsJJyStYDH/KtZDKekOKS5ylKLPVL4UY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1FZvz/btsJJyStYDH/KtZDKekOKS5ylKLPVL4UY0/img.png&quot; data-alt=&quot;테스트 코드 수행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1FZvz/btsJJyStYDH/KtZDKekOKS5ylKLPVL4UY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1FZvz%2FbtsJJyStYDH%2FKtZDKekOKS5ylKLPVL4UY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1300&quot; height=&quot;256&quot; data-filename=&quot;blog-01.png&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트 코드 수행 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 오차가 생기는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;assertNotEquals를 사용하는 게 더 나은 테스트 코드 작성 방식이지만 에러 로그 출력을 위해 일부러 실패하는 테스트를 작성하였습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;세무에서의 부동소수점 오차 문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세금 계산에서 부동소수점 오차가 발생하면 법적, 재무적 리스크가 생길 수 있습니다. 예를 들어, 세율 계산에서 발생한 미세한 오차가 대규모 거래에서는 큰 금액 차이로 이어질 수 있습니다. 또한, 이러한 오차는 세금 보고서 작성 시 불일치 문제를 유발하여 기업에 불이익을 줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자비스앤빌런즈는 현재 대부분의 코드를 자바로 작성하고 있고, 자바에서 발생할 수 있는 부동소수점 문제 해결을 위해 BigDecimal을 사용하고 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;BigDecimal 이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BigDecimal은 자바에서 부동소수점 연산의 정확도를 보장하기 위해 사용되는 클래스입니다. BigDecimal은 임의의 정밀도를 지원하며, 소수점 이하 자릿수의 오차 없이 숫자를 정확하게 표현할 수 있습니다. 이는 특히 금액 계산에서 중요한데, 부동소수점의 근사치 문제로 인한 오차를 피할 수 있기 때문입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BigDecimal의 주요 특징&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BigDecimal은 아래와 같은 특징을 가지고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정밀한 연산&lt;/b&gt;: BigDecimal은 내부적으로 숫자를 정수로 저장하고, 소수점 위치를 따로 관리합니다. 이 방식은 소수점 이하의 미세한 값까지도 정확하게 처리할 수 있게 해줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;임의의 자릿수 지정 가능&lt;/b&gt;: 계산 과정에서 원하는 만큼의 자릿수를 지정할 수 있으며, 계산 결과를 반올림, 올림, 버림 등 다양한 방식으로 처리할 수 있습니다. 이를 통해 필요한 소수점 이하 자리까지 정확한 계산이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 특징 덕분에 금융, 세무, 회계 등 정확한 계산이 필요한 분야에서 사용됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BigDecimal 사용 예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 BigDecimal을 사용하여 앞서 부동소수점 문제를 해결하는 예제입니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;import static org.junit.jupiter.api.Assertions.assertEquals;

import java.math.BigDecimal;
import org.junit.jupiter.api.Test;

class BigDecimalTest {

  @Test
  void testBigDecimalAddition() {
    BigDecimal sum = BigDecimal.ZERO;
    BigDecimal value = new BigDecimal(&quot;0.1&quot;); // BigDecimal.valueOf(0.1)을 사용해도 동일

    for (int i = 0; i &amp;lt; 10; i++) {
      sum = sum.add(value);
    }

    BigDecimal expected = new BigDecimal(&quot;1.0&quot;);
    assertEquals(expected, sum, &quot;The sum should be exactly 1.0&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실행 결과&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog-02.png&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bILVxU/btsJHUbiReW/3WTaM8J8kUfWlhTJ2iWrI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bILVxU/btsJHUbiReW/3WTaM8J8kUfWlhTJ2iWrI1/img.png&quot; data-alt=&quot;수정된 테스트 코드 수행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bILVxU/btsJHUbiReW/3WTaM8J8kUfWlhTJ2iWrI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbILVxU%2FbtsJHUbiReW%2F3WTaM8J8kUfWlhTJ2iWrI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1286&quot; height=&quot;146&quot; data-filename=&quot;blog-02.png&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;수정된 테스트 코드 수행 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 보듯이, BigDecimal을 사용하면 0.1을 10번 더한 결과가 정확하게 1.0이 됩니다. 이처럼 BigDecimal을 활용하면, 세금 계산에서 발생할 수 있는 미세한 오차를 완벽히 방지할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주의 사항&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지는 많이 알려져있고 이해하는 데 큰 어려움이 없는 내용입니다. 하지만 BigDecimal 객체를 생성하는 과정에서 흔히 하는 실수가 있습니다. 이를 피하기 위해 정확한 방법을 이해하는 것이 중요합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BigDecimal.valueOf(double val)와 new BigDecimal(double val) 의 차이점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BigDecimal에는 다양한 생성자가 있지만, 이 섹션에서는 특히 double 타입을 전달하는 생성자들에 대해 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new BigDecimal(double)을 사용할 때, double 값을 직접 전달하는 것은 위험할 수 있습니다. 앞서 언급한 것처럼 double은 부동소수점 연산의 특성상 근사치로 표현되기 때문에, 이를 그대로 BigDecimal로 변환하면 의도하지 않은 값이 생성될 수 있습니다. 예를 들어,&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;BigDecimal bd = new BigDecimal(0.1);
System.out.println(bd);  // 출력 결과: 0.1000000000000000055511151231257827021181583404541015625&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 0.1이라는 숫자를 BigDecimal로 변환하면, 의도와 다르게 매우 긴 소수점 이하의 숫자가 포함된 값이 생성됩니다. 이는 double의 정확하지 않은 표현이 그대로 전달되기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, BigDecimal.valueOf(double) 메서드는 double 값을 정확하게 BigDecimal로 변환해줍니다. 이 메서드는 double의 부동소수점 표현 문제를 해결하기 위해 내부적으로 String 변환을 사용하여 값을 처리합니다. 따라서, new BigDecimal(double) 대신 BigDecimal.valueOf(double)를 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BigDecimal.valueOf(double)는 내부적으로 new BigDecimal(Double.toString(double))을 호출하여 double 값을 String으로 변환한 후 BigDecimal을 생성합니다. 또한, 이 메서드는 특정 범위의 값에 대해 미리 캐싱된 객체를 반환하여 성능을 최적화합니다(이 캐싱에 대해서는 뒤에서 자세히 설명할 예정입니다). 이로 인해 double 값이 포함된 BigDecimal 객체를 생성할 때, BigDecimal.valueOf(double)는 더 안정적이고 효율적인 방법입니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;BigDecimal bd = BigDecimal.valueOf(0.1);
System.out.println(bd);  // 출력 결과: 0.1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는 BigDecimal.valueOf(0.1)을 사용하여, 예상한 대로 정확하게 0.1이라는 값을 얻을 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html&quot;&gt;Javadoc&lt;/a&gt;에서 위 내용을 확인할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;new BigDecimal(double val)&lt;/b&gt;&lt;br /&gt;double 값을 BigDecimal로 변환하여, 해당 double의 이진 부동소수점 값을 정확히 표현하는 BigDecimal을 생성합니다. 반환된 BigDecimal의 소수 자릿수(scale)는 (10^scale &amp;times; val)이 정수가 되는 가장 작은 값입니다.&lt;br /&gt;주의:&amp;nbsp;이 생성자의 결과는 다소 예측하기 어려울 수 있습니다. 예를 들어, Java에서 new BigDecimal(0.1)을 작성하면, BigDecimal이 정확히 0.1과 같을 것(확대되지 않은 값이 1이고 소수 자릿수가 1인 상태)이라고 가정할 수 있습니다. 하지만 실제로는 0.1000000000000000055511151231257827021181583404541015625와 같습니다. 이는 0.1이 double로 정확히 표현될 수 없기 때문입니다(또는, 어떤 유한한 길이의 이진 분수로도 정확히 표현될 수 없습니다). 따라서 생성자에 전달되는 값은 외견상 0.1과 동일하지 않습니다.&lt;br /&gt;반면, String 생성자는 매우 예측 가능하게 동작합니다. new BigDecimal(&quot;0.1&quot;)를 작성하면, 기대한 대로 정확히 0.1과 같은 BigDecimal이 생성됩니다. 따라서, 이 생성자보다는 String 생성자를 사용하는 것이 일반적으로 권장됩니다.&lt;br /&gt;double을 BigDecimal의 소스로 사용해야 하는 경우, 이 생성자는 double 값을 그대로 반영한 BigDecimal을 생성하며, double의 정확한 이진 부동소수점 값을 표현합니다. 그러나 이 변환은 double 값을 String으로 변환한 후 BigDecimal(String) 생성자를 사용하는 것과는 다른 결과를 가져옵니다. 만약 double의 값을 정확히 표현하는 BigDecimal을 얻고자 한다면, static valueOf(double) 메서드를 사용하는 것이 적절합니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;BigDecimal.valueOf(double val)&lt;/b&gt;&lt;br /&gt;double 값을 Double.toString(double) 메서드가 제공하는 double의 표준 문자열 표현을 사용하여 BigDecimal로 변환합니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;BigDecimal의 캐싱 기능&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BigDecimal.valueOf(long val) 메서드는 캐싱을 사용하여 자주 사용되는 값을 재활용합니다. 특히, 0에서 10 사이의 정수 값에 대해 캐싱이 이루어집니다. 이 범위 내의 값에 대해 BigDecimal.valueOf를 호출하면, 새로운 객체를 생성하는 대신, 이미 생성된 객체를 반환합니다. 이는 메모리 사용을 줄이고, 성능을 향상시키는 데 기여합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예제 코드&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;import static org.junit.jupiter.api.Assertions.assertSame;

import java.math.BigDecimal;
import org.junit.jupiter.api.Test;

class BigDecimalTest {

  @Test
  void testBigDecimalCaching() {
    BigDecimal a = BigDecimal.valueOf(10);
    BigDecimal b = BigDecimal.valueOf(10);

    assertSame(a, b, &quot;The two BigDecimals should be the same object&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실행 결과&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog-03.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsiodH/btsJJWZxVrh/0zaElfxNwUfwcGqyBmg43k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsiodH/btsJJWZxVrh/0zaElfxNwUfwcGqyBmg43k/img.png&quot; data-alt=&quot;예제 코드 수행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsiodH/btsJJWZxVrh/0zaElfxNwUfwcGqyBmg43k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsiodH%2FbtsJJWZxVrh%2F0zaElfxNwUfwcGqyBmg43k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1284&quot; height=&quot;160&quot; data-filename=&quot;blog-03.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예제 코드 수행 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서 BigDecimal.valueOf(10)을 두 번 호출했을 때, 두 객체가 같은 인스턴스를 참조하는 것을 확인할 수 있습니다. 이는 BigDecimal이 10에 대해 캐싱된 객체를 반환하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 0~10 사이의 정수가 캐싱되는 이유는 valueOf(long val) 메서드의 구현 내용에서 확인할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// Cache of common small BigDecimal values.
private static final BigDecimal ZERO_THROUGH_TEN[] = {
    new BigDecimal(BigInteger.ZERO,       0,  0, 1),
    new BigDecimal(BigInteger.ONE,        1,  0, 1),
    new BigDecimal(BigInteger.TWO,        2,  0, 1),
    new BigDecimal(BigInteger.valueOf(3), 3,  0, 1),
    new BigDecimal(BigInteger.valueOf(4), 4,  0, 1),
    new BigDecimal(BigInteger.valueOf(5), 5,  0, 1),
    new BigDecimal(BigInteger.valueOf(6), 6,  0, 1),
    new BigDecimal(BigInteger.valueOf(7), 7,  0, 1),
    new BigDecimal(BigInteger.valueOf(8), 8,  0, 1),
    new BigDecimal(BigInteger.valueOf(9), 9,  0, 1),
    new BigDecimal(BigInteger.TEN,        10, 0, 2),
};

// 이하 생략

public static BigDecimal valueOf(long val) {
  if (val &amp;gt;= 0 &amp;amp;&amp;amp; val &amp;lt; ZERO_THROUGH_TEN.length)
    return ZERO_THROUGH_TEN[(int)val];
  else if (val != INFLATED)
    return new BigDecimal(null, val, 0, 0);
  return new BigDecimal(INFLATED_BIGINT, val, 0, 0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 배열은 0부터 10까지의 정수를 미리 생성하여 배열에 할당한 것으로, 이로 인해 valueOf(long val) 메서드가 해당 범위의 값을 요청받으면 새로운 객체를 생성하는 대신 미리 생성된 객체를 반환하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 캐싱은 BigDecimal.valueOf(long) 메서드에서만 제공됩니다. new BigDecimal(long)이나 new BigDecimal(double)을 사용하면 캐싱이 적용되지 않으며, 항상 새로운 객체가 생성됩니다. 따라서, 가능한 경우 BigDecimal.valueOf(long)을 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;BigDecimal 사용 시 고려해야 할 단점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BigDecimal은 정밀한 계산을 위해 매우 유용하지만, 몇 가지 단점도 고려해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;성능 문제&lt;/b&gt;: BigDecimal은 숫자를 내부적으로 문자열로 처리하기 때문에, 기본 원시 타입(double, long 등)에 비해 연산 속도가 느릴 수 있습니다. 이는 특히 대규모 계산 작업에서 성능 저하로 이어질 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 사용&lt;/b&gt;: BigDecimal은 원시 타입보다 더 많은 메모리를 사용합니다. 복잡한 계산이 많은 경우, 메모리 사용량이 증가할 수 있으며, 이는 시스템 자원에 부담을 줄 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복잡성 증가&lt;/b&gt;: BigDecimal은 다루기 복잡할 수 있으며, 개발자가 올바른 사용법을 숙지하지 않으면 의도하지 않은 결과를 초래할 수 있습니다. 특히, 반올림 모드나 소수 자릿수 처리에 주의를 기울여야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;추가 고려 사항&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 장단점과 도메인적인 특징 때문에 실무에선 아래와 같은 사항들을 추가로 고려해볼 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;가장 작은 단위 사용&lt;/b&gt;: 통화 계산에서는 정확성을 높이기 위해 가장 작은 단위로 long형을 사용하여 금액을 저장하는 것이 좋습니다. 한국의 원(₩)은 더 작은 단위가 없기 때문에 원 단위로 처리하면 되고, 미국처럼 센트(&amp;cent;)를 사용하는 경우, 달러($) 대신 센트로 금액을 저장해 소수점 처리를 피할 수 있습니다. 예를 들어, 1.25달러를 표현하려면 125센트를 long 타입으로 저장하여 소수점 연산으로 인한 오류를 방지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기본 연산&lt;/b&gt; long &lt;b&gt;타입 사용&lt;/b&gt;: 더하기나 빼기 같은 간단한 연산은 long형을 사용하는 것이 성능 면에서 효율적입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과는 정수 사용&lt;/b&gt;: 곱셈이나 나눗셈 결과는 반올림이나 반내림을 통해 정수로 처리하여 계산의 정확성을 유지해야 합니다. 세무 도메인에서는 10원 단위 절삭(절사), 소수점 한 자리에서 반올림, 절상 등을 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;52비트 범위 안에서 계산&lt;/b&gt;: 계산 결과는 항상 double의 52비트 범위 안에서 처리되도록 유의해야 합니다. 대부분의 금전적 계산은 이 범위 내에서 처리되지만, 계산 중에 값이 이 범위를 넘어가는 경우도 있을 수 있습니다. 이럴 때는 숫자를 문자열로 변환한 후, BigDecimal을 사용하여 더 정밀하게 계산하는 것이 좋습니다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;나눗셈/곱셈 시&lt;/b&gt; MathContext &lt;b&gt;사용&lt;/b&gt;: BigDecimal로 나눗셈이나 곱셈을 할 때는 MathContext.DECIMAL64와 같은 옵션을 사용해 무한 소수점이 나오지 않도록 설정해야 합니다. 기본값은 MathContext.UNLIMITED인데, 무한 소수점이 발생하면 ArithmeticException이 발생할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.math.BigDecimal;

public class ArithmeticExceptionExample {
    public static void main(String[] args) {
        BigDecimal dividend = BigDecimal.ONE;
        BigDecimal divisor = BigDecimal.valueOf(3);

        try {
            // MathContext 없이 나눗셈을 시도 (무한 소수 발생)
            BigDecimal result = dividend.divide(divisor);
            System.out.println(result);
        } catch (ArithmeticException e) {
            System.out.println(&quot;ArithmeticException 발생: &quot; + e.getMessage());
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 실행하게되면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog-04.png&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br4OBr/btsJJN2PMbY/IecfBYI98uGrPGDzHK9OeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br4OBr/btsJJN2PMbY/IecfBYI98uGrPGDzHK9OeK/img.png&quot; data-alt=&quot;예제 코드 수행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br4OBr/btsJJN2PMbY/IecfBYI98uGrPGDzHK9OeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr4OBr%2FbtsJJN2PMbY%2FIecfBYI98uGrPGDzHK9OeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1552&quot; height=&quot;60&quot; data-filename=&quot;blog-04.png&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;60&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예제 코드 수행 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 에러가 발생하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MathContext.DECIMAL64는 국제 표준으로 유효 자릿수를 16자리로 제한하여 소수점이 무한히 이어지는 문제를 막을 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;BigDecimal result = dividend.divide(divisor, MathContext.DECIMAL64);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog-05.png&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciIKcO/btsJJMiDg02/LHKykANcHNFaw0KQJKLa5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciIKcO/btsJJMiDg02/LHKykANcHNFaw0KQJKLa5K/img.png&quot; data-alt=&quot;수정 후 수행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciIKcO/btsJJMiDg02/LHKykANcHNFaw0KQJKLa5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciIKcO%2FbtsJJMiDg02%2FLHKykANcHNFaw0KQJKLa5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;310&quot; height=&quot;56&quot; data-filename=&quot;blog-05.png&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;수정 후 수행 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자비스앤빌런즈와 같은 택스테크 스타트업에서 BigDecimal의 활용은 필수적입니다. 부동소수점 오차로 인한 법적 리스크를 피하고, 고객에게 신뢰할 수 있는 세금 계산 서비스를 제공하기 위해, 코드 작성 시 항상 BigDecimal을 적절히 사용하는 것이 중요합니다. BigDecimal의 정확한 계산과 캐싱 기능을 활용하면, 금전적인 오차를 방지하고 성능을 최적화할 수 있습니다. 결국, 정확한 계산은 고객의 신뢰를 쌓는 데 중요한 역할을 하며, 이는 택스테크 솔루션의 핵심 가치라고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 BigDecimal 사용시 고민할 필요 없이 &lt;b&gt;숫자 타입(&lt;/b&gt;long&lt;b&gt;,&lt;/b&gt; double&lt;b&gt;)은&lt;/b&gt; BigDecimal.valueOf&lt;b&gt;로, 문자열 타입(&lt;/b&gt;char[]&lt;b&gt;,&lt;/b&gt; String&lt;b&gt;)은&lt;/b&gt; new BigDecimal&lt;b&gt;로 생성해야 정확성을 유지&lt;/b&gt;할 수 있습니다.&lt;/p&gt;</description>
      <category>Java</category>
      <author>Jaime.Lee</author>
      <guid isPermaLink="true">https://jaime-note.tistory.com/505</guid>
      <comments>https://jaime-note.tistory.com/505#entry505comment</comments>
      <pubDate>Mon, 23 Sep 2024 15:23:36 +0900</pubDate>
    </item>
    <item>
      <title>[macOS] 크롬 자동 업데이트 끄기</title>
      <link>https://jaime-note.tistory.com/504</link>
      <description>&lt;p&gt;요즘 크롬 업데이트가 너무 빈번하게 되는 것 같아서 업데이트를 자동으로 하지 않도록 설정하는 방법을 알아보았습니다.&lt;/p&gt;
&lt;p&gt;저는 폰에서 badge 표시도 싫어해서 카톡이나 이메일, 그 외 모든 알림도 빨리 제거해야하는 성격인데 크롬창에 자꾸 업데이트 알림이 떠있어서 너무 거슬려서 방법을 찾아보았는데 매우 간단하네요.&lt;/p&gt;
&lt;p&gt;터미널에 아래 명령어를 입력하면 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;defaults write com.google.keystone.agent checkinterval 0&lt;/code&gt;&lt;/pre&gt;</description>
      <category>macOS</category>
      <category>Chrome</category>
      <category>macos</category>
      <category>맥 크롬 업데이트 끄기</category>
      <category>크롬 업데이트 끄기</category>
      <category>크롬 자동 업데이트 끄기</category>
      <author>Jaime.Lee</author>
      <guid isPermaLink="true">https://jaime-note.tistory.com/504</guid>
      <comments>https://jaime-note.tistory.com/504#entry504comment</comments>
      <pubDate>Tue, 26 Mar 2024 10:00:42 +0900</pubDate>
    </item>
    <item>
      <title>상위 1% 엔지니어의 간단한 습관</title>
      <link>https://jaime-note.tistory.com/503</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅은 &lt;a href=&quot;https://engineercodex.substack.com/p/7-simple-habits-of-the-top-1-of-engineers&quot;&gt;원글&lt;/a&gt;을 번역한 글입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;나는 FAANG과 같은 대규모 기업과 스타트업과 같은 작은 기업에서 뛰어난 엔지니어들과 함께 일한 경험이 있습니다.&lt;br /&gt;이 중 일부 엔지니어들은 자신의 회사를 시작하거나 웹을 변화시키는 개발을 주도했으며(예: Vercel), 오늘날 큰 기술 기업에서 수십 억 달러 가치의 프로젝트를 주도하고 있습니다.&lt;br /&gt;내가 그들과 일하면서 주목한 것은 그들이 생산한 코드에서 일부 중복되는 습관을 가지고 있다는 것입니다.&quot;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴퓨터가 아닌 사람을 위한 코드를 작성하라&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;어떤 바보도 컴퓨터가 이해할 수 있는 코드를 작성할 수 있습니다. 좋은 프로그래머는 인간이 이해할 수 있는 코드를 작성합니다.&quot; - 마틴 파울러&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 컴퓨터뿐만 아니라 인간을 위한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 팀의 엔지니어들을 위한 것입니다. 그들이 코드를 읽고 유지 관리하며 코드를 기반으로 더 많은 것을 구축합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 사용자를 위한 것입니다. 그것이 핸드폰을 사용하는 아이, API를 호출하는 개발자 또는 여러분 자신이 사용하는 모든 사람을 대상으로 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 아는 최고의 엔지니어들은 제품 중심적이며, 먼저 인간을 위한 문제 해결을 고려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 알던 최고의 엔지니어들은 항상 코드의 가치를 모든 대상을 위해 평가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 대상을 놓친다면, 그 코드는 제품으로 들어가지 않았습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드 자체로부터 분리하라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놀라운 엔지니어들은 코드 자체에 얽매이지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들은 코드를 삭제하고 처음부터 다시 시작해도 두렵지 않았습니다. 결과적으로 전반적으로 더 나아질 경우에는 90% 정도 완료한 상태에서도 그렇게 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 개인적인 것이 아니므로 피드백을 수용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 완벽하지 않습니다. 아무도 완벽한 코드에 관심을 가지지 않습니다. 그들은 변화를 제공하는 코드에 관심이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신의 코드에 대한 감정적인 연결을 끊기 위한 가장 좋은 방법은 20년 후에는 대부분의 코드가 기술 부채로 남거나 폐기되거나 다시 작성될 가능성이 높다는 것을 깨닫는 것입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;일관된 표준 사용하라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 작성할 때, 일관된 코딩 표준과 스타일을 따르세요. 일관성은 미래의 여러분과 팀원 모두에게 코드를 더 쉽게 읽고 이해하게 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일관된 스타일 가이드는 팀과 코드베이스 모두가 더 쉽게 확장될 수 있도록 합니다. 이것이 Meta와 Google과 같은 회사가 코드베이스가 시간이 지남에도 읽기 어렵고 유지하기 어렵지 않게 빠르게 많은 코드를 배포하는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 아는 모든 최고의 엔지니어들은 팀의 코드 표준을 내면화하고 그 이점을 알고 가능한 한 따르곤 했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Google은 깨끗한 코드를 유지하기 위한 가독성 프로세스가 있습니다.&lt;/li&gt;
&lt;li&gt;Google은 일부 스타일 가이드를 오픈 소스로 공개하고 있습니다. (&lt;a href=&quot;https://google.github.io/styleguide/&quot;&gt;링크&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Meta는 일부 오픈 소스 코드를 위한 C++ 스타일 가이드가 있습니다. (&lt;a href=&quot;https://github.com/facebook/hhvm/blob/master/hphp/doc/coding-conventions.md&quot;&gt;링크&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;팁: 이미 설정된 경우를 제외하고 팀에게 코드 포매터(린터, linter)를 설정하는 것은 분명히 가치가 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;간단한 코드를 작성하라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 알고 있는 모든 엘리트 엔지어들은 코드를 생산했습니다. 이 코드는 생산하기는 복잡할 수 있었지만 끝에 이르면 읽고 이해하기 쉬웠습니다. 내가 이것에 대해 가장 좋아하는 말은 그들의 코드가 '미적으로 매력적'이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들의 코드는 깨끗하고 조직적이며 논리적이었습니다. 코드에서 내린 각 결정은 이치에 맞았고, 그렇지 않을 때는 코드 내에서 잘 문서화되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깨끗한 코드를 작성하는 좋은 방법 중 하나는 SOLID 원칙과 같은 원칙을 따르는 것입니다. 이 원칙들은 처음에 객체지향 프로그래밍(OOP)을 고려하여 디자인되었지만, 일반적인 프로그래밍에도 확장 가능합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;단일 책임 원칙: 클래스는 단 하나의 책임만 가져야 합니다.&lt;/li&gt;
&lt;li&gt;개방-폐쇄 원칙: 소프트웨어 객체(클래스, 모듈 등)는 확장에는 열려 있지만 수정에는 닫혀 있어야 하며, 예측 가능하고 유지보수 가능한 코드를 허용해야 합니다.&lt;/li&gt;
&lt;li&gt;리스코프 치환 원칙: 서브타입은 그들의 베이스 타입에 영향을 미치지 않고 대체 가능해야 합니다.&lt;/li&gt;
&lt;li&gt;인터페이스 분리 원칙: 코드는 사용하지 않는 큰 인터페이스에 의존해서는 안 되며, 대신 패키지는 작고 특정한 인터페이스를 포함하고 가져올 수 있어야 합니다.&lt;/li&gt;
&lt;li&gt;의존성 역전 원칙: 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 양쪽 모두 추상화에 의존하도록하여 더 유연하고 결합이 낮은 시스템 디자인을 촉진해야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것의 한 예는 네이밍(naming)입니다. 좋은 네이밍은 마법스러운 것이 아니라, 명확한 구분, 설명적인 함수 이름 및 이해하기 쉬운 변수를 가져야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;의외성을 허용하지 마라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 놀라움을 일으키지 않아야 합니다. 이는 코드 원칙을 따르고 적절한 테스트를 작성함으로써 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 코드는 예측 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 코드의 명확성과 예측 가능성을 강제합니다. 그것은 신뢰를 제공합니다. 좋은 자동화된 테스트는 팀이 보이지 않는 것을 망치지 않고 코드를 변경할 수 있도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 테스트 유형은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;개별 구성 요소 및 격리된 함수에 대한 유닛 테스트&lt;/li&gt;
&lt;li&gt;여러 구성 요소 간의 상호 작용을 검사하는 통합 테스트&lt;/li&gt;
&lt;li&gt;사용자 관점에서 전체 시스템 기능을 평가하는 엔드 투 엔드 테스트&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 간단해야 합니다. 실패한 테스트를 읽을 때 무엇이 잘못되었는지 쉽게 식별할 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇을 테스트하지 말아야 하는지 알아야 하는 것도 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 엔드 투 엔드 테스트의 노력이 프로그램의 실제 이점을 초과하면, 해당 테스트는 신중한 문서화, 모니터링 및 코드 소유자와 같은 적절한 사람에게 경보를 대체해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 테스트는 코드 내의 구현 세부 정보를 테스트하지 않아야 합니다. 예를 들어, 프론트엔드 코드에서 특정 CSS 선택기를 테스트하는 대신 데이터 속성을 사용하거나 스크린샷 테스트를 수행하는 것과 같이 코드 내의 구현 세부 정보를 테스트해서는 안됩니다.&quot;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자주 소통하라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위대한 시스템은 혼자서는 구축되지 않았습니다. 우수한 엔지니어들은 설계 검토를 거치고 피드백을 요청하며 코드의 초기 설계를 계속해서 개선했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 사람은 다른 사람들이 채울 수 있는 지식의 공백을 가지고 있습니다. 새로운 관점은 종종 코드를 더 명확하게 만들거나 이전에 생각하지 못한 새로운 접근 방식을 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최고의 엔지니어들은 의사소통과 협업 능력을 모두 가지고 있었으며, 더 나은 최종 결과를 위해 함께 일할 시간을 아낄 염려가 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 동료에게 문서 검토를 요청하거나 중요한 풀 리퀘스트에 추가 코드 리뷰어를 지정하는 것과 같이 간단한 일로 할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;빠르게... 그리고 느리게 코딩하라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 아는 최고의 엔지니어들은 빠르게 프로젝트를 완료합니다. 느리게 코딩해서요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상하게 들리죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 모든 원칙과 습관은 코딩의 전체 첫 번째 단계에 더 많은 시간을 추가합니다. 그러나 이러한 원칙을 따르면 엔지니어들은 프로젝트의 진행을 단계별로 나아갈 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준을 사용하고 적절한 테스트를 수행하고 원칙을 사용하고 자주 의사소통하는 시간을 들이는 것으로 자신들을 장기적으로 더 많은 시간을 절약합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 개인적인 경험으로 말하자면, 인턴과 주니어 엔지니어로 일했을 때 경험한 대체 방법은 3단계를 빠르게 나아가려다가 장애물에 부딪혀 5단계를 되돌아야 했습니다. 아마도 많은 다른 사람들도 비슷한 경험을 했을 것입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;맹목적으로 규칙을 따르지 마라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 '규칙'과 '원칙'은 그저 지침일 뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 것이 깔끔하고 완벽하게 지침에 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 작성 중인 코드는 그냥 그 원을 맞출 수 없는 사각형일 수 있습니다. 괜찮습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 경우, 코드가 특정한 방식으로 작성된 이유를 문서화해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않으면 미래의 여러분과 같은 누군가는 코드를 나중에 보고 '와, 나는 그 때 멍청했군. 왜 이것이 우리의 표준을 따르지 않는 걸까?'라고 생각할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 그들은 표준에 맞게 다시 코딩하기 위해 20시간을 소비하고 결국 이전과 동일한 결론에 도달할 것입니다. 익숙한 일이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 개발의 현실은 모든 코드가 깨끗하거나 완벽하게 규칙을 따르지 않을 수 있다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 그것은 일관되고 깨끗하며 이해 가능하며 테스트 가능하며 가치 있게 될 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최고의 엔지니어에게서 발견한 또 다른 패턴들:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나 이상의 분야에서 깊은 도메인 지식. 내가 메모한 모든 엔지니어는 프론트엔드 인프라, 분산 시스템 또는 깨끗한 UI와 같은 특정 분야에서 집중하고 전문가가 되어 현재는 자신의 분야 최상위에 있습니다.&lt;/li&gt;
&lt;li&gt;자주 그리고 적절하게 자신을 마케팅. 이러한 엔지니어들은 전혀 감추지 않았습니다. 그들의 팀 구성원과 협업한 모든 사람은 그들의 가치와 전문성을 알고 있었습니다. 이는 적절한 방법으로 자신을 마케팅하고 고영향 프로젝트에 참여함의 조합을 통해 나타났습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>ETC</category>
      <category>개발자</category>
      <category>습관</category>
      <category>원칙</category>
      <author>Jaime.Lee</author>
      <guid isPermaLink="true">https://jaime-note.tistory.com/503</guid>
      <comments>https://jaime-note.tistory.com/503#entry503comment</comments>
      <pubDate>Tue, 24 Oct 2023 03:50:40 +0900</pubDate>
    </item>
  </channel>
</rss>