<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[RSS Feed]]></title><description><![CDATA[Backend Engineer]]></description><link>https://steadiness.dev</link><generator>GatsbyJS</generator><lastBuildDate>Sun, 18 Jan 2026 16:27:03 GMT</lastBuildDate><item><title><![CDATA[URL Shortener]]></title><description><![CDATA[URL Shortener란? URL Shortener는 긴 URL을 짧은 URL로 변환해주는 서비스다.  이 글에서는 URL Shortener에 대해 정리해보고, 구현 시 고민했던 문제들을 살펴본다. 기능 정의 URL Shortener…]]></description><link>https://steadiness.dev/url-shortener-system-design/</link><guid isPermaLink="false">https://steadiness.dev/url-shortener-system-design/</guid><pubDate>Mon, 19 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;url-shortener란&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#url-shortener%EB%9E%80&quot; aria-label=&quot;url shortener란 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;URL Shortener란?&lt;/h2&gt;
&lt;p&gt;URL Shortener는 긴 URL을 짧은 URL로 변환해주는 서비스다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 536px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a4bcc6ccd1846d8b160c5fa6b96d8a53/2d920/url-shortener-concept.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 85.88957055214723%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAABZElEQVR42qWUi27CMAxF8/+/xVcgIVEKbdNCC/RB6YOaHGuZNsRGBZaukjj2jZ3YMfv9XqrzWZq6lvoBVVUpzm7/EWVZ6p4fQZ7nYrIsE9uOEtlUCqc4Ho/SNI0aFkWhIwTM2QM4gtPpJIfDQdI0VT3BGRT1MEmBsXPCGUMc2MOQNXrmXsdBzLFjfrlcdG30ZKf0Dj8jYA8jHwVrkH9lwkEePgMzTZP8hdvtpuMwDBKGoSBe92jjYWSG4BRF0Uu7l4QYINfrVZbL5Tf5fzIrQgjX6/WvQ94ipLZWq5WmG8exbLdb2e120vf9e4Rd1+kLQgyRL6dxHD9LmTRpgDli5pDxEHTBx3fohRQ3m81nhF3XS9u2eo90ymKx0AL3OuazCXFKkkRi92GE7mUT98JBEOg9+pbk1Z/VpHlWyCXfknvR1P1CQWQltYlYa7WfKSHsuFMOnh0hEeBEudDHRAwhQA+eRXgHEY0tsXVAJ+YAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;URL Shortener Concept&quot;
        title=&quot;&quot;
        src=&quot;/static/a4bcc6ccd1846d8b160c5fa6b96d8a53/2d920/url-shortener-concept.png&quot;
        srcset=&quot;/static/a4bcc6ccd1846d8b160c5fa6b96d8a53/222b7/url-shortener-concept.png 163w,
/static/a4bcc6ccd1846d8b160c5fa6b96d8a53/ff46a/url-shortener-concept.png 325w,
/static/a4bcc6ccd1846d8b160c5fa6b96d8a53/2d920/url-shortener-concept.png 536w&quot;
        sizes=&quot;(max-width: 536px) 100vw, 536px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이 글에서는 URL Shortener에 대해 정리해보고, 구현 시 고민했던 문제들을 살펴본다.&lt;/p&gt;
&lt;h2 id=&quot;기능-정의&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EA%B8%B0%EB%8A%A5-%EC%A0%95%EC%9D%98&quot; aria-label=&quot;기능 정의 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;기능 정의&lt;/h2&gt;
&lt;p&gt;URL Shortener는 다음의 두 가지 기능을 갖는다.&lt;/p&gt;
&lt;h3 id=&quot;1-short-url-생성&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-short-url-%EC%83%9D%EC%84%B1&quot; aria-label=&quot;1 short url 생성 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. Short URL 생성&lt;/h3&gt;
&lt;p&gt;입력받은 URL로 Short URL을 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1c77e60dd85398616798c50cdd345700/f6386/short-url-creation.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 33.74233128834356%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA60lEQVR42n2S2aqGMAyE+/7vJ3ingvu+75qfL9DixeEEYtAmM5mpRv6I53m01nUtaZq6Wpal3Pct/4U5jkOWZZF5nmXbNuH9fV8HGIahFEUhURRJnudyXZeeresqXddJ0zRa+76X8zzFALbvu1hgGgCggY3YLMsyzSRJ9AyScRydGhagMmv4CAONBOBsQoWRxu8Q3yBp21ZJmKOihjR2/WmaFJABz/MU1BLBDAADbM4ZFkHKLIlCBeQBg5XGIKAEFgRBIHEci+/7TgVeYwdLDMOgxNZPgwxujrTS7KXQDFFVVe62v3+CteFryw8rIBp+2zvSWwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Short URL Creation&quot;
        title=&quot;&quot;
        src=&quot;/static/1c77e60dd85398616798c50cdd345700/a6d36/short-url-creation.png&quot;
        srcset=&quot;/static/1c77e60dd85398616798c50cdd345700/222b7/short-url-creation.png 163w,
/static/1c77e60dd85398616798c50cdd345700/ff46a/short-url-creation.png 325w,
/static/1c77e60dd85398616798c50cdd345700/a6d36/short-url-creation.png 650w,
/static/1c77e60dd85398616798c50cdd345700/f6386/short-url-creation.png 686w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-short-url-접속-시-원본-url-리다이렉트&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-short-url-%EC%A0%91%EC%86%8D-%EC%8B%9C-%EC%9B%90%EB%B3%B8-url-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8&quot; aria-label=&quot;2 short url 접속 시 원본 url 리다이렉트 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. Short URL 접속 시 원본 URL 리다이렉트&lt;/h3&gt;
&lt;p&gt;Short URL로 접속하면 원본 URL로 리다이렉트한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d99f41255fcf951ca40003e0a657f9d6/f6386/short-url-redirect.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 28.22085889570552%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA3ElEQVR42k2R16rFUAhE8///l7yF9N47qR6W4OUKg5HtjKNxpmmScRxlGAbNy7II8b6vJEkiXddpbppG4jiWbdv0fZ5nfavrWtq2Vf66ruJANDzPIwwoy1IFoiiSLMtUCFAXRSGu60rf9/J9n9z3rTwCUYcPmiASOAjDUIVxS23AAaAXZ2SQpqlqYEIFr+tSEPu+i+d5SvjvEhI1Q1iRjDMcwrXtHHNCZg3WPc9TGxD3fV/yPJcgCLQHAdyyHjXZ7q8OIdokm1ZV1d+PYBXuR40wTqmP49C7Gw9Q/wBLm8jKFDX9CAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Short URL Redirect&quot;
        title=&quot;&quot;
        src=&quot;/static/d99f41255fcf951ca40003e0a657f9d6/a6d36/short-url-redirect.png&quot;
        srcset=&quot;/static/d99f41255fcf951ca40003e0a657f9d6/222b7/short-url-redirect.png 163w,
/static/d99f41255fcf951ca40003e0a657f9d6/ff46a/short-url-redirect.png 325w,
/static/d99f41255fcf951ca40003e0a657f9d6/a6d36/short-url-redirect.png 650w,
/static/d99f41255fcf951ca40003e0a657f9d6/f6386/short-url-redirect.png 686w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;data-modeling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#data-modeling&quot; aria-label=&quot;data modeling permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Data Modeling&lt;/h2&gt;
&lt;p&gt;기능 요구사항을 충족하려면 어떤 데이터를 저장해야 할까?&lt;/p&gt;
&lt;p&gt;먼저 원본 URL, 이와 매핑되는 Short URL을 저장하는 테이블이 하나 필요하다.&lt;/p&gt;
&lt;p&gt;URL Shortener는 하나의 원본 URL로 여러 개의 Short URL을 생성할 수 있는데, 이를 통해 각 유입 경로별 클릭 수와 같은 마케팅 지표도 수집 가능하다.&lt;/p&gt;
&lt;p&gt;반영하면 &lt;code class=&quot;language-text&quot;&gt;URL 매핑&lt;/code&gt;과 &lt;code class=&quot;language-text&quot;&gt;통계 지표 수집&lt;/code&gt; 이라는 관심사를 분리하여 테이블을 분리할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 488px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5f64fdf8f28690e62b606424bcf15a38/bd48c/data-modeling.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 59.50920245398773%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABbElEQVR42p2S3Y6CQAyF5/2fTW5IFBUNqCio/AiiEVC7+Zo0cVeutsmEmeH09PR03Ol0kv1+L2maymq10v1isZAsy4R4v9+6iMfjIUEQyPF4lN1uJ+fzWZIk0TP3URSJK8tSCcIwlOl0Kuv1WmazmYKN0GIYBjkcDkpCThzHSkQOe3JcURTSdd0vNSSi+JPM9uBvt9sXniL3+13cdrtVZSikXSpOJhNZLpfyGUZolszncy0KzvM88X1f6roWh8y+77UKX3xq21bBr9fri5DWwIC1BQ5h2rIZywWDwODL5SJ4+3w+vwgxHiyrqiodTp7nmsdyHCDAm+v1qnuIKDRGuNlstDVwTdPonhnwWlQhrVHJLiAG+JfQginz354aeEi5Vw+RzAGAgVCK8jGFFEYlXmIVuZAiAO8dfUPAgQo8CYwGNKaQ/3QEllx852wdqUKeAWab4YBRMkZoD5yiePc5acLZRFEJEQt13P8nfgCzH5L4A2nkgAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Data Modeling&quot;
        title=&quot;&quot;
        src=&quot;/static/5f64fdf8f28690e62b606424bcf15a38/bd48c/data-modeling.png&quot;
        srcset=&quot;/static/5f64fdf8f28690e62b606424bcf15a38/222b7/data-modeling.png 163w,
/static/5f64fdf8f28690e62b606424bcf15a38/ff46a/data-modeling.png 325w,
/static/5f64fdf8f28690e62b606424bcf15a38/bd48c/data-modeling.png 488w&quot;
        sizes=&quot;(max-width: 488px) 100vw, 488px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;short-url-생성-방식&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#short-url-%EC%83%9D%EC%84%B1-%EB%B0%A9%EC%8B%9D&quot; aria-label=&quot;short url 생성 방식 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Short URL 생성 방식&lt;/h2&gt;
&lt;p&gt;Short URL은 고유성을 보장해야 한다. 기본 구조는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 423px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6e297017f6da0202241c4195f42a571f/f687d/short-url-structure.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 23.926380368098158%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAABGklEQVR42m2P0U+CYBTF+f//jl5LNHAlXzVfbOspW2u6hQiKoHyi+CEgKr/Qyl46u3c7O/fu3HO11/mGuVyi1KZuxeFw4AQpJY7jUBQF62SNShV5njMcDinLkm2aMh47tZaRZRnBbHaea1fvPs17QdswMEwTXdcRQtDr9TANE9ERCEvwYH1rlnikfWdx3WjSEU80mre0jDY3eovJ1EfL9kd29cX96ep2SxiG56RxHBPnK0K1INkp7NDh07Uhl3XZ7DceVVnzZZ0ymZDHDociQeMHVVWd+5efMA2nDOwhru/h+C7lcV8bLhi9dbH7XbyPZ7zBC8GoT5VHcNz9Gf5nnKqUMAjx61dkJC87kVzhBxGrRDFfSFzPv8y+AMvCcTRAfZ9xAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Short URL Structure&quot;
        title=&quot;&quot;
        src=&quot;/static/6e297017f6da0202241c4195f42a571f/f687d/short-url-structure.png&quot;
        srcset=&quot;/static/6e297017f6da0202241c4195f42a571f/222b7/short-url-structure.png 163w,
/static/6e297017f6da0202241c4195f42a571f/ff46a/short-url-structure.png 325w,
/static/6e297017f6da0202241c4195f42a571f/f687d/short-url-structure.png 423w&quot;
        sizes=&quot;(max-width: 423px) 100vw, 423px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Short URL에는 Base62 인코딩을 적용하는데, 다음의 문자들로 구성된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 475px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/cacef6ef4b7c4a8de11b10a90b76941a/466da/base62-characters.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.901840490797547%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA60lEQVR42lWQ2YqEQAxF/f8/U/BFGlxaRQX3pdV2f8lwAtUwBZeKSd3FWHVdyziOct+37Psux3Ho/f1+9R6GQT6fj/R9rzBzbnCep3Lneda3VlEUsm3bPyDG4+d5JAxDcV1XHMeRIAiUbMSWZVEgBvI8FyuOYynLUqqqkrZtNQVDyE3TqDgmpLquS7+zLBN4JhWAS8/quk44xpHEiL7fbxUgEWuBFEXR7w8wQxA+BtSIWiTjmD1BpoaMIIBszExS9r6uq0zT9JvrDhGhwYAGjsC2bfF9X93ZTZIkWqdpKq/XSzzP+60KcTho/AEfasJ/Ax46uAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Base62 Characters&quot;
        title=&quot;&quot;
        src=&quot;/static/cacef6ef4b7c4a8de11b10a90b76941a/466da/base62-characters.png&quot;
        srcset=&quot;/static/cacef6ef4b7c4a8de11b10a90b76941a/222b7/base62-characters.png 163w,
/static/cacef6ef4b7c4a8de11b10a90b76941a/ff46a/base62-characters.png 325w,
/static/cacef6ef4b7c4a8de11b10a90b76941a/466da/base62-characters.png 475w&quot;
        sizes=&quot;(max-width: 475px) 100vw, 475px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;만약 해시 값을 통해 생성하면 해시 충돌에 의해 중복이 발생할 수 있으므로 별도 처리가 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 274px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6216006eb9157c68036812d5b803a9f6/d3fa7/hash-collision-handling.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 224.53987730061348%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAtCAYAAACu/EtoAAAACXBIWXMAAAsTAAALEwEAmpwYAAADmklEQVR42q2XWU8qQRCF+///Ap98IDHGP8CLvhgeNIi44QaKgoq7IK7gUjdfJYfbdxgG0NtJp5np7ppTVadON+H9/d3q9brVajWrVCred3d3/fnk5MROT0+t2Wz6eHR0ZB8fH5bVAhvv7u6s2+1au9225+dn/827Tqdjr6+vdn19bb1ezw3yEdrX19dQ//7+trC9vW2fn592fHxs6+vrtr+/bxcXF45yb2/PWq2WFQoFu729tfv7ezs7O3ODbE5FWCqV7O3tzR9wf3l52Q3xgWKx6Ib7/b7P39zcOMK5uTmbmZkx9rKGcW1tzT0Kh4eH9vT05BtAen5+7qhAcnV15ah4T7u8vPR54kmsFSbCAgA+FnjATRJDVxIwqERtbGw4atASy7jJdeLcaDQsJGNAcGlsfnx8TI0TRmRIIzEGTEhbTCNZDw8PmQmI5wgNboe0RaDM5/MDikxikFgSoiGDSsDCwoJndVKDhKdarf41GMcF+CSB5CRjxag4x+9hCowJycVKPw3abG1tDXiahlTvqDDYEvQC+uzs7LgR3CYh1C2xKZfLgwQxD3KRPd4PM4L8x7qQQIHFxUXfrCTBUQzBS0qRsGAEV+mUK3NBFQBN1BCBpaUlJ7QaYcB9PsJ6QgMIvNrc3PTSw5sQE5NEvLy8eE0zyQjq1dVVr4JJ2j9JoaxACnwaxY4CxQSPe6p8JbPFiEughQaiSBYXhxCmcZG6JPAIbcy7qQzGjYTgPpmbpo00CPegxbQtE6EMThq/sQihzX9BCCLo8mOD4pCyCf/m5+ed7GnzUyNEHHK5nFdNMobj4hnYTGXQOZjgH2UGqREEapjOaUdPHlJDBikrZAc0dD5AR7qQKOKI1knvkPnMgx6Z5xymYUTnMR/CiK4jmsdoJkLOUxkEEXShjjEMF3mOk0MoMg2yGOEUglhJ4tqWKh8cHGQbxAhxQVQZObnoCCgjaHGTZ2KNWGTGcNRNAAPcXRQKoR/Lw6Ro6lxGpZNokiGYiNiqBmSLDP9aHLSZmMrlX4lDfFkSXaZGmIwjbXZ21o9I1faPxUEG4ZtcnkocoIP+PkAV9VgodKsFcdpHhmqZDSzAOB1Foeu3XEZtSFamQYkBnKMSQMTxSZWIOisrK46YW4ROwZEGEQYEgsX6B8DliQ/wnosQ7kp1JA6ZLrOIBZQYLqKLbGbEIL9xG0/GXZMDRqTMZJYbFrLFIcWfGm5V3K4QDub1n2aUwT/+h4xAIHWmNwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Hash Collision Handling&quot;
        title=&quot;&quot;
        src=&quot;/static/6216006eb9157c68036812d5b803a9f6/d3fa7/hash-collision-handling.png&quot;
        srcset=&quot;/static/6216006eb9157c68036812d5b803a9f6/222b7/hash-collision-handling.png 163w,
/static/6216006eb9157c68036812d5b803a9f6/d3fa7/hash-collision-handling.png 274w&quot;
        sizes=&quot;(max-width: 274px) 100vw, 274px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;또 다른 방법으로는, ID (Auto Increment) 값을 기반으로 인코딩하는 방식이 있다.
이 방식을 적용하여 URL을 생성하는 기본 아이디어는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/cb92df59ecb82138967914e7cb49a1be/35751/sequence-to-base62.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 11.65644171779141%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAX0lEQVR42j2OSwrAMBBCe/9jhpCQ/48kS4tCuxCdxdN5YoyYc6L3jjEGSily3sx0ay1CCMr3XpxzlFNKv+ecsdbC45yDMUYQxZK9t8Baq8q994I4zDKKMJ9prWnsK3wB50OXcnWKpaEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Sequence to Base62&quot;
        title=&quot;&quot;
        src=&quot;/static/cb92df59ecb82138967914e7cb49a1be/a6d36/sequence-to-base62.png&quot;
        srcset=&quot;/static/cb92df59ecb82138967914e7cb49a1be/222b7/sequence-to-base62.png 163w,
/static/cb92df59ecb82138967914e7cb49a1be/ff46a/sequence-to-base62.png 325w,
/static/cb92df59ecb82138967914e7cb49a1be/a6d36/sequence-to-base62.png 650w,
/static/cb92df59ecb82138967914e7cb49a1be/35751/sequence-to-base62.png 873w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;시퀀스 번호에 따라 문자열의 길이는 늘어난다.
최대 7자리까지의 고유한 URL을 발행한다고 가정하고 계산해보자.&lt;/p&gt;
&lt;p&gt;Base62 인코딩으로 7자리 문자열을 생성하면 대략 3.5조 개를 표현할 수 있다.&lt;/p&gt;
&lt;p&gt;다만 이 방법을 채택할 경우, Sequence Number를 어떤 방식으로 생성하고 관리할지를 결정해야 한다.&lt;/p&gt;
&lt;p&gt;다음의 3가지 방법을 살펴보자.&lt;/p&gt;
&lt;h3 id=&quot;1-redis-incr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-redis-incr&quot; aria-label=&quot;1 redis incr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. Redis (INCR)&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5a7da846d6bb0827926294b86c56a099/cab8c/redis-incr.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.987730061349694%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABbElEQVR42pVSXVOCUBS8//8P9dJLPTiZipCAlhbTgKTx4fc3sLHHuUbkQ52ZHb1w2N2z56rZbIY4jhEEASaTCXiez+fQVRSF4C/FPrXZbLBerzEej4UoyzJMp1MsFgscDocfZNfI8zwX8DshXC6XQrjdbgU8U2S32yFJEnGfpqk8P51OF2JWkqSIPiN5F8eJTKh0g1aqO6AyBXzfF+cU4DnPM4y8dzS7DkxniLY1gPHUh+KYdKLd8LeaYT0juqSjIPBh2S469hCNtoVHsw/LeTlnyFFpN4oiccn/JOWCCD73PO/Sw3w97w2tjomuM8LN7R3uHwx07WcoviTYqN0xUx00HRE6jv1+f3HYaLbEoem+lsRDNI0elN5cFfUiob5eXBBFKRCEHzB6gzI/B50yv55bOlytVtCgMt3SBT9gFCThMgiefwsWOB6PMg1LsUlvUS8jDEPJixHU72L12lwrxVHO9+h7w3T5H5Iq4Rck9Ex9kCm+sAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Redis INCR&quot;
        title=&quot;&quot;
        src=&quot;/static/5a7da846d6bb0827926294b86c56a099/a6d36/redis-incr.png&quot;
        srcset=&quot;/static/5a7da846d6bb0827926294b86c56a099/222b7/redis-incr.png 163w,
/static/5a7da846d6bb0827926294b86c56a099/ff46a/redis-incr.png 325w,
/static/5a7da846d6bb0827926294b86c56a099/a6d36/redis-incr.png 650w,
/static/5a7da846d6bb0827926294b86c56a099/cab8c/redis-incr.png 744w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;모든 서버가 Redis를 통해 Sequence를 생성하는 방식이다.
레디스의 연산은 원자적이므로 간단하게 구현할 수 있는 것이 장점이다.
다만 중앙 집중 방식이기 때문에 단일 장애점 (SPOF)이 될 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;2-snowflake-id&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-snowflake-id&quot; aria-label=&quot;2 snowflake id permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. Snowflake ID&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 509px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/70844a75da5f5f80bced2511e36cdfff/71554/snowflake-id.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.717791411042946%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABGUlEQVR42nWSi6qCQBCG9/3fTRFUEIksb5mmpSUZahPfwIbBOQPD7vz7z31NXdfStq08Hg/p+15ut5vq9Xr9atd1f+LY+OB7uVzkdDqJaZpGkPf7LVux9jAMkiSJ7Pd7sdz/hKBmt9spkejn81k1z3N9ROmgKAo5HA5SlqVUVaVci8Hlznk8HsVAQtZ11fP5fCphi43jqA5b7PV6SZqmPxjFGLKhWZZ9s9EilVMNGsexYnRCxcwULmPgTiBmSgwDwMVxHAmCQKIo0gRktxjB4RDY931xXVc5JPE874dnyLgsi7Ywz7O2R0awaZoUu9/vWikc3rF5IykjAmPTumUCMny+ANViU6UdPsqwwzDUpdjFEIyq8EHt9/oAD1xZQgf9Os8AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Snowflake ID&quot;
        title=&quot;&quot;
        src=&quot;/static/70844a75da5f5f80bced2511e36cdfff/71554/snowflake-id.png&quot;
        srcset=&quot;/static/70844a75da5f5f80bced2511e36cdfff/222b7/snowflake-id.png 163w,
/static/70844a75da5f5f80bced2511e36cdfff/ff46a/snowflake-id.png 325w,
/static/70844a75da5f5f80bced2511e36cdfff/71554/snowflake-id.png 509w&quot;
        sizes=&quot;(max-width: 509px) 100vw, 509px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;64비트를 timestamp / datacenter / machine ID / sequence로 나눠서 생성하는 방식이다.
분산 환경에서 각 서버가 독립적인 ID 생성을 할 수 있다는 장점이 있다.
또한 timestamp가 반영되므로 시간순 정렬이 가능하다.&lt;/p&gt;
&lt;h3 id=&quot;3-machine-id--sequence-number&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-machine-id--sequence-number&quot; aria-label=&quot;3 machine id  sequence number permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3. Machine ID + Sequence Number&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 416px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9fabe4dbe5bd6de4cd3c9e9268a6c62c/b0122/machine-id-sequence.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.104294478527606%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABKElEQVR42nWS64qDQAyF5/2fyEcoCPVHEbX24q3WS6uICK22ZvkCIwvLDqTpJCenJ2dqmqaRLMuEnKapxHEsQRBswf14PMrpdNIchqHmy+Wi+NvtprPX61XGcRRDY55n+e8AzPNczueztG2rtXVd/+A+n4/UdS0mSRIpy1KqqtL8O+73+6bQ932JokjrqKJHMAcRGXGGFSAtimIDkO3g4/GQ/X6vZPSoWQIyWGpsAcbw5fv9quyu6zZlwzCosmma9A6xxUDA+niHb9YGPDU0IaQA2HVdORwOqorHWpZF1Vj/IHEcRzzPk91up9tx4GBLw8fr9drMZZghgH3fbyQE5/1+6wNxZ/b5fGodDh7QMEQDBWQCZRDyV+FRUGN9JBi0vmMRdvAjbPgDfJNSYcgFKnQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Machine ID and Sequence&quot;
        title=&quot;&quot;
        src=&quot;/static/9fabe4dbe5bd6de4cd3c9e9268a6c62c/b0122/machine-id-sequence.png&quot;
        srcset=&quot;/static/9fabe4dbe5bd6de4cd3c9e9268a6c62c/222b7/machine-id-sequence.png 163w,
/static/9fabe4dbe5bd6de4cd3c9e9268a6c62c/ff46a/machine-id-sequence.png 325w,
/static/9fabe4dbe5bd6de4cd3c9e9268a6c62c/b0122/machine-id-sequence.png 416w&quot;
        sizes=&quot;(max-width: 416px) 100vw, 416px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Snowflake ID는 64비트를 사용하므로 약 11자리의 URL이 생성된다.&lt;/p&gt;
&lt;p&gt;Snowflake ID 방식을 간소화하여 &lt;code class=&quot;language-text&quot;&gt;Machine ID&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;Sequence Number&lt;/code&gt; 조합으로 생성하면,
구조를 단순화하면서도 더 짧은 길이(약 7자리)의 ID를 생성할 수 있다.&lt;/p&gt;
&lt;h2 id=&quot;조회-성능-최적화&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%A1%B0%ED%9A%8C-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94&quot; aria-label=&quot;조회 성능 최적화 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;조회 성능 최적화&lt;/h2&gt;
&lt;p&gt;URL Shortener의 주요 컴포넌트는 다음과 같이 구성된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6f6b1ce5c860550f3a9306a1378e5f0c/1e088/system-components.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.306748466257666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA0klEQVR42qWSyQqFMAxF+/9f6UJBEed5vI8TyMOViAaCbZqeXJOGsiw1DIPatlVd17Z227bNYtM0mbPe9/1/3ve9xZZl0TiOKopCgeA8z6qqygBZlqlpGuV5riiK7IxL5AElTm6SJIrjWOd52h2giArXCq7iOA6Dk4Bi4oCBskfluq4G9rjvTWHXdZaIA3KjOjGKeGuIXVviRfjiQQ8Mhdfe3tkjoKv/DPRfTtPUhkGf6e9rID1i4jSc5uNAPwFRCITBoY7JvwYC4LGi0p0ndgf8AWR5EIRfMFssAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;System Components&quot;
        title=&quot;&quot;
        src=&quot;/static/6f6b1ce5c860550f3a9306a1378e5f0c/a6d36/system-components.png&quot;
        srcset=&quot;/static/6f6b1ce5c860550f3a9306a1378e5f0c/222b7/system-components.png 163w,
/static/6f6b1ce5c860550f3a9306a1378e5f0c/ff46a/system-components.png 325w,
/static/6f6b1ce5c860550f3a9306a1378e5f0c/a6d36/system-components.png 650w,
/static/6f6b1ce5c860550f3a9306a1378e5f0c/1e088/system-components.png 840w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;URL Shortener는 생성보다 조회 연산이 훨씬 많다.&lt;/p&gt;
&lt;p&gt;URL은 불변 값이기 때문에, 캐시를 사용하기에 적합하다.
Cache-Aside 패턴을 적용하면 DB 부하를 줄이고 조회 성능을 크게 높일 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 633px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d6409052835ec3ca28629b88eca9e486/bce72/cache-aside-pattern.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 108.58895705521472%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAByElEQVR42p2VCa+CQAyE+f+/UIgJiveJBx4I2pevyRAeAho3Wdll26HTztZgs9kYc7vd2m638/V4PPZnkiS2WCz8yTmT9+v12tdpmlpzBOfz2a7Xq4M8Hg83Go1G7jCbzWy5XNpwOLTBYOB2nHNWFIXt9/t3QKK63W4mYCLI89wPX6+XT5wPh4ODiQX2rYAcYBxFkWVZ5rNtACwQGOBzv9/fAfl5Pp82n8+tbwCogW19/w9QtKDTZdQck8mkG1CLtoo1R1mWnt8wDP0Js05AcvIJjGLwYXJ5Op1cPj8DUgCAUAQSI0Ltf6KMdNAfKkA6gLEn8lZA5PCpKESJVqny5XLpls23gBrk7mOVCf9bwD6JVYBUsMuIxFNV6EKT+807aMOMfEr8wTc04ji21WrlxQAAcKbkQzd6A8ShDRB60+nUjsdja+X5CC2OlCGlChAaTUD2GAIoBpoSu9ockZOOCpCvcJWaoERBP9Q1q4PKlpRI4EFfB4EuxVIE0KvfX/LHO24ZkRJUUE+87inVxBgjBk5ESo7om/rbYKr7cI5PBciVIvFIgfBZy4GC8fdAjqBPRDirkOSXc94HfQ1Vd5ap9o9jnTIgBCPafxyas1n9VXt0AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Cache Aside Pattern&quot;
        title=&quot;&quot;
        src=&quot;/static/d6409052835ec3ca28629b88eca9e486/bce72/cache-aside-pattern.png&quot;
        srcset=&quot;/static/d6409052835ec3ca28629b88eca9e486/222b7/cache-aside-pattern.png 163w,
/static/d6409052835ec3ca28629b88eca9e486/ff46a/cache-aside-pattern.png 325w,
/static/d6409052835ec3ca28629b88eca9e486/bce72/cache-aside-pattern.png 633w&quot;
        sizes=&quot;(max-width: 633px) 100vw, 633px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;references&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#references&quot; aria-label=&quot;references permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/System-Design-Interview-insiders-Second/dp/B08CMF2CQF&quot;&gt;System Design Interview – An Insider&apos;s Guide (Alex Xu)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/twitter-archive/snowflake&quot;&gt;Twitter Snowflake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://redis.io/commands/incr/&quot;&gt;Redis INCR Command&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Base62&quot;&gt;Base62 Encoding&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Transactional Outbox Pattern 알아보기]]></title><description><![CDATA[이중 쓰기(Dual Write)란 하나의 비즈니스 로직에서 둘 이상의 시스템에 데이터를 쓰는 작업이다.
이 방식은 서로 다른 시스템에 데이터를 쓰기 때문에 원자성을 보장하지 못하고 정합성이 훼손될 수 있다. Transactional Outbox…]]></description><link>https://steadiness.dev/transactional-outbox-pattern/</link><guid isPermaLink="false">https://steadiness.dev/transactional-outbox-pattern/</guid><pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이중 쓰기(Dual Write)란 하나의 비즈니스 로직에서 둘 이상의 시스템에 데이터를 쓰는 작업이다.
이 방식은 서로 다른 시스템에 데이터를 쓰기 때문에 원자성을 보장하지 못하고 정합성이 훼손될 수 있다.&lt;/p&gt;
&lt;p&gt;Transactional Outbox Pattern은 이 문제를 해결할 수 있는 방법 중 하나다.&lt;/p&gt;
&lt;p&gt;이 글에서는 Outbox Pattern이 무엇인지 알아보고, 원자성 및 정합성 문제를 어떻게 해결하는지 살펴본다.&lt;/p&gt;
&lt;h2 id=&quot;dual-write-problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dual-write-problem&quot; aria-label=&quot;dual write problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dual Write Problem&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 491px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f474ede9ba13c859d5f0f2ea6cc0d5e6/13566/dual-write-problem.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 59.50920245398773%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACD0lEQVR42pWT22sTQRjF85erb+KLCKVWxSdBsKDxpQ+FlrRFCSrStDGNNqbJ3ppNstndxCR7m539ObNJDZT2wW85nNnZ2fNdp8IdVqhHFpJcQ2rk5buGyCVJVhCnkijJWcS54tU5bZW7BIUULLOITGaIQpSc5Sn6l/GohdvbZWhUcfvvcY0PGL/eYQ06G8GiKP5BBaeiEKWQVNGEQYg38tQ6x7ke0268ZWk/pv/9AebpQ4VHRM4TbOsL+f0R5qQqKi0ezEO6gx6uO6Bv2ly2jxhcvsRovaJ7vkPndBvrYofRuKOPUxEip2mMaTozLhTObYUrFy/wMB2LgecSxlMSoVOWpEIQpQlxlhJlidrPWCZxuV+m/GcZ86I+5OnxNc+OHbbqPq+/+Rh9g5ty/I9VFkrwzVeX5ycW20c9tmoGuw0f07IJpzMWUaw6qOtLyRpSr9eQa9w4rhRqFIaTEGvo07Ndes4IZ+SrlAThfIk3GOLZ1/izBXGi0lMOgonPzA+Zh9OSfc9nHiX3j02U5mWBc9XZwFeOul1Mo8/FzzY/jmrMazWsvT26H6v4h4cEBwfYv69WTbk9NhpxthK8+SZV13XOeg7dRgPqdcb7+1jVKsuTY+TnT/Ra7Y3gbYuFVIO8qpco60Z5Q/ScTScB5lkTs3G+xhl2s4UfzO5PWdc3XYveZt0IdfNIlCONuORNl/8CA56P1vnaMIcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Dual Write Problem&quot;
        title=&quot;&quot;
        src=&quot;/static/f474ede9ba13c859d5f0f2ea6cc0d5e6/13566/dual-write-problem.png&quot;
        srcset=&quot;/static/f474ede9ba13c859d5f0f2ea6cc0d5e6/222b7/dual-write-problem.png 163w,
/static/f474ede9ba13c859d5f0f2ea6cc0d5e6/ff46a/dual-write-problem.png 325w,
/static/f474ede9ba13c859d5f0f2ea6cc0d5e6/13566/dual-write-problem.png 491w&quot;
        sizes=&quot;(max-width: 491px) 100vw, 491px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;MSA 환경에서 DB에 데이터를 저장하고 메시지 브로커를 통해 이벤트를 다른 서비스로 전파하는 경우, 다음의 시나리오가 모두 발생할 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DB 저장(O), 이벤트 발행(O)&lt;/li&gt;
&lt;li&gt;DB 저장(O), 이벤트 발행(X)&lt;/li&gt;
&lt;li&gt;DB 저장(X), 이벤트 발행(O)&lt;/li&gt;
&lt;li&gt;DB 저장(X), 이벤트 발행(X)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1번과 같은 정상 케이스를 제외한 2, 3, 4번 모두 시스템 간 데이터 정합성을 지킬 수 없게 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;이 문제의 근본적인 원인은 DB와 메시지 브로커가 서로 다른 트랜잭션 경계를 가지기 때문이다.&lt;/strong&gt;
결국 두 작업을 하나의 원자적 연산으로 처리할 방법이 필요하다.&lt;/p&gt;
&lt;h2 id=&quot;transactional-outbox-pattern&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#transactional-outbox-pattern&quot; aria-label=&quot;transactional outbox pattern permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Transactional Outbox Pattern&lt;/h2&gt;
&lt;p&gt;Outbox Pattern은 메시지를 외부 시스템에 직접 발행하지 않는다. 그 대신 동일 DB 내 별도 Outbox 테이블에 이벤트와 관련 정보를 저장한다.&lt;/p&gt;
&lt;p&gt;동일 트랜잭션 내에서 데이터 처리와 이벤트 저장이 동시에 발생하기 때문에 원자성을 보장할 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;Outbox Pattern의 동작 과정은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/05b7eb9b39870e4fd46e184561ef1266/8c557/outbox-pattern-flow.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.39263803680981%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAACAUlEQVR42qWSW2/TQBBG+5t55JV/UJB4oKJC7QOo4qJSKgGtkiZFTZRQUAVxk5Rc7MSJvXZip3Zuvhw2jomgCkXASKNZe1ZnvpmdDW5YHAREs6mMIZOxxdAo0m+f0VfPsPUC3rX4cZN1thFFEf5kxngW4MkYTibgDMGbMlDfYlc3qRU3+Va+j1N/gGgcQiRxcbQe2B9cs100eFzos1UwqXYHzN0hhmnTVF7jW3kG+qlUd4rTy8h4koiLo3A90HJ9npV77OTr7BY06prAazUwdZOG8pKB9oJ+cx+hvsFs7Un48RIYBuuBabl0LFEym3g+Tz6FmqNb3aPx5RXdq0N69edYWn7ZcqowjuOVp0BWidVZzjWeB8w8l5nTQK2VaF0WGRkKM38kgfEv928o/L1N5Gs7U5+ubVJTmzT1Dtdjj2CxCbKo57rYhpG4Y1nJv7XAOF2JrPaBh8o2W8ouT66e8uhqh/eNLLqqU1EqWNks5sEB3f19prkcrgTfCrwUNY60HMetHEftE961M1RFPcn54zFDRWFwfk5bwpyLC3zHub3lxfCXLkuEski4PKdDZy5bdDyPVqdDkKTiPwBvUb+Yozca4QiBJcTqYf8a+PNGOJMpJU0nU6nyVdhM5br9EzBKgYVOjzv5MnfPPnHvYwXTHf2fQtvzKfUExW6fz4bFPAj5DlMfLZD8+w2RAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Outbox Pattern 동작 과정&quot;
        title=&quot;&quot;
        src=&quot;/static/05b7eb9b39870e4fd46e184561ef1266/a6d36/outbox-pattern-flow.png&quot;
        srcset=&quot;/static/05b7eb9b39870e4fd46e184561ef1266/222b7/outbox-pattern-flow.png 163w,
/static/05b7eb9b39870e4fd46e184561ef1266/ff46a/outbox-pattern-flow.png 325w,
/static/05b7eb9b39870e4fd46e184561ef1266/a6d36/outbox-pattern-flow.png 650w,
/static/05b7eb9b39870e4fd46e184561ef1266/8c557/outbox-pattern-flow.png 700w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;데이터 변경 시점에 Outbox 테이블에 이벤트를 함께 저장한다.&lt;/li&gt;
&lt;li&gt;별도 프로세스(Message Relay)가 주기적으로 Outbox 테이블을 읽는다.&lt;/li&gt;
&lt;li&gt;Outbox 테이블에서 조회한 이벤트를 메시지 브로커로 발행한다. 발행이 성공하면 Outbox 테이블의 해당 이벤트를 처리 완료 상태로 변경한다.&lt;/li&gt;
&lt;li&gt;컨슈머에서 발행된 이벤트를 처리한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이제 간단한 코드 구현을 통해 Outbox Pattern을 적용해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; AggregateType &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ORDER&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; PAYMENT &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; EventType &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ORDER_CREATED&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ORDER_CANCELLED &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; OutboxStatus &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; PENDING&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; COMPLETED&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; FAILED &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token annotation builtin&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;OutboxEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;

  &lt;span class=&quot;token annotation builtin&quot;&gt;@Id&lt;/span&gt; &lt;span class=&quot;token annotation builtin&quot;&gt;@GeneratedValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; GenerationType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IDENTITY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token annotation builtin&quot;&gt;@Enumerated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;EnumType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;STRING&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; aggregateType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AggregateType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; aggregateId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token annotation builtin&quot;&gt;@Enumerated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;EnumType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;STRING&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; eventType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; payload&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token annotation builtin&quot;&gt;@Enumerated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;EnumType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;STRING&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; OutboxStatus&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LocalDateTime
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Outbox Pattern의 핵심은 데이터 처리와 이벤트 저장을 동일 트랜잭션 내에서 수행하는 것이다.
주문 저장과 Outbox 데이터 저장이 모두 같은 트랜잭션에서 실행된다. 둘 중 하나라도 실패하면 모두 롤백된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;OrderCreatedEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;OrderService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; orderRepository&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; OrderRepository&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; outboxEventRepository&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; OutboxRepository&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; objectMapper&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ObjectMapper
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation builtin&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Order &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; order &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; orderRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token function&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; quantity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; quantity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; event &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;OrderCreatedEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          orderId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          quantity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

      outboxEventRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token function&quot;&gt;OutboxEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
              aggregateType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AggregateType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ORDER&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              aggregateId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              eventType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; EventType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ORDER_CREATED&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              payload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; objectMapper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeValueAsString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; order
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;저장된 이벤트는 Message Relay 컴포넌트가 별도 프로세스에서 조회한다.
조회된 이벤트는 메시지 브로커에 발행한다.&lt;/p&gt;
&lt;p&gt;아래는 Relay의 간단한 예시 코드이다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MessageRelay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; outboxRepository&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; OutboxRepository&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; kafkaTemplate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; KafkaTemplate&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; logger &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; LoggerFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;javaClass&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token annotation builtin&quot;&gt;@Scheduled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fixedDelay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation builtin&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; events &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; outboxRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findByStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;OutboxStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PENDING&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; event &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                kafkaTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                  &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;order-events&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;aggregateId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;payload
                &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OutboxStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;COMPLETED
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Exception&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Failed to publish event: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OutboxStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FAILED
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;지금까지 Outbox Pattern을 간단한 예시 코드로 살펴보았다.&lt;/p&gt;
&lt;p&gt;위 구현에는 몇 가지 고려해야 할 점이 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;MessageRelay가 이벤트를 발행한 후 상태 변경 전 장애가 발생하면, 같은 이벤트가 중복 발행될 수 있다.&lt;/li&gt;
&lt;li&gt;여러 이벤트가 동시에 처리되면 순서가 보장되지 않을 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 문제의 해결 방법을 살펴보자.&lt;/p&gt;
&lt;h3 id=&quot;멱등성&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EB%A9%B1%EB%93%B1%EC%84%B1&quot; aria-label=&quot;멱등성 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;멱등성&lt;/h3&gt;
&lt;p&gt;멱등성이란 동일한 연산을 몇 번 하더라도 결과가 동일함을 보장하는 성질이다.
그럼 Outbox Pattern에서 멱등성을 보장하기 위해서는 어떻게 해야 할까?&lt;/p&gt;
&lt;p&gt;각 Outbox Event는 고유한 id 값을 갖는다. 따라서 메시지를 소비하는 쪽에서 이벤트 식별자를 저장해두고, 처리 시마다 처리된 이력이 있는지 조회하면 된다.&lt;/p&gt;
&lt;p&gt;만약 이미 처리된 이력이 있는 경우 해당 이벤트를 무시하고, 없는 경우 이벤트 ID를 저장하고 처리한다. 이를 코드에 적용해보면 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DomainEventLog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token annotation builtin&quot;&gt;@Id&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; eventId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LocalDateTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; LocalDateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;OrderEventConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; domainEventLogRepository&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DomainEventLogRepository&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; inventoryService&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; InventoryService
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation builtin&quot;&gt;@KafkaListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;topics &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;order-events&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token annotation builtin&quot;&gt;@Transactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token annotation builtin&quot;&gt;@Header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;eventId&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; eventId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;domainEventLogRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;existsByEventId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eventId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; event &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; objectMapper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; OrderCreatedEvent&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        inventoryService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decreaseStock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        domainEventLogRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;DomainEventLog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eventId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; eventId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;순서-보장&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#%EC%88%9C%EC%84%9C-%EB%B3%B4%EC%9E%A5&quot; aria-label=&quot;순서 보장 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;순서 보장&lt;/h3&gt;
&lt;p&gt;여러 이벤트가 동시에 발행되면 순서가 보장되지 않을 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 같은 주문에 대해 생성, 취소 이벤트가 발생할 수 있다. 만약 두 이벤트가 순서대로 처리되지 않으면, 데이터 정합성이 훼손된다.&lt;/p&gt;
&lt;p&gt;Kafka와 같은 메시지 브로커는 같은 파티션 내에서의 메시지 순서를 보장한다.&lt;/p&gt;
&lt;p&gt;앞선 코드 예시에서는 aggregateId 값을 파티션 키로 사용하면, 같은 aggregate 내에서 처리 순서를 보장할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;kafkaTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;order-events&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;aggregateId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// 같은 파티션 키를 사용하면 동일한 파티션으로 전송된다.&lt;/span&gt;
  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;payload
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;references&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#references&quot; aria-label=&quot;references permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://microservices.io/patterns/data/transactional-outbox.html&quot;&gt;Transactional Outbox Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/transactional-outbox.html&quot;&gt;AWS Prescriptive Guidance - Transactional Outbox Pattern&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Cache Stampede 알아보기]]></title><description><![CDATA[Cache Stampede는 캐시가 만료되는 순간, 다수의 요청이 DB로 몰리는 현상이다. 이는 캐시를 활용하고 있음에도 DB에 부하를 주며, 장애로 이어질 수 있다. 이 글에서는 구체적인 발생 원인과 해결 방법을 다룬다. Cache Stampede…]]></description><link>https://steadiness.dev/cache-stampede/</link><guid isPermaLink="false">https://steadiness.dev/cache-stampede/</guid><pubDate>Sun, 11 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cache Stampede는 캐시가 만료되는 순간, 다수의 요청이 DB로 몰리는 현상이다. 이는 캐시를 활용하고 있음에도 DB에 부하를 주며, 장애로 이어질 수 있다.&lt;/p&gt;
&lt;p&gt;이 글에서는 구체적인 발생 원인과 해결 방법을 다룬다.&lt;/p&gt;
&lt;h2 id=&quot;cache-stampede-발생-원인&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cache-stampede-%EB%B0%9C%EC%83%9D-%EC%9B%90%EC%9D%B8&quot; aria-label=&quot;cache stampede 발생 원인 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cache Stampede 발생 원인&lt;/h2&gt;
&lt;p&gt;보통 변경이 빈번하지 않은 데이터는 조회 시 Cache Aside 패턴을 적용하기 때문에 DB를 직접 조회하는 빈도가 낮다. 하지만 캐시가 만료되는 순간, 해당 데이터를 조회하는 모든 요청이 순간적으로 DB에 집중될 수 있다.&lt;/p&gt;
&lt;p&gt;이 현상은 다음 조건에 해당 될 때 발생한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Hot Key&lt;/strong&gt; : 특정 키에 요청이 집중된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TTL 만료&lt;/strong&gt; : 해당 키의 캐시가 만료된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;높은 트래픽 (동시 요청)&lt;/strong&gt; : 만료 시점에 다수의 요청이 들어온다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;cache-aside-패턴의-한계&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cache-aside-%ED%8C%A8%ED%84%B4%EC%9D%98-%ED%95%9C%EA%B3%84&quot; aria-label=&quot;cache aside 패턴의 한계 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cache Aside 패턴의 한계&lt;/h3&gt;
&lt;p&gt;Cache Aside 패턴의 핵심 로직은 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;캐시에서 데이터를 조회한다.&lt;/li&gt;
&lt;li&gt;데이터가 캐시에 있으면 바로 반환한다.&lt;/li&gt;
&lt;li&gt;만약 데이터가 없다면, DB에서 원본 데이터를 조회 후 캐시에 저장한 뒤 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;문제는 3번에서 발생한다. 캐시 미스가 발생하면 모든 요청은 독립적으로 각자 DB를 직접 조회한다. 요청 간 &apos;DB를 조회하는 상태&apos;를 공유하지 않기 때문이다.&lt;/p&gt;
&lt;p&gt;초당 1000건의 요청이 발생한다고 가정하면, 캐시가 만료되는 순간 1000개의 동일한 DB 조회가 발생한다. 첫 번째 요청이 캐시에 데이터를 다시 저장하기 전까지 나머지 999개의 요청도 캐시 미스를 겪게 되고, 결국 DB를 조회하게 되는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f13c1220d42f542ccb4579dade621037/a44c1/cache-stampede-comparison.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.993865030674847%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAr0lEQVR42m1QSQ6DMAzM/79Ij6iHHqqyKBDipTMmQRywNIrXydhJVV1EvNYaoDE+jsPNzA11vwMW+QbOm52gn7Q10UhIonEc/TUMPk2T130nQ0BK8QKwh2/O2QUzSmKA84m/XEUoI+jP8xwNSqyrK+Lw20Z8ow4h5fv28vu4IEinZLsaiGVZQl3eNleo6es+EVb4JjiPnrOpq2LQG6l4x6rMGXKdkP79fkTMQlptPH8qwomDbXnuXAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Cache Stampede 비교&quot;
        title=&quot;&quot;
        src=&quot;/static/f13c1220d42f542ccb4579dade621037/a6d36/cache-stampede-comparison.png&quot;
        srcset=&quot;/static/f13c1220d42f542ccb4579dade621037/222b7/cache-stampede-comparison.png 163w,
/static/f13c1220d42f542ccb4579dade621037/ff46a/cache-stampede-comparison.png 325w,
/static/f13c1220d42f542ccb4579dade621037/a6d36/cache-stampede-comparison.png 650w,
/static/f13c1220d42f542ccb4579dade621037/e548f/cache-stampede-comparison.png 975w,
/static/f13c1220d42f542ccb4579dade621037/3c492/cache-stampede-comparison.png 1300w,
/static/f13c1220d42f542ccb4579dade621037/a44c1/cache-stampede-comparison.png 4175w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;즉, Cache Stampede 문제를 해결하기 위해서는 &apos;동시에 여러 요청이 DB에 접근하는 것&apos;을 막으면 된다.&lt;/p&gt;
&lt;p&gt;이를 위한 몇 가지 해결 방법을 알아보자.&lt;/p&gt;
&lt;h2 id=&quot;cache-stampede-해결-방법&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cache-stampede-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95&quot; aria-label=&quot;cache stampede 해결 방법 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cache Stampede 해결 방법&lt;/h2&gt;
&lt;h3 id=&quot;1-lock-mutex&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-lock-mutex&quot; aria-label=&quot;1 lock mutex permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. Lock (Mutex)&lt;/h3&gt;
&lt;p&gt;Cache miss가 발생하면 하나의 요청만 DB를 조회하고, 나머지는 대기한다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/713a047fa1e5d496ce0a31c292ff3cb8/eaf69/cache-stampede-lock.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 49.079754601226995%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA6ElEQVR42qWSTWrEMAxGc/+bdNULDF2UXqCLbsqUbtMhBGwnceI4/tpn0JCB7iwQ+rOfZOFOSDEtapUu5aSXnzc9XZ91ub0qbrENeJRD1+lb78OHPsOXnHfKOTcAj0MpJsUQldddKSXN8yzyCHDzEfx93++58rcmfIsrMISgcRzlg68ADq3rqhhjtcuyVAjNiGlIzWJT7nZcpjhNU01aJ6z3voIAbNtWG2OpcZY75AAZtE7onFPf99We9wcMAGrCVMRYWwkDobDuE/IsS56Fejl9J4v/yz3scBiG+gSmavo2Njaglu9iwF9LAxN5JB5H5wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Lock 메커니즘&quot;
        title=&quot;&quot;
        src=&quot;/static/713a047fa1e5d496ce0a31c292ff3cb8/a6d36/cache-stampede-lock.png&quot;
        srcset=&quot;/static/713a047fa1e5d496ce0a31c292ff3cb8/222b7/cache-stampede-lock.png 163w,
/static/713a047fa1e5d496ce0a31c292ff3cb8/ff46a/cache-stampede-lock.png 325w,
/static/713a047fa1e5d496ce0a31c292ff3cb8/a6d36/cache-stampede-lock.png 650w,
/static/713a047fa1e5d496ce0a31c292ff3cb8/e548f/cache-stampede-lock.png 975w,
/static/713a047fa1e5d496ce0a31c292ff3cb8/3c492/cache-stampede-lock.png 1300w,
/static/713a047fa1e5d496ce0a31c292ff3cb8/eaf69/cache-stampede-lock.png 1701w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;첫 번째 요청이 DB에서 데이터를 가져와서 캐시에 데이터를 저장하면, 대기 중이던 요청은 캐시에서 데이터를 즉시 조회할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;retrieveData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Data &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 1. 캐시에서 데이터를 조회한다. 존재하면 즉시 반환한다.&lt;/span&gt;
    cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; it &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 2. 캐시에 데이터가 없으면 락 획득을 시도한다.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; lock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; redisLockRegistry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;obtain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tryLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; TimeUnit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SECONDS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 3. 락을 획득한 사이 다른 요청이 캐시에 저장했을 수 있으므로 다시 확인한다.&lt;/span&gt;
            cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; it &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// 4. DB에서 데이터를 조회하고, 캐시에 저장한다.&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Duration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ofMinutes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            lock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 5. 락 획득에 실패하면 잠시 대기 후 재시도한다.&lt;/span&gt;
    Thread&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;retrieveData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;단, 락을 획득하지 못한 요청은 대기해야 하므로 응답 지연이 발생 할 수 있다.&lt;/p&gt;
&lt;h3 id=&quot;2-per-probabilistic-early-recomputation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-per-probabilistic-early-recomputation&quot; aria-label=&quot;2 per probabilistic early recomputation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. PER (Probabilistic Early Recomputation)&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 536px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/28f4cbeab1519b3f52dd06f4a26f14df/2d920/cache-stampede-per.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.104294478527606%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABdklEQVR42n2S3U7bQBSEef9H4AZcoRZatSD1vlLFBRehiSJQwHWK6yQbO3Zsr52NnR87X3cXmgoqGOn4rFaa2ZlzfMAbWG82yEVBJjPSLLWVyZy8kBSqRFVLdrvdM86B+cRxTBRFpGmK67qMxiMiEXE97uIEZ5w8fOLY+4DjnfLu10dO/M+8Fxec/bygaVuEEPi+/0+w1Zer1YqNdmR6VVXUVU1YJHRjl97M5Toc0Jne0glv9bnPj6jPTTTQgo3lb7fbR0FjOc/zfZnXjFtZKDJNyLW77OEc+fucpfhKMvxCtZCP+Z7izudzy9kLGkdKKVsm/mwWkeUl8bRHGhwxDxyy0TGLyRFT95CyiC253bW2J0nCZDKx87SR67q2UZumYb1e0+puIGPBsPedYfcb/s0VuSxZ1pv/FvHX0H6GJmoYhkgp8TyP+/s7xnopYnBJ2Tmk7juIKwe1KJ+SPhcMgsDy9g5fg3GtSslSz0zp38ds9KXYS/wBV7dcBro+jq0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;PER 메커니즘&quot;
        title=&quot;&quot;
        src=&quot;/static/28f4cbeab1519b3f52dd06f4a26f14df/2d920/cache-stampede-per.png&quot;
        srcset=&quot;/static/28f4cbeab1519b3f52dd06f4a26f14df/222b7/cache-stampede-per.png 163w,
/static/28f4cbeab1519b3f52dd06f4a26f14df/ff46a/cache-stampede-per.png 325w,
/static/28f4cbeab1519b3f52dd06f4a26f14df/2d920/cache-stampede-per.png 536w&quot;
        sizes=&quot;(max-width: 536px) 100vw, 536px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;캐시가 만료되기 전에 미리 갱신하는 방식이다. 확률적 갱신을 적용하며, TTL이 적게 남을수록 확률이 높아진다.&lt;/p&gt;
&lt;p&gt;만료전에 캐시를 갱신한다는 점, 모든 요청이 갱신하는 것이 아닌 특정 확률로 동작한다는 점에서 Stampede 현상을 방지할 수 있다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;retrieveData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Data &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 1. 캐시에서 데이터와 메타정보를 조회한다.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; cached &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getWithMetadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; run &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 2. 캐시 미스 시 DB에서 조회 후 저장한다.&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; queryTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; start

            cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Duration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ofMinutes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; queryTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 3. 만료 전 갱신이 필요한지 확률적으로 계산한다.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; ttlRemaining &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cached&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ttl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toSeconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// `BETA`는 갱신 적극성을 조절하는 상수로, 일반적으로 1.0을 사용한다.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; threshold &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cached&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queryTime &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; BETA &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextDouble&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 4. TTL이 적게 남을수록 갱신 확률이 높아진다.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ttlRemaining &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; threshold &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;refreshAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cached&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;3-ttl-jitter&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-ttl-jitter&quot; aria-label=&quot;3 ttl jitter permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3. TTL Jitter&lt;/h3&gt;
&lt;p&gt;동일한 TTL을 여러 키에 부여하면, 동시에 다수의 키가 만료될 수 있다.&lt;/p&gt;
&lt;p&gt;TTL Jitter는 만료 시간을 분산시켜서 Stampede 현상을 방지하는 방법이다.
캐시 만료 시간에 랜덤 값을 추가하는 방식으로 구현한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ttl&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Duration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// TTL에 +- 10% 범위에서 무작위 값을 추가한다.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; jitterRange &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ttl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; jitter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nextDouble&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; jitterRange
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; actualTtl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ttl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; jitter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Duration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ofMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;actualTtl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;references&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#references&quot; aria-label=&quot;references permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cache_stampede&quot;&gt;Cache stampede - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.designgurus.io/answers/detail/what-is-a-cache-stampede-and-how-to-prevent-it&quot;&gt;What is a Cache Stampede and How to Prevent It? - Design Gurus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cloudflare.com/sometimes-i-cache/&quot;&gt;Sometimes I cache: implementing lock-free probabilistic caching - Cloudflare&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Java Annotation]]></title><description><![CDATA[스프링을 사용해서 개발을 하다보면    같은 Annotation을 자주 접하게 된다.
스프링에서는 이러한 Annotation들을 컴포넌트 스캔 과정에서 메타 정보로 활용하고, 빈을 생성하여 IoC…]]></description><link>https://steadiness.dev/java-annotation/</link><guid isPermaLink="false">https://steadiness.dev/java-annotation/</guid><pubDate>Sun, 10 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;스프링을 사용해서 개발을 하다보면 &lt;code class=&quot;language-text&quot;&gt;@RestController&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;@Service&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;@Component&lt;/code&gt; 같은 Annotation을 자주 접하게 된다.
스프링에서는 이러한 Annotation들을 컴포넌트 스캔 과정에서 메타 정보로 활용하고, 빈을 생성하여 IoC 컨테이너를 구성한다.
이처럼 어노테이션은 코드의 메타 데이터를 제공하는 역할을 한다.&lt;/p&gt;
&lt;p&gt;어노테이션을 활용하면 컴파일 타임, 실행 시점에 해당 메타 정보를 제공하여 동적으로 특정 코드를 생성하거나 기능을 수행시킬 수 있다.&lt;/p&gt;
&lt;p&gt;본 글에서는 어노테이션을 구성하는 각 요소들과 커스텀 어노테이션 생성 방법을 정리해보고자 한다.&lt;/p&gt;
&lt;h1 id=&quot;annotation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#annotation&quot; aria-label=&quot;annotation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Annotation&lt;/h1&gt;
&lt;p&gt;어노테이션을 정의 할때는 Java에 내장된 몇 가지 메타 어노테이션들을 같이 활용한다.
자주 사용되는 메타 어노테이션은 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@Retention&lt;/code&gt; : 컴파일 ~ 런타임 과정 중 어느 단계까지 유지되어야 하는지를 결정한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@Target&lt;/code&gt; : 어노테이션 선언이 가능한 위치 정보를 설정한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@Documented&lt;/code&gt; : Javadoc에 관련 정보를 함께 생성할 것인지 여부를 결정한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;@Inherited&lt;/code&gt; : 상위 타입에 어노테이션이 적용되어 있는 경우, 하위 타입에 이를 전파할 것인지 결정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;METHOD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Inherited&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Documented&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExampleAnnotation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;This is example.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;retention&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#retention&quot; aria-label=&quot;retention permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;@Retention&lt;/h2&gt;
&lt;p&gt;Java는 소스 코드를 작성하고, 작성된 소스 코드를 컴파일하고, 컴파일 된 바이트 코드가 클래스 로더에 의해 JVM에 올라가는 과정을 거쳐서 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fa5ac141688cb21161e97266147011cd/8f5d7/java-annotation01.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 38.65030674846626%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAvElEQVR42qVROQ6EMAzk/y+iQaKgpOAZQEIOyH3tTkBClKx2FFnWxDO27ObzB5r3pTFG5xyiDxVVnFICZa0F+6zzvtY8SWNMDEEKgYj6KpZSMsb4CaUU4nEcy7IQQoQQlFIwiPu+z/PMp4kPgxzHg/Mq1lrDBq5Ics4wQk/ksNAn8LtRiungsva9bFvWdWJdq7iUgskxHpT3hOWBm1RaB84TIXHbtFJvF3ZZoIH13oTg7oX9dJuS8/WuU30Bv7LTX4c8ZyQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Alt Image&quot;
        title=&quot;&quot;
        src=&quot;/static/fa5ac141688cb21161e97266147011cd/a6d36/java-annotation01.png&quot;
        srcset=&quot;/static/fa5ac141688cb21161e97266147011cd/222b7/java-annotation01.png 163w,
/static/fa5ac141688cb21161e97266147011cd/ff46a/java-annotation01.png 325w,
/static/fa5ac141688cb21161e97266147011cd/a6d36/java-annotation01.png 650w,
/static/fa5ac141688cb21161e97266147011cd/e548f/java-annotation01.png 975w,
/static/fa5ac141688cb21161e97266147011cd/3c492/java-annotation01.png 1300w,
/static/fa5ac141688cb21161e97266147011cd/8f5d7/java-annotation01.png 1475w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;여기서 &lt;code class=&quot;language-text&quot;&gt;@Retention&lt;/code&gt; 은 Annotation이 어느 단계까지 유지되어야 하는지를 결정한다. &lt;code class=&quot;language-text&quot;&gt;SOURCE&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;CLASS&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;RUNTIME&lt;/code&gt; 세가지 타입이 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/67d0ca883dfef473b30be2318c057e77/229ad/java-annotation02.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.423312883435585%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA+0lEQVR42pWQy27DIBBF/f9fVambqquoyiZ9KbbjJOZpGGCweRUnTaosOxoYdGHgcppSSs4lpRLTurhHTjnFVDNf1Ptca76WUpqckjOn2Q4IPZpjjKGqIQSmOTdCGClAxBiruCyzNYDOzogGIKXUxLhY+QbsVZGXWW89WmOsdXb09ADHXg/EMaUUpRQRJaecjIISRsZ649ps+IYens/7Jys2zpnd7l2qiXj+Sb4/yBdBRhntu957T8fT0LVDu2fjeX25DqfbiF30nYO2elsdhqXaln5Ss6bAw8V2bQaljFYwSVBT/Xbziyf/0coP3MqN0wO262ZzO3LPf8QP+7yUriAr6wsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Alt Image&quot;
        title=&quot;&quot;
        src=&quot;/static/67d0ca883dfef473b30be2318c057e77/a6d36/java-annotation02.png&quot;
        srcset=&quot;/static/67d0ca883dfef473b30be2318c057e77/222b7/java-annotation02.png 163w,
/static/67d0ca883dfef473b30be2318c057e77/ff46a/java-annotation02.png 325w,
/static/67d0ca883dfef473b30be2318c057e77/a6d36/java-annotation02.png 650w,
/static/67d0ca883dfef473b30be2318c057e77/e548f/java-annotation02.png 975w,
/static/67d0ca883dfef473b30be2318c057e77/3c492/java-annotation02.png 1300w,
/static/67d0ca883dfef473b30be2318c057e77/229ad/java-annotation02.png 1356w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;retentionpolicysource&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#retentionpolicysource&quot; aria-label=&quot;retentionpolicysource permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RetentionPolicy.SOURCE&lt;/h3&gt;
&lt;p&gt;컴파일 단계에서 컴파일러게 관련 정보를 제공하지만, 바이트 코드(&lt;code class=&quot;language-text&quot;&gt;.class&lt;/code&gt;)에는 기록되지 않는다
익숙한 &lt;code class=&quot;language-text&quot;&gt;@Override&lt;/code&gt;로 예를 들어보면, 서브 타입에서 메서드 오버라이딩을 하는 경우, &lt;code class=&quot;language-text&quot;&gt;@Override&lt;/code&gt; 가 선언되어 있다면 컴파일 타임에 메서드 시그니처가 잘 선언되어 있는지 검증한다.
그러나 바이트 코드에는 &lt;code class=&quot;language-text&quot;&gt;@Override&lt;/code&gt;가 남아있지 않다.&lt;/p&gt;
&lt;h3 id=&quot;retentionpolicyclass&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#retentionpolicyclass&quot; aria-label=&quot;retentionpolicyclass permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RetentionPolicy.CLASS&lt;/h3&gt;
&lt;p&gt;컴파일 단계를 거쳐 바이트 코드까지 유지되지만 런타임에는 유지되지 않는다.&lt;/p&gt;
&lt;h3 id=&quot;retentionpolicyruntime&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#retentionpolicyruntime&quot; aria-label=&quot;retentionpolicyruntime permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RetentionPolicy.RUNTIME&lt;/h3&gt;
&lt;p&gt;런타임까지 유지되며, &lt;code class=&quot;language-text&quot;&gt;리플렉션&lt;/code&gt;을 통해 해당 메타 정보에 접근할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/cb7774f72a615f1e5dab4b724dffdc67/8f5d7/java-annotation03.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.920245398773%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsTAAALEwEAmpwYAAABD0lEQVR42qVRS27DIBD1/Y+UKDmCb5BFdlHcGBswMD/ow1abbqOOntDMm/8wtE+k1qo/AnP4KLMQETOQczazAdTOcCkF9juu9Ei8R5ODTNtGMSbnyraJ6gDf9Hw6N0Occyml4P26ro/HY1mWaZqghxD6m9J0u83n83o6vcaRax3QLUbwOaKYKPohFBVD8DFG7z10KCiEqdz9/rpc/PX6NY6CsTFNk1KNG8Cxv9WagtFW+6zvnVtLMZZlKfMcnNO+c6ukJFXERCWxUjeNwWhVMrZqv/kCMQNYBCfAwQx3UUwMqBWmTHn/CxHqJ4MDhv1BxaqK5OPaOePWTB1IOMBE3s1+nrnrh/et0P41Q/uHfAOf1EhlQJmfpQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Alt Image&quot;
        title=&quot;&quot;
        src=&quot;/static/cb7774f72a615f1e5dab4b724dffdc67/a6d36/java-annotation03.png&quot;
        srcset=&quot;/static/cb7774f72a615f1e5dab4b724dffdc67/222b7/java-annotation03.png 163w,
/static/cb7774f72a615f1e5dab4b724dffdc67/ff46a/java-annotation03.png 325w,
/static/cb7774f72a615f1e5dab4b724dffdc67/a6d36/java-annotation03.png 650w,
/static/cb7774f72a615f1e5dab4b724dffdc67/e548f/java-annotation03.png 975w,
/static/cb7774f72a615f1e5dab4b724dffdc67/3c492/java-annotation03.png 1300w,
/static/cb7774f72a615f1e5dab4b724dffdc67/8f5d7/java-annotation03.png 1475w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;class-vs-runtime&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#class-vs-runtime&quot; aria-label=&quot;class vs runtime permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;CLASS VS RUNTIME&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;CLASS&lt;/code&gt;와 &lt;code class=&quot;language-text&quot;&gt;RUNTIME&lt;/code&gt; 의 중요한 차이점은 &lt;strong&gt;리플렉션 활용 가능 여부&lt;/strong&gt;이다. &lt;code class=&quot;language-text&quot;&gt;RUNTIME&lt;/code&gt;으로 선언되어야만 실행 중에도 해당 메타 정보를 활용할 수 있다.&lt;/p&gt;
&lt;p&gt;코드를 통해 확인해보자.&lt;/p&gt;
&lt;h3 id=&quot;retentionpolicyclass-1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#retentionpolicyclass-1&quot; aria-label=&quot;retentionpolicyclass 1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RetentionPolicy.CLASS&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CLASS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassRetentionAnnotation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ClassRetentionAnnotation&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassRetentionTarget&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReflectionTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doClassRetentionTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ClassRetentionAnnotation&lt;/span&gt; annotation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassRetentionTarget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ClassRetentionAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;// RetentionPolicy.CLASS 인 경우 리플렉션으로 활용 불가&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/bee3a1cbda9769ae94c37f4e777a91b4/d3d45/java-annotation04.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.993865030674847%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAmUlEQVR42q2OwQ7DIAxD+ZJCISGBrZ06dqiK9v+f5QHSpkm99vCkxI6tGFHGu1jcMyEQg0PAPM8InkC6IubcdBraPxQZqXkpZVDzmXlgbrogtNJ1e2BdFsQYx4H3vgXdCPf5S99zK+phO01w1sI5N+ieURGICp6vDbVW1OPAvu8jpKonpN2XUqBJYZ09fW6ipF/7FRiJ+dLCDwLbjjJkFFsuAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Alt Image&quot;
        title=&quot;&quot;
        src=&quot;/static/bee3a1cbda9769ae94c37f4e777a91b4/a6d36/java-annotation04.png&quot;
        srcset=&quot;/static/bee3a1cbda9769ae94c37f4e777a91b4/222b7/java-annotation04.png 163w,
/static/bee3a1cbda9769ae94c37f4e777a91b4/ff46a/java-annotation04.png 325w,
/static/bee3a1cbda9769ae94c37f4e777a91b4/a6d36/java-annotation04.png 650w,
/static/bee3a1cbda9769ae94c37f4e777a91b4/e548f/java-annotation04.png 975w,
/static/bee3a1cbda9769ae94c37f4e777a91b4/3c492/java-annotation04.png 1300w,
/static/bee3a1cbda9769ae94c37f4e777a91b4/d3d45/java-annotation04.png 1614w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;retentionpolicyruntime-1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#retentionpolicyruntime-1&quot; aria-label=&quot;retentionpolicyruntime 1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RetentionPolicy.RUNTIME&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RuntimeRetentionAnnotation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@RuntimeRetentionAnnotation&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RuntimeRetentionTarget&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReflectionTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doRuntimeRetentionTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;RuntimeRetentionAnnotation&lt;/span&gt; annotation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RuntimeRetentionTarget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RuntimeRetentionAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNotNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/927030f0d3afd41d9398e70caee666d7/50e7d/java-annotation05.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 26.993865030674847%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAzklEQVR42n2PUXLDIAxEfRQIICyDwY4bu8VxkvtfaovwTCcfmX680ayQVks3sMdrc7h9TYgxgX0P7wnMI0KewXGo2oOIGswMz6d2jqC1hjEGWimoSscU4OvSz16wXK9tWXDOwlpTq/vDWoucM2KIGELAXOfnZUGqvTElXKpxJ4NaKxh3Lp+XpXeB0ufVdySR73us24b98cDxfKLsezOnGqQj4hZXBt8NRH9C3iRpHEekaUKuSBXdDD0N/xp8MpRf3NYV5X5vCffjwHcpzfwXgv6SlFVQkesAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Alt Image&quot;
        title=&quot;&quot;
        src=&quot;/static/927030f0d3afd41d9398e70caee666d7/a6d36/java-annotation05.png&quot;
        srcset=&quot;/static/927030f0d3afd41d9398e70caee666d7/222b7/java-annotation05.png 163w,
/static/927030f0d3afd41d9398e70caee666d7/ff46a/java-annotation05.png 325w,
/static/927030f0d3afd41d9398e70caee666d7/a6d36/java-annotation05.png 650w,
/static/927030f0d3afd41d9398e70caee666d7/e548f/java-annotation05.png 975w,
/static/927030f0d3afd41d9398e70caee666d7/3c492/java-annotation05.png 1300w,
/static/927030f0d3afd41d9398e70caee666d7/50e7d/java-annotation05.png 1738w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;target&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#target&quot; aria-label=&quot;target permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;@Target&lt;/h2&gt;
&lt;p&gt;어노테이션이 어디에 위치할 수 있는지를 결정한다.
하고, 메서드에 적용하기 위해서는 &lt;code class=&quot;language-text&quot;&gt;ElementType.METHOD&lt;/code&gt; 가 필요하다. 타겟은 여러개 지정 가능하며 다음과 같이 선언한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;METHOD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SOURCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MultipleTargetAnnotation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ElementType&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;TYPE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;클래스, 인터페이스, 어노테이션 타입, 열거 타입에 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;FIELD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;필드(멤버 변수)에 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;METHOD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;메서드에 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;PARAMETER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;메서드 파라미터에 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;CONSTRUCTOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;생성자에 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;LOCAL_VARIABLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;지역 변수에 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;ANNOTATION_TYPE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;어노테이션에 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;PACKAGE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;패키지 선언에 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;TYPE_PARAMETER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;제네릭 타입 선언의 타입 파라미터 (클래스, 메서드)&lt;br&gt;(Java 8 이상)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;TYPE_USE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;필드, 메서드 리턴 타입, 메서드 파라미터, 생성자 파라미터 등 타입 사용의 모든 경우에 적용 가능 &lt;br&gt;(Java 8 이상)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;MODULE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;모듈 선언 파일 (module-info.java)에 적용 &lt;br&gt;(Java 9 이상)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;RECORD_COMPONENT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;레코드의 각 컴포넌트(레코드 내에 선언된 필드)에 적용 가능 (Java 16이상)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;element&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#element&quot; aria-label=&quot;element permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Element&lt;/h2&gt;
&lt;p&gt;어노테이션 내부에 요소들을 정의하고, 어노테이션이 사용되는 선언부에 특정 값을 할당하여 사용할 수 있다.
해당 메타 정보들은 런타임에 리플렉션을 통해 접근 할 수 있다.
Element는 &lt;code class=&quot;language-text&quot;&gt;반환 타입&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;참조 될 이름&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;기본 값&lt;/code&gt; 으로 구성된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExampleAnnotation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/**
	 * Element 1
	 */&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;This is example.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/**
	 * Element 2
	 */&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;첫 번째 요소인 &lt;code class=&quot;language-text&quot;&gt;value&lt;/code&gt; 에는 기본 값으로 &lt;code class=&quot;language-text&quot;&gt;This is example.&lt;/code&gt; 이 설정되어있고, &lt;code class=&quot;language-text&quot;&gt;visible&lt;/code&gt; 에는 true가 지정되어 있다.&lt;/p&gt;
&lt;p&gt;만약 다음과 같이 &lt;code class=&quot;language-text&quot;&gt;value&lt;/code&gt;에 다른 값을 할당하면 &lt;code class=&quot;language-text&quot;&gt;value&lt;/code&gt; 에는 해당 값이 저장된다. 반면 &lt;code class=&quot;language-text&quot;&gt;visible&lt;/code&gt;에는 별도 값을 지정하지 않았기 때문에 기본 값으로 설정되어 있는 true가 유지된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ExampleAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;This is value.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExampleAnnotationTarget&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AnnotationElementTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ExampleAnnotation&lt;/span&gt; annotation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExampleAnnotationTarget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ExampleAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// value에 지정한 값을 참조한다.&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;This is value.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// visible에 default로 선언된 true 값을 가진다.&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;annotation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isTrue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;documented&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#documented&quot; aria-label=&quot;documented permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;@Documented&lt;/h2&gt;
&lt;p&gt;만약 &lt;code class=&quot;language-text&quot;&gt;@Documented&lt;/code&gt;가 선언되어 있는 경우, Javadoc에 해당 어노테이션 정보를 함께 기록한다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;METHOD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Documented&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;-&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExampleAnnotation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;This is example.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 *  `@ExampleAnnotation`에 `@Documented` 가 선언되어 있는 경우
 *   `ExampleAnnotationTarget`의 Javadoc에
 *   어노테이션 정보가 추가된다.
 */&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ExampleAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;This is value.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExampleAnnotationTarget&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0a96239914f36a1157889451cdf7d1e4/ef0e6/java-annotation07.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.104294478527606%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA6UlEQVR42qWQX0vDMBTF8/2/j0+WOR9WH7SsCGMqm4pzW9M2/5ukbR6OiQ8TFKbFwI8Ll3tzzrnkYlFinue4zAtkd2tktyvMijWuy6ezzJcPyIpHXJXbE7PlBoRxAdoKMM7hncV/H6npEYf9Djx96D1CCJMYv0GazQ3oyz2MCxBSQukO2tjJpD3nPEhFKd7e95DKwHQOzvc/sHHwNzrr0PdDjNwy7A4VqrrBsW7BpI5ODXiqUSShtP0UO0dymcQJpQ22z69omICKzeRUqu6rxihqSuRklYt0Ow0fLQ9jwDCMJ/o/4tP8GPAB5hhhafzMevIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Alt Image&quot;
        title=&quot;&quot;
        src=&quot;/static/0a96239914f36a1157889451cdf7d1e4/a6d36/java-annotation07.png&quot;
        srcset=&quot;/static/0a96239914f36a1157889451cdf7d1e4/222b7/java-annotation07.png 163w,
/static/0a96239914f36a1157889451cdf7d1e4/ff46a/java-annotation07.png 325w,
/static/0a96239914f36a1157889451cdf7d1e4/a6d36/java-annotation07.png 650w,
/static/0a96239914f36a1157889451cdf7d1e4/e548f/java-annotation07.png 975w,
/static/0a96239914f36a1157889451cdf7d1e4/3c492/java-annotation07.png 1300w,
/static/0a96239914f36a1157889451cdf7d1e4/ef0e6/java-annotation07.png 3832w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;만약 &lt;code class=&quot;language-text&quot;&gt;@Documented&lt;/code&gt; 가 없는 경우, 아래와 같이 &lt;code class=&quot;language-text&quot;&gt;@ExampleAnnotation&lt;/code&gt; 정보를 찾을 수 없다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/68bd7f8829c8484f2a90577b746d0b91/7ed3f/java-annotation08.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.263803680981596%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA9ElEQVR42qWRW0sDMRCF8///ioisfay41OqDlFbxyRcvVDZpaLa7uWw2mwWPk6ggPrRFAx8nmZyZzBB2crnARTlFUd6imN/jbLbKOrl5OMhpuUBxtcTk+i5zPl+BbVUNqRqoeoeh9wDe8Z/FNnwNXq2hjUYIATFGjON4NPEXTD7OoPgTrI9oWg3jOtg/YGwH13mwSki8cYHW2BzsfA+fCfB9yJpih3Aph/yMb6hgJSDkFlwqqMZgR9RJ209a4/Jj+9DkcS53KPD88kqJGpZa1unyi58Jlsz7MNbTyD2Yo/lbramARR8GhCGSRvqgmPff52NI3g/vYl7RsVrzEAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Alt Image&quot;
        title=&quot;&quot;
        src=&quot;/static/68bd7f8829c8484f2a90577b746d0b91/a6d36/java-annotation08.png&quot;
        srcset=&quot;/static/68bd7f8829c8484f2a90577b746d0b91/222b7/java-annotation08.png 163w,
/static/68bd7f8829c8484f2a90577b746d0b91/ff46a/java-annotation08.png 325w,
/static/68bd7f8829c8484f2a90577b746d0b91/a6d36/java-annotation08.png 650w,
/static/68bd7f8829c8484f2a90577b746d0b91/e548f/java-annotation08.png 975w,
/static/68bd7f8829c8484f2a90577b746d0b91/3c492/java-annotation08.png 1300w,
/static/68bd7f8829c8484f2a90577b746d0b91/7ed3f/java-annotation08.png 3830w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;inherited&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#inherited&quot; aria-label=&quot;inherited permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;@Inherited&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;@Inherited&lt;/code&gt; 는 서브 타입으로의 어노테이션 전파 여부를 결정한다.
만약 어노테이션에 &lt;code class=&quot;language-text&quot;&gt;@Inherited&lt;/code&gt; 가 선언되어 있다면
하위 클래스에 직접 선언하지 않더라도 해당 어노테이션이 동일하게 적용된다.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Inherited&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ParentAnnotation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@ParentAnnotation&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ParentClass&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChildAnnotation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 *  `@ParentAnnotation`이 선언되어 있지 않다.
 */&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ChildAnnotation&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChildClass&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ParentClass&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InheritedAnnotationTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ParentAnnotation&lt;/span&gt; parentAnnotation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChildClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ParentAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ChildAnnotation&lt;/span&gt; childAnnotation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChildClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ChildAnnotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;/**
		 *  ChilClass에 `@ParentAnnotation`이 없음에도
		 *  직접 참조할 수 있다.
		 */&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parentAnnotation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNotNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;childAnnotation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNotNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b35d1820ee645076e39c45dd6073acbd/baa75/java-annotation09.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.607361963190186%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA7ElEQVR42p2Py3KEIBBF/ZIR5CEoKDgaRxwXSf7/m266yVSqskwWp7roau6jORaJ98OjbBHXW8Rzj9izg9YaklEdhBA/tG0LKSWMNTDGQCkF1XV18r75eCh8ngNyTkjBIww9gu8xxhWBdsMU0VsL+8I5BzcQ3mMYx2rMYjzZsNHOYy0HSjkxhlCPbN+Tu4alBJWXGCeKMSKlVAWneUK+L4jThLQs1ayRUkBbVT90FJ35richpPhVl6l1SXjOGeu24VEKtn3Hsq5ItGv44K9wxTsJ7CR2XheO86yB2tvtn4LUIlD1mapzMobfPL8A61KvpEsAnZkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Alt Image&quot;
        title=&quot;&quot;
        src=&quot;/static/b35d1820ee645076e39c45dd6073acbd/a6d36/java-annotation09.png&quot;
        srcset=&quot;/static/b35d1820ee645076e39c45dd6073acbd/222b7/java-annotation09.png 163w,
/static/b35d1820ee645076e39c45dd6073acbd/ff46a/java-annotation09.png 325w,
/static/b35d1820ee645076e39c45dd6073acbd/a6d36/java-annotation09.png 650w,
/static/b35d1820ee645076e39c45dd6073acbd/e548f/java-annotation09.png 975w,
/static/b35d1820ee645076e39c45dd6073acbd/3c492/java-annotation09.png 1300w,
/static/b35d1820ee645076e39c45dd6073acbd/baa75/java-annotation09.png 1746w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;만약 &lt;code class=&quot;language-text&quot;&gt;@ParentAnnotation&lt;/code&gt;에 선언되어 있는 &lt;code class=&quot;language-text&quot;&gt;@Inherited&lt;/code&gt; 를 제거하면, 아래와 같이 테스트가 실패하게 된다.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/322b9bd24edc56f4ab1bb44fd73b322c/966ce/java-annotation10.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25.766871165644172%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAzUlEQVR42n2PXU7EMAyEcxMSO3Z+2nRZoS1leef+NxomFUKLQDx8spVMZibhenF8vCvu+wXHy4r9uWJpDdZWWHGkmJBSQuSsljAswzXD3KGq590jYa0dowsFBSKZDwUxCYRiEfkWKk00fxnIZOrSL0I2x1gr3o4dYww0titsVnxSuBc497FtGK1jYWhlmM4QBptM9CRzD8UrvxX/rP8DipfsuJeOwxtuVvHKufGssf2iMygjWC78Zjz5z/CJ9/PhjQWuVjDUMLKdRv3B8BM7448s4JaTVAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Alt Image&quot;
        title=&quot;&quot;
        src=&quot;/static/322b9bd24edc56f4ab1bb44fd73b322c/a6d36/java-annotation10.png&quot;
        srcset=&quot;/static/322b9bd24edc56f4ab1bb44fd73b322c/222b7/java-annotation10.png 163w,
/static/322b9bd24edc56f4ab1bb44fd73b322c/ff46a/java-annotation10.png 325w,
/static/322b9bd24edc56f4ab1bb44fd73b322c/a6d36/java-annotation10.png 650w,
/static/322b9bd24edc56f4ab1bb44fd73b322c/e548f/java-annotation10.png 975w,
/static/322b9bd24edc56f4ab1bb44fd73b322c/3c492/java-annotation10.png 1300w,
/static/322b9bd24edc56f4ab1bb44fd73b322c/966ce/java-annotation10.png 2050w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;custom-annotation-생성-및-적용-예시&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#custom-annotation-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%A0%81%EC%9A%A9-%EC%98%88%EC%8B%9C&quot; aria-label=&quot;custom annotation 생성 및 적용 예시 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Custom Annotation 생성 및 적용 예시&lt;/h2&gt;
&lt;p&gt;지금까지 어노테이션 관련 기본 내용들을 살펴보았다.
지금부터는 커스텀한 어노테이션을 직접 재정의 해보면서 해당 내용들을 다시
해당 예시에서는 기존에 익숙하게 사용했을 법한 &lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt; 어노테이션을 활용하였다.&lt;/p&gt;
&lt;p&gt;JPA를 사용할 때 데이터 읽기 작업만을 수행하는 경우, 불필요한 더티 체킹이 발생하지 않도록 &lt;code class=&quot;language-text&quot;&gt;@Transactional(readOnly=true)&lt;/code&gt; 를 지정한다.&lt;/p&gt;
&lt;p&gt;이를 커스텀한 어노테이션으로 재정의하고 사용해보자.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ElementType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;METHOD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Retention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RetentionPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RUNTIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Transactional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;readOnly &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token annotation punctuation&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadOnlyTransactional&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;@Target : 기존 &lt;code class=&quot;language-text&quot;&gt;@Transactional&lt;/code&gt; 과 동일하게 클래스, 메서드에 모두 사용할 수 있도록 &lt;code class=&quot;language-text&quot;&gt;ElementType.TYPE&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;ElementType.METHOD&lt;/code&gt; 를 속성으로 지정하였다.&lt;/li&gt;
&lt;li&gt;@Retention : 런타임에 참조될 수 있도록 하기 위해 &lt;code class=&quot;language-text&quot;&gt;RetentionPolicy.RUNTIME&lt;/code&gt; 을 설정하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Service&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransactionService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;/**
	 * `@Transactional(readOnly=true)`과 동일한 동작을 한다.
	 */&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@ReadOnlyTransactional&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isReadOnlyTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransactionSynchronizationManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isCurrentTransactionReadOnly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransactionServiceTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransactionService&lt;/span&gt; transactionService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readOnlyTransactionTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; readOnlyTransaction &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; transactionService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isReadOnlyTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;readOnlyTransaction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isTrue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 650px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c67f15b776968469d1c11ec34edf200c/1e1c3/java-annotation11.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.607361963190186%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA2UlEQVR42p2QXW6DMBCEfRLA2OB/bGhIADeV8tT7n2i6dpSoqlJV6sNo5dXMt7tmn4dAmBd452CUxjBIWJugwxv8EjGOClLKp7TW0FbDTxN8mMg/kGeEMQZCCLB1W5HzO2KMtVkCSqt78IX2fa+V97wOKMC2bdE0DVoSE1rCOEMbenjvK7Tve3DOX0opGkae8+WCfL3i43ZDWujCEJDmGayYCuC7foM9VE6cCbISNKZU348s+yv8U13XYSDAcjrhvG3Yc67VWFu3/B+Q/q0AVwJtx1FBBeicwxeDN68c9vVPugAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Alt Image&quot;
        title=&quot;&quot;
        src=&quot;/static/c67f15b776968469d1c11ec34edf200c/a6d36/java-annotation11.png&quot;
        srcset=&quot;/static/c67f15b776968469d1c11ec34edf200c/222b7/java-annotation11.png 163w,
/static/c67f15b776968469d1c11ec34edf200c/ff46a/java-annotation11.png 325w,
/static/c67f15b776968469d1c11ec34edf200c/a6d36/java-annotation11.png 650w,
/static/c67f15b776968469d1c11ec34edf200c/e548f/java-annotation11.png 975w,
/static/c67f15b776968469d1c11ec34edf200c/3c492/java-annotation11.png 1300w,
/static/c67f15b776968469d1c11ec34edf200c/1e1c3/java-annotation11.png 1670w&quot;
        sizes=&quot;(max-width: 650px) 100vw, 650px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;references&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#references&quot; aria-label=&quot;references permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/6/docs/api/java/lang/annotation/RetentionPolicy.html&quot;&gt;https://docs.oracle.com/javase/6/docs/api/java/lang/annotation/RetentionPolicy.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.baeldung.com/java-gradle-javadoc&quot;&gt;https://www.baeldung.com/java-gradle-javadoc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[About]]></title><description><![CDATA[Backend Engineer]]></description><link>https://steadiness.dev/default/about/</link><guid isPermaLink="false">https://steadiness.dev/default/about/</guid><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Backend Engineer&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Default Private]]></title><description><![CDATA[Default Private Posts This is a default private post. It's recommended not to delete this posts 😵 (현재 이 포스트는 삭제하지 말아주세요.)]]></description><link>https://steadiness.dev/default/private-default/</link><guid isPermaLink="false">https://steadiness.dev/default/private-default/</guid><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;default-private-posts&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#default-private-posts&quot; aria-label=&quot;default private posts permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Default Private Posts&lt;/h2&gt;
&lt;p&gt;This is a default private post. It&apos;s recommended not to delete this posts 😵&lt;/p&gt;
&lt;p&gt;(현재 이 포스트는 삭제하지 말아주세요.)&lt;/p&gt;</content:encoded></item></channel></rss>