{"componentChunkName":"component---src-templates-post-template-jsx","path":"/works/posts/2021-12-05--001","result":{"data":{"site":{"siteMetadata":{"title":"Blog by Eunyoung","subtitle":"작업 기록 블로그","copyright":"© All rights reserved.","author":{"name":"EunYoung","twitter":"#"},"disqusShortname":"","url":"https://ssongey.github.io"}},"markdownRemark":{"id":"98ee4581-547a-5f29-b535-f56113b4b50d","html":"<br/>\n<p>이번에 진행하게된 프로젝트에서 레디스 동시성 이슈에 대한 문제에 부딪혔다.<br>\n기존에는 어떤 방식을 사용했고, 어떻게 수정을 했는지에 대해 기록한다.</p>\n<br/>\n<h2>✔️ 이슈내용</h2>\n<ul>\n<li>레디스에 넣은 key 값이 expired 되었을 경우 특정 로직을 수행하는 기능이 있다.</li>\n<li>만료된 키에 대한 이벤트는 Keyspace notifications 을 이용하였고, 이 이벤트는 레디스의 Pub/Sub으로 이벤트를 이용하게 된다.</li>\n<li>즉, 서버에서 <em>`__keyevent@</em>__:expired`* 를 구독하고 있다가, 레디스 내의 키가 expired 가 되면, 서버에서 이벤트를 받아 처리하는 구조이다.</li>\n<li>여기서 문제가 발생하는데, 서버가 1대 일 경우에는 문제가 없지만, <strong>여러대의 서버일 경우에 모든 서버가 이벤트를 받아 처리하므로 중복처리가 발생</strong>하게 된다.</li>\n<li>만약 expired 가 발생할 경우 알람이 발생하도록 로직이 구현되어 있다면, 서버 대수만큼 알람이 발생하게 된다.</li>\n</ul>\n<br/>\n<h2>✔️ 기존방식</h2>\n<p>기존에는 아래와 같은 로직으로 되어 있었다.</p>\n<p>\n  <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/devHistoryBlog/static/04d8a6c46e37deb9da362b94090707b6/ebf47/001-1.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n  \n  <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 688px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 197.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAAACXBIWXMAABYlAAAWJQFJUiTwAAADqklEQVRIx6VXaXPaSBTk//+r1NY6W9nEZWLj2FwOQqATIXTP6Oj0GwROsvsBKaqa0sGo9d50v37DRKkaZugGqqhQlwqN0mgqzfvy/Pwy54YxuYLlJdooQuK6OFgWou0WTXSETtJBoO+AaQ4kCQ5hCGe3g+s4aNIU+niCqtuBgD1odUoJmsJ/26AKQtSndGTKHFoGX247wPMCZFyCpoV5PhhQXipIQkJSCoJ+/PQZIVPN+TwnSYMABawk2JapBpXCjus4Xa5gHY+ItMZ3Ps/5u74x9YlMzBiFnxdA2+L7eo3l66s5qyxDQOmk/L2+GVDSYgS7LMeG7L6uV1i8vWHO8bpcwmLEGX+/GfBy4RHQYzQHXq/9AE6aIaTA95RTWdU3k3MFbBhBwzPIrOd4KAjUUX/NGNlciBGWhdnp0zOCKGaqZ/b1UMCKQ1KTlH2Ou+lX2KcTgrIy7JcDQN9ZLkuEvo/lYoE9yVmvVghY195glntAr5dNQaks5nOkZBddNxzwPeXMvCziNiyLBk3K2aB1/IWUU196272LMD7rbxQpV3Pg6Cibj3f/MOUMbdONM4erFglwpCk8P3+D74fmXo0FrCnigB4Y0mDliOMYruufo/8/QuTZ7+PCsoBFFHIizP50FEXBjxxQcR0lWq1bM1d8UomtiWdK7+HZ3BPLRLjfO3h8fCRoBIv9RKJ0pbccDljSIByWYsI1TSn+NCsQMvKa/aah+BPH5Tkx/Uea3CQn+mz2jIeHBwM6nU5xf3+Pp6cncz2bzfDy8oLNZgvb3mGz3eHfD3+hZr9RqsIj5zQNPYAyU2wZE0nhcDjyBcukmHKigJ/4dXlBIpYI5ZC5MkQJ9TE2UXYsTTlr3l9JkfXJmIrn+SyOjgXTmrOkHlOPvDXreCHEXEsn5KiEjP76J5a1+bKAynoqpQz4iSnUdfcL2CAd1v1X5vOFAfsjHV5SERDRX06z0APN9T+ASp9r2rJsCPum+Y8FFKuvVYOOEa7YRrO+luX5KHNw2aQcDmlMH758gR2faLoVdrS1Ug1oUqaNluc2qpoGe7p2RA36rJpSK9h5Ps6xpZfIYW82cGwb7n4PMoSwqti8Rji2zdRcMnskiPTkgEACtmEljHbsSxtdW1uzWfpzx6awZTsnRiBVM3o7dzXMWGwoxsPfd0hYgmbDOQqw3xJ39LW6ZunRrqSewV6t+IHhW+L+H0BziBgVJbN+g2YLaMWSKPDhm/YrqIKmfFpZT35A5cP/VvwAqqUfMXr1shEAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    >\n      <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;\"\n        alt=\"001 1\"\n        title=\"\"\n        src=\"/devHistoryBlog/static/04d8a6c46e37deb9da362b94090707b6/ebf47/001-1.png\"\n        srcset=\"/devHistoryBlog/static/04d8a6c46e37deb9da362b94090707b6/8ff5a/001-1.png 240w,\n/devHistoryBlog/static/04d8a6c46e37deb9da362b94090707b6/e85cb/001-1.png 480w,\n/devHistoryBlog/static/04d8a6c46e37deb9da362b94090707b6/ebf47/001-1.png 688w\"\n        sizes=\"(max-width: 688px) 100vw, 688px\"\n      />\n    </span>\n  </span>\n  \n  </a>\n    </p>\n<br/>\n<p>아직까지는 큰 문제는 없었다고 하지만, 만약 아래와 같은 상황이 발생한다면 장애로 이어지게 된다.</p>\n<ul>\n<li>한 host에서 2개 이상의 pod가 뜬다면</li>\n<li>deployment 의 replica 감소 등으로 레디스에 등록된 서버가 죽는다면</li>\n</ul>\n<br/>\n<h2>✔️ 수정된 방식</h2>\n<p>찾아보니 레디스에서 <code class=\"language-text\">setnx</code> 명령어를 활용하는 방법이 현재 상황에 가장 알맞아 보였다.</p>\n<ul>\n<li>Redis의 setnx 명령어는 key값이 존재하지 않으면 데이터를 set하는 atomic한 명령어이다. </li>\n<li>이 명령어를 활용하여 개발자가 직접 Lock 프로세스를 구현할 수 있다.</li>\n</ul>\n<p>보통은 spin lock 으로 구현을 하는데 (lock 점유를 루프로 시도), 현재 나의 경우는 점유가 되어 있으면 skip 을 하면 되기 때문에 레디스 부하없이 쉽게 해결이 가능했다.</p>\n<p>\n  <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/devHistoryBlog/static/ba3e60e94d470fdd45e17e2263eb6494/5fd3e/001-2.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n  \n  <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 594px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 202.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAApCAYAAAA1bQl+AAAACXBIWXMAABYlAAAWJQFJUiTwAAAD+ElEQVRIx51WyZLbNhTU/3+MnYOTSm6xneSWsTOrFmskitTCVSQlbgCpTj+IVM1UchDEqlcEQaL5ln4NjJpGw5hqzb0uazSD1QqN7nD55gobXcAOBXQYoktTtElC26MNI6jsYAU6Ml6oDmrnA12H+XIJ1/Pw9PKCpirREbS29pAeqH2GLo5R07uD76MIAugohqKn8kM7wCHsomLoR8xfJtDHEo1Yn1trQCUmuSLAer2Fbk9mrCzALoCyqGIui6pBUTb4/fNXZCxSyTkx1Vzv5Ujz44wUmaYZgkZhVxPw7zt4xyN8Pi/zA7YEb+ntNcUZyYdpVWNHAGc+x3Q6QcCiOKz2fDZDUddwmdf2ylyOND/c08OwLFEeDgbs7u7O3KuiQK013Pxo56EAelx8AlDqFj6pU7YtNJ8zpbAiua8GlIJI4l0uGuzbbA6PXpln5vDAYqlrQx6q3HFBR9qIJ97Kw6kfd9JFNlUeBnVvwr1v3+9R0+vL3C3EFmtJ5gMp8vHjTwjDWFr7/DMBl54fxvK9EJ9R1JIKaYheQM4hc1JzIggiWgC5Yvb1joIh704noxvmh3JJOuqYakQ6oayoSDmafWrkbiQLSlZ5tXKx3+8NkNyjKEKWZXBdD563wXbr890eL+MpnPsHQUfOnzuTMVpymKFBUUwugMulYwDW6zXDDendDnmew3FWHAfGe98PMZ7MsPz+j/E0pXZOJhO04j4bQFHq3oUsC0NOnk6d8XSz2Zq8DOG2/MaELJwkeBsnRM0NUBMl55DfFkUWZuTe4+MT/54bEPnZUJB3RemV6cyM7vI8+r/Su+7aWgf/Q+y2J3bDrlgxbyXFtZMwLTXRtJ64HVOtE5rcP3z6BdskNT0uz4XRxCsBxYOEXBqzYjErFVNdVhSHsGkQ0UQXHea1s1ObCilVRbj1/PgIl1q4ohWkkVwiEFfrofGQYflVRT508EnWhMTe0+OaHtcsvcNOuFq+RGBlCxizdTbUxC3V+1U4yB+sixILerexUeyhyrqXKOHZbPoDb3dDrW6QL9MtPUU23EalK2zB3gEeyT9R5pzh//zrb4jYKUd6K3NWR5FhkxLa+NyofG6j968L3rkTMo8OVcS7ZZMSDsqVsCA56eIsFtQ99jfnhk3KwsMKUQ+45slru9lg8fpKEIX2DaCVhxOGHDLESGnsSJ+EJA9IIZedYkXsYZD3BUlJ8ieKqHBTLLctyjDQPUVUf/oatPBm2lzO2PTo65c/UFC+1Jv5G87YR+iAss6N5uHPvyjxPs/YIRrZ0aw7RfdnbF4VqfLw/ExSn6uu/eAGQFlgztgJNM/UqetCkY8ddfF8xm5vzGF/rm5ZXSVjniJsc/gvBzxtIbfp/6YAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    >\n      <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;\"\n        alt=\"001 2\"\n        title=\"\"\n        src=\"/devHistoryBlog/static/ba3e60e94d470fdd45e17e2263eb6494/5fd3e/001-2.png\"\n        srcset=\"/devHistoryBlog/static/ba3e60e94d470fdd45e17e2263eb6494/8ff5a/001-2.png 240w,\n/devHistoryBlog/static/ba3e60e94d470fdd45e17e2263eb6494/e85cb/001-2.png 480w,\n/devHistoryBlog/static/ba3e60e94d470fdd45e17e2263eb6494/5fd3e/001-2.png 594w\"\n        sizes=\"(max-width: 594px) 100vw, 594px\"\n      />\n    </span>\n  </span>\n  \n  </a>\n    </p>\n<br/>\n<p>코드는 아래와 같다.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">override</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">doHandleMessage</span><span class=\"token punctuation\">(</span>message<span class=\"token operator\">:</span> Message<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">val</span> key <span class=\"token operator\">=</span> <span class=\"token function\">String</span><span class=\"token punctuation\">(</span>message<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">)</span>\n\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">lock</span><span class=\"token punctuation\">(</span>key<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n                <span class=\"token comment\">// 로직 수행</span>\n            <span class=\"token punctuation\">}</span> <span class=\"token keyword\">finally</span> <span class=\"token punctuation\">{</span>\n                <span class=\"token function\">unlock</span><span class=\"token punctuation\">(</span>key<span class=\"token punctuation\">)</span>\n            <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">private</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">lock</span><span class=\"token punctuation\">(</span>key<span class=\"token operator\">:</span> String<span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Boolean <span class=\"token operator\">=</span> redisTemplate<span class=\"token punctuation\">.</span><span class=\"token function\">opsForValue</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">setIfAbsent</span><span class=\"token punctuation\">(</span>key<span class=\"token punctuation\">,</span> <span class=\"token string\">\"lock\"</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">?:</span> <span class=\"token boolean\">false</span>\n\n<span class=\"token keyword\">private</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">unlock</span><span class=\"token punctuation\">(</span>key<span class=\"token operator\">:</span> String<span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> redisTemplate<span class=\"token punctuation\">.</span><span class=\"token function\">delete</span><span class=\"token punctuation\">(</span>key<span class=\"token punctuation\">)</span></code></pre></div>\n<br/>\n<p>참고로, 스핀락을 구현해야하는 상황이면 Lettuce 라이브러리보단 Redisson 라이브러리를 사용하는게 더 낫다..!</p>","fields":{"tagSlugs":["/tags/redis/"],"slug":"/works/posts/2021-12-05--001"},"frontmatter":{"title":"레디스 동시성 이슈","tags":["redis"],"date":"2021-12-05","description":""}}},"pageContext":{"slug":"/works/posts/2021-12-05--001"}},"staticQueryHashes":[]}