<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>완득님의 블로그</title>
    <link>https://minjooig.tistory.com/</link>
    <description>차근차근 쌓아가는 지식</description>
    <language>ko</language>
    <pubDate>Fri, 19 Jun 2026 12:32:11 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>wanduek</managingEditor>
    <image>
      <title>완득님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/7148583/attach/3242bebcad104958a694becd86d5e006</url>
      <link>https://minjooig.tistory.com</link>
    </image>
    <item>
      <title>SQL INJECTION (SQL 인젝션 방지)</title>
      <link>https://minjooig.tistory.com/202</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SQL INJECTION이란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;SQL 인젝션(SQL Injection)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 애플리케이션에서 &lt;b&gt;사용자 입력값을 제대로 검증하지 않고 SQL 쿼리에 포함시킬 때 발생하는 보안 취약점입니다&lt;/b&gt;. 공격자는 이를 악용해 데이터베이스를 임의로 조작하거나 민감한 정보를 탈취할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745934703128&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 로그인 쿼리 예시
SELECT * FROM users WHERE username = 'admin' AND password = '1234';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 쿼리가 아래처럼 사용자 입력을 문자열로 그대로 받는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745934749777&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;username = 'admin' -- '
password = '아무거나'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 최종 쿼리는 이렇게 변합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745934772387&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM users WHERE username = 'admin' -- ' AND password = '아무거나';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--는 SQL 주석이라 이후는 무시되고, 결국 username = 'admin'만으로 로그인하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 username = 'admin' OR '1'='1'로 하게된다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745935310888&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 1 FROM users WHERE username = '1' OR '1'='1' AND password= '...';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리는 다음과 같은 의미로 해석됩니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;550&quot; data-start=&quot;462&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;487&quot; data-start=&quot;462&quot;&gt;username = '1'는 무시되고&lt;/li&gt;
&lt;li data-end=&quot;513&quot; data-start=&quot;488&quot;&gt;'1'='1'는 항상 참이기 때문에&lt;/li&gt;
&lt;li data-end=&quot;550&quot; data-start=&quot;514&quot;&gt;해당 조건을 만족하는 &lt;b&gt;모든 유저들&lt;/b&gt;이 조회될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;576&quot; data-start=&quot;552&quot; data-ke-size=&quot;size16&quot;&gt;이게 바로 &lt;b&gt;SQL 인젝션 공격&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방어방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;820&quot; data-start=&quot;637&quot;&gt;&lt;b&gt;Prepared Statement (PreparedQuery)&lt;/b&gt; 사용&lt;br /&gt;&amp;rarr; SQL 쿼리를 사전에 컴파일하고, 변수만 바인딩해 실행&lt;br /&gt;&amp;rarr; 예: Java의 PreparedStatement, Python의 cursor.execute(&quot;SELECT ... WHERE id = %s&quot;, [user_id])&lt;/li&gt;
&lt;li data-end=&quot;890&quot; data-start=&quot;822&quot;&gt;&lt;b&gt;ORM 사용&lt;/b&gt;&lt;br /&gt;&amp;rarr; JPA, Hibernate, Django ORM 등은 기본적으로 SQL 인젝션에 안전&lt;/li&gt;
&lt;li data-end=&quot;941&quot; data-start=&quot;892&quot;&gt;&lt;b&gt;입력 값 검증 및 필터링&lt;/b&gt;&lt;br /&gt;&amp;rarr; 숫자만 필요한 곳엔 숫자만 받도록 제한&lt;/li&gt;
&lt;li data-end=&quot;1004&quot; data-start=&quot;943&quot;&gt;&lt;b&gt;최소 권한의 DB 계정 사용&lt;/b&gt;&lt;br /&gt;&amp;rarr; 애플리케이션이 DB에 접근할 때, 꼭 필요한 권한만 부여&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>SQL 정리</category>
      <author>wanduek</author>
      <guid isPermaLink="true">https://minjooig.tistory.com/202</guid>
      <comments>https://minjooig.tistory.com/202#entry202comment</comments>
      <pubDate>Tue, 29 Apr 2025 23:03:44 +0900</pubDate>
    </item>
    <item>
      <title>DB 커넥션 수 최적화 전략</title>
      <link>https://minjooig.tistory.com/201</link>
      <description>&lt;h3 id=&quot;10초-이내의-1000-개의-요청이-100개-이상의-커넥션을-맺고-있다면-어떻게-이-커넥션을-줄일-수-있을까?&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;891&quot; data-ke-size=&quot;size23&quot;&gt;만일 다수의 DB 커넥션이 연결되어 있어 리소스의 비용이 낭비되고 있을때 어떻게 해야 커넥션 수를 줄여 리소스 비용을 줄일수 있을까&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;954&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;989&quot; data-ke-size=&quot;size16&quot;&gt;[Access Phase]&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조건 검증 + Rate Limit (Shared dict 활용)
&lt;ul style=&quot;list-style-type: circle;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;조건 검증 : JWT 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;과도 요청시 바로 차단 (return 429)
&lt;ul style=&quot;list-style-type: circle;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;shared dict late limiter를 통해 과도한 접속 제한&lt;/li&gt;
&lt;li&gt;과도하게 접속한 유저는 일정시간동안 접속 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DB 커넥션 연결 전에 최대한 fail fast 처리&lt;/li&gt;
&lt;li&gt;fail fast 처리
&lt;ul style=&quot;list-style-type: circle;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;rate limiter&lt;/li&gt;
&lt;li&gt;header 검사&lt;/li&gt;
&lt;li&gt;JWT Signature Validation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1277&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1279&quot; data-ke-size=&quot;size16&quot;&gt;[Content Phase]&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB 연결 (필요할 때만)&lt;/li&gt;
&lt;li&gt;커넥션 풀 사용 (keepalive)
&lt;ul style=&quot;list-style-type: circle;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;커넥션 풀을 사용하면 미리 일정 수의 DB 연결을 만들어두고, 요청이 올 때마다 풀에서 커넥션을 재사용함. 이렇게 하면 연결과 종료에 드는 시간을 줄여서 응답 속도 개선과 과도한 커넥션 생성을 막고 DB 서버의 부하를 줄일 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;캐싱 (Redis)
&lt;ul style=&quot;list-style-type: circle;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;shared dict보다 더 많은 수용이 가능해야하는 인 메모리 캐시가 필요하다. 즉 shared dict에 비해 더 많은 용량을 사용할 수 있는 Redis를 사용하는 것이 적합하다고 판단함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;캐싱 전략
&lt;ul style=&quot;list-style-type: circle;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;일관성 중요, 커넥션 수 줄이는 것도 중요 그러면 write-through 캐싱전략&lt;/li&gt;
&lt;li&gt;write_through를 통해 DB에 쓰는 작업과 동시에 캐시에 쓰는 작업이기에 데이터 일관성을 유지할 수 있음&lt;/li&gt;
&lt;li&gt;혹여나 캐시 누락 방지를 위해 TTL을 사용하여 캐시 누락을 방지함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1774&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1776&quot; data-ke-size=&quot;size16&quot;&gt;[Log Phase]&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 / 응답 / 커넥션 상태 로깅&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1815&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1817&quot; data-ke-size=&quot;size16&quot;&gt;부하 테스트 (/seller/api/v1/records 기준)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1853&quot; data-ke-size=&quot;size16&quot;&gt;부하 테스트를 통해 keepalive의 적용 전 적용 후를 비교하여 실제 커넥션 수가 얼마나 활성화가 되어 있는지 활성화 되어 있는 커넥션 수가 커넥션 풀로인해 감소가 되는지 커넥션 수가 감소되면 얼마나 감소되는지에 대해 알아보기 위해 WRK와 HEY를 통해 부하 테스트를 진행하게 되었습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2020&quot; data-ke-size=&quot;size16&quot;&gt;WRK 테스트&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2030&quot; data-ke-size=&quot;size16&quot;&gt;-t 10 &amp;rarr; 10개의 Thread&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2051&quot; data-ke-size=&quot;size16&quot;&gt;-c 100 &amp;rarr; 100개의 연결 유지&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2073&quot; data-ke-size=&quot;size16&quot;&gt;-d 10s &amp;rarr; 10초동안 최대 요청&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2095&quot; data-ke-size=&quot;size16&quot;&gt;keepalive 적용 전&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;51e79422-8961-4056-9570-4e870cc00644.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bekmTa/btsNlilp4e8/vssRFJzkViRpOA3ctERte0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bekmTa/btsNlilp4e8/vssRFJzkViRpOA3ctERte0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bekmTa/btsNlilp4e8/vssRFJzkViRpOA3ctERte0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbekmTa%2FbtsNlilp4e8%2FvssRFJzkViRpOA3ctERte0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;212&quot; data-filename=&quot;51e79422-8961-4056-9570-4e870cc00644.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용 후&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;c67f39c4-236f-4560-a4c2-56ce0432d432.png&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;229&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9vqYE/btsNlDa9VdS/I5DIGcXoSk7l3P8R26gDU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9vqYE/btsNlDa9VdS/I5DIGcXoSk7l3P8R26gDU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9vqYE/btsNlDa9VdS/I5DIGcXoSk7l3P8R26gDU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9vqYE%2FbtsNlDa9VdS%2FI5DIGcXoSk7l3P8R26gDU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;752&quot; height=&quot;229&quot; data-filename=&quot;c67f39c4-236f-4560-a4c2-56ce0432d432.png&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;229&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;keepalive 적용 전&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;keepalive 적용 후&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;변화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;평균 Latency&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;102ms&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;58ms&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;43% 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;Requests/sec&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;981 req/s&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1842 req/s&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;88% 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;총 처리 요청 수&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;9875&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;18563&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;88% 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;Transfer/sec&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;3.67MB/s&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;6.89MB/s&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;88% 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2369&quot; data-ke-size=&quot;size16&quot;&gt;HEY 테스트&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2378&quot; data-ke-size=&quot;size16&quot;&gt;-n 1000 &amp;rarr; 총 1000건&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2397&quot; data-ke-size=&quot;size16&quot;&gt;-c 100 &amp;rarr; 동시 100개 연결 유지&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2421&quot; data-ke-size=&quot;size16&quot;&gt;총 1000건 응답&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2434&quot; data-ke-size=&quot;size16&quot;&gt;keepalive 적용 전&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6c582ab1-ddce-4bcc-92eb-8b1954935ab3.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX3VYm/btsNk4npoP9/kLqR5TCkHv7HVoV3DRKqwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX3VYm/btsNk4npoP9/kLqR5TCkHv7HVoV3DRKqwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX3VYm/btsNk4npoP9/kLqR5TCkHv7HVoV3DRKqwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX3VYm%2FbtsNk4npoP9%2FkLqR5TCkHv7HVoV3DRKqwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;600&quot; data-filename=&quot;6c582ab1-ddce-4bcc-92eb-8b1954935ab3.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용 후&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;8014cb83-4cbe-41d9-b2d3-ee658f429fc7.png&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x01WH/btsNkGUPSUd/okYSv3LbvzKdyK2uWc4Ud0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x01WH/btsNkGUPSUd/okYSv3LbvzKdyK2uWc4Ud0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x01WH/btsNkGUPSUd/okYSv3LbvzKdyK2uWc4Ud0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx01WH%2FbtsNkGUPSUd%2FokYSv3LbvzKdyK2uWc4Ud0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;665&quot; height=&quot;599&quot; data-filename=&quot;8014cb83-4cbe-41d9-b2d3-ee658f429fc7.png&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&quot;평균-응답-시간-47%-감소&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2462&quot; data-ke-size=&quot;size23&quot;&gt;평균 응답 시간 47% 감소&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;90.7ms &amp;rarr; 47.8ms로 크게 줄음.&lt;/li&gt;
&lt;li&gt;DB 연결을 매번 새로 열지 않고, 커넥션 풀을 재사용한 효과가 나타남.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;처리-속도-49%-증가&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2552&quot; data-ke-size=&quot;size23&quot;&gt;처리 속도 49% 증가&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초당 처리 가능한 요청 수가 &lt;b&gt;1011 &amp;rarr; 1503&lt;/b&gt;으로 거의 1.5배 증가함.&lt;/li&gt;
&lt;li&gt;이는 시스템 자원을 더 효율적으로 사용하고 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;대다수-요청의-응답-시간-개선&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2645&quot; data-ke-size=&quot;size23&quot;&gt;대다수 요청의 응답 시간 개선&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;75%의 요청이 39.2ms 이내에 처리됨 (keepalive 후)&lt;/li&gt;
&lt;li&gt;반면, keepalive 전에는 75%가 93.4ms 이내에 처리됨.&lt;/li&gt;
&lt;li&gt;&amp;rarr; 빠르게 끝나는 요청이 훨씬 많아졌음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;0d0010a7-51cd-4066-b1c6-b4449bce4662.png&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFgW46/btsNkJ43yVa/pK3SIFWfGyC0x7zwuOhmK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFgW46/btsNkJ43yVa/pK3SIFWfGyC0x7zwuOhmK1/img.png&quot; data-alt=&quot;커넥션 풀 적용 전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFgW46/btsNkJ43yVa/pK3SIFWfGyC0x7zwuOhmK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFgW46%2FbtsNkJ43yVa%2FpK3SIFWfGyC0x7zwuOhmK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;235&quot; height=&quot;125&quot; data-filename=&quot;0d0010a7-51cd-4066-b1c6-b4449bce4662.png&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;커넥션 풀 적용 전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;0daaca7b-89ae-4675-95aa-cc12418cd7af.png&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfD3BW/btsNkIE4CV0/UrGY2kQY97Fp0hXN5rn3E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfD3BW/btsNkIE4CV0/UrGY2kQY97Fp0hXN5rn3E0/img.png&quot; data-alt=&quot;커넥션 풀 적용 후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfD3BW/btsNkIE4CV0/UrGY2kQY97Fp0hXN5rn3E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfD3BW%2FbtsNkIE4CV0%2FUrGY2kQY97Fp0hXN5rn3E0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;229&quot; height=&quot;135&quot; data-filename=&quot;0daaca7b-89ae-4675-95aa-cc12418cd7af.png&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;커넥션 풀 적용 후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2813&quot; data-ke-size=&quot;size16&quot;&gt;동일한 조건으로 Records 데이터를 조회했을 때, 커넥션 풀을 적용하지 않은 경우 PostgreSQL에는 2개의 커넥션이 활성화되었습니다. 이는 매 요청마다 새로운 커넥션을 생성하고 종료하기 때문에 발생하는 현상으로, 시스템 부하 증가 및 자원 낭비로 이어질 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;2969&quot; data-ke-size=&quot;size16&quot;&gt;반면 커넥션 풀(keepalive) 을 적용한 이후에는 1개의 커넥션만 유지된 채 모든 요청이 처리되었습니다. 이는 이미 생성된 커넥션을 재사용함으로써 불필요한 커넥션 생성을 방지하고, 네트워크 및 DB 리소스 사용을 최소화한 결과입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;3104&quot; data-ke-size=&quot;size16&quot;&gt;이 실험을 통해 커넥션 풀 도입이 실제로 DB 커넥션 수를 줄이고, 시스템 자원을 보다 효율적으로 사용하는 데 효과적이라는 것을 확인할 수 있었습니다. (다만 커넥션 풀에 연결할 수 있는 최대 커넥션 수를 테스트 규모에 비해 많이 설정해 놓아 대기 중인 커넥션 풀 수가 많어 다음 커넥션 풀 테스트 진행시 조절을 해야될 것 같다.)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;3104&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;Access-Phase&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;3294&quot; data-ke-size=&quot;size26&quot;&gt;Access Phase&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;3308&quot; data-ke-size=&quot;size16&quot;&gt;access_by_lua에서 효율적인 요청 처리 전략을 구축하기 위해, 조건 검증과 Rate Limiting을 통해 불필요한 DB 요청을 사전에 차단하고, 과도한 트래픽을 제한하는 방식으로 하여 초기에 DB 접근을 최소화 하고자 하였습니다.&lt;/p&gt;
&lt;h3 id=&quot;1.-조건-검증-(Validation)&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;3444&quot; data-ke-size=&quot;size23&quot;&gt;1. 조건 검증 (Validation)&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;3467&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JWT 검증&lt;/b&gt;: Records API 요청에 대해 JWT (JSON Web Token)을 검증하여 사용자의 인증 여부를 확인합니다. JWT 검증은 API 요청의 유효성을 먼저 검사하는 중요한 작업입니다. 이 과정에서 유효하지 않은 토큰을 가진 요청을 빠르게 차단함으로써 DB 및 시스템 자원의 낭비를 방지합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;3642&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Header 및 Parameter 검증: &lt;/b&gt;Records API가 필수적으로 요구하는 targerId나 targetModel 혹은 페이지네이션 파라미터가 누락되었거나 잘못된 형식인 경우 이를 조기에 감지하여 즉시 400 Bad Request을 반환하여 불필요한 DB 및 시스템 자원의 낭비를 방지합니다.&lt;/p&gt;
&lt;h3 id=&quot;2.-Rate-Limiting-(Shared-Dict-활용)&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;3812&quot; data-ke-size=&quot;size23&quot;&gt;2. Rate Limiting (Shared Dict 활용)&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;3847&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;과도한 요청 제한&lt;/b&gt;: Rate Limiting을 통해 유저에 대한 요청을 제한할 수 있습니다. shared dict를 활용하여, 요청의 수가 급증하는 상황에서 과도한 요청을 차단하고, 일정 시간 동안 반복된 요청을 제한합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Shared Dict&lt;/b&gt;를 이용해 각 유저의 요청 횟수를 추적하고, 일정 횟수를 초과한 유저에 대해 제한을 가할 수 있습니다.&lt;/li&gt;
&lt;li&gt;과도한 요청을 보내는 유저는 일정 시간 동안 429 Too Many Requests 상태 코드로 응답하고, 제한된 시간 동안 더 이상 요청을 처리하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4141&quot; data-ke-size=&quot;size16&quot;&gt;이 두가지를 통해 Fail Fast를 하여 시스템에서 오류를 미리 감지하고 빠르게 처리하여 문제의 확산을 막는 전략을 구상하였습니다. DB 커넥션을 설정하기 전에 빠르게 오류를 감지하고, 불필요한 DB 연결을 방지할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4271&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;Shared-Dict을-사용하는-이유는?&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4273&quot; data-ke-size=&quot;size23&quot;&gt;Shared Dict을 사용하는 이유는?&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4297&quot; data-ke-size=&quot;size16&quot;&gt;Rate Limiting은 클라이언트(IP 또는 사용자 기반)의 요청 횟수를 짧은 시간 동안 추적하여 과도한 트래픽을 제어하는 기능입니다. 이때 사용되는 데이터는 단순 카운터이며, 보통 소량의 키-값 정보만을 저장하면 되기 때문에 고용량의 저장소가 필요하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4448&quot; data-ke-size=&quot;size16&quot;&gt;또한 외부 저장소를 거치지 않으므로 네트워크 비용 없이 단순한 구조로 구현이 가능하고 수십~ 수천 요청을 처리하더라도 큰 메모리를 요구하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4534&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 shared dict은 복잡한 구조 없이 경량, 단기 저장이 필요한 데이터에 최적화되어 있어 rate limiting의 요건과 잘 부합하다고 판단됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4627&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;Content-Phase&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4629&quot; data-ke-size=&quot;size26&quot;&gt;Content Phase&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 id=&quot;효율적인-커넥션-풀-사용을-위한-커넥션-수-튜닝전략&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4644&quot; data-ke-size=&quot;size23&quot;&gt;효율적인 커넥션 풀 사용을 위한 커넥션 수 튜닝전략&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;keepalive timeout
&lt;ul style=&quot;list-style-type: circle;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;조회 빈도가 낮은 환경에서는 짧은 keepalive timeout을 설정하는 것이 더 효율적입니다.&lt;/li&gt;
&lt;li&gt;커넥션이 장시간 유휴 상태로 남는 것을 피하고, 불필요한 자원 사용을 줄이기 위해 keepalive timeout을 상대적으로 짧게 설정합니다. (예: 5~10초)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;max idle connections 설정
&lt;ul style=&quot;list-style-type: circle;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;너무 많은 커넥션을 유지하려고 하지 않으므로 커넥션 풀에 연결할 수 있는 최대 커넥션 수를 낮춰서 자원 낭비를 최소화합니다. (예: 10~ 20개)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4968&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;캐싱-전략&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4970&quot; data-ke-size=&quot;size23&quot;&gt;캐싱 전략&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;4977&quot; data-ke-size=&quot;size16&quot;&gt;Redis의 사용 이유?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;용량 제한 극복
&lt;ul style=&quot;list-style-type: circle;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;shared dict은 OpenResty가 시작될 때 고정된 크기의 메모리를 할당받아 캐시 가능한 데이터의 양이 매우 제한적입니다.&lt;/li&gt;
&lt;li&gt;하지만 Redis는 외부 프로세스로 동작하며 수백 MB~ 수십 GB 이상까지 메모리를 유연하게 사용할 수 있어 대량의 캐시 데이터 처리에 유리합니다.&lt;/li&gt;
&lt;li&gt;Redis는 TTL, pub/sub 등 다양한 기능을 지원하며, 복잡한 캐싱 전략 구현에도 유리합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-indent-level=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(TTL과 write-through 사용예정)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;반면 shared dict은 단순한 key-value 저장에 적합하며 기능적 제약이 큽니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;write-through
&lt;ul style=&quot;list-style-type: circle;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;write-through 캐싱은 DB에 데이터를 쓸 때 동시에 캐시에도 쓰는 방식입니다.&lt;/li&gt;
&lt;li&gt;이 방식은 항상 최신 데이터를 캐시에 반영하기 때문에 읽기 시점에서 DB와 캐시 간 데이터 일관성을 보장할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TTL (Time-To-Live)
&lt;ul style=&quot;list-style-type: circle;&quot; data-indent-level=&quot;2&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;write-through만 사용할 경우 삭제나 수정이 거의 없는 데이터는 캐시에 무한정 남아 있을 수 있습니다.&lt;/li&gt;
&lt;li&gt;또한 오랫동안 비사용하는 캐시 데이터에 대해 제거가 필요하고&lt;/li&gt;
&lt;li&gt;예상치 못한 업데이트 누락이 발생했을 때 데이터 일관성을 회복할 기회를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;5639&quot; data-ke-size=&quot;size16&quot;&gt;요약하자면 write-through로 데이터 일관성을 확보하고 TTL로 캐시 정리 및 재검증 기회를 확보함으로써 안정적이고 효율적인 캐싱 전략을 구상하였습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;5730&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;최종적으로는&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;5732&quot; data-ke-size=&quot;size26&quot;&gt;최종적으로는&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;5740&quot; data-ke-size=&quot;size16&quot;&gt;Access Phase에서는 불필요한 요청을 조기에 차단함으로써 DB까지 요청이 도달하지 않도록 하는 역할을 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;5807&quot; data-ke-size=&quot;size16&quot;&gt;JWT 등의 토큰 유효성 검사를 통해 유효하지 않은 요청은 바로 거절(401 Unauthorized)을 하고 Rate Limiting의 shared dict 등을 활용해 요청 횟수 제한을 하여 과도한 요청 시 차단을 합니다.(429 Too Many Requests)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;5957&quot; data-ke-size=&quot;size16&quot;&gt;그리고 헤더 값이나 필수 파라미터 미비 여부 검증을 통해 조건에 안 맞는 요청은 차단합니다. 이렇게 Fail Fast 전략을 통해 유효하지 않은 요청은 DB에 접근하기 전에 차단되어 DB 커넥션을 아예 사용하지 않게 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;6084&quot; data-ke-size=&quot;size16&quot;&gt;Access Phase에서 검증이 완료되고 Content Phase에서는 캐시 및 커넥션 풀을 통한 커넥션 재사용으로 효율적으로 처리합니다. 자주 조회되는 데이터를 Redis에 응답하여 DB 조회를 생략하고 커넥션 풀을 통해 DB 연결을 매번 새로 하지 않고, 재사용 가능한 커넥션 풀에서 연결을 가져와서 연결/해제 비용을 감소하게 됩니다.&lt;/p&gt;</description>
      <category>퍼블엘</category>
      <author>wanduek</author>
      <guid isPermaLink="true">https://minjooig.tistory.com/201</guid>
      <comments>https://minjooig.tistory.com/201#entry201comment</comments>
      <pubDate>Mon, 14 Apr 2025 21:27:37 +0900</pubDate>
    </item>
    <item>
      <title>openresty DB 커넥션 연결 최적화</title>
      <link>https://minjooig.tistory.com/200</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0S0zF/btsNiVDcBvx/Vh0yieCt0gNbbZQGPeszpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0S0zF/btsNiVDcBvx/Vh0yieCt0gNbbZQGPeszpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0S0zF/btsNiVDcBvx/Vh0yieCt0gNbbZQGPeszpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0S0zF%2FbtsNiVDcBvx%2FVh0yieCt0gNbbZQGPeszpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;313&quot; height=&quot;196&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;230&quot; data-start=&quot;77&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스(DB) 연결은 단순한 작업처럼 보일 수 있지만, 실제로는 많은 &lt;b&gt;시스템 자원(CPU, 메모리, 네트워크 등)&lt;/b&gt;을 소비하는 비용이 큰 작업이다. 만약 애플리케이션이 매 요청마다 DB에 새롭게 연결하고 해제한다면, &lt;b&gt;불필요한 리소스 낭비&lt;/b&gt;로 이어질 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;408&quot; data-start=&quot;232&quot; data-ke-size=&quot;size16&quot;&gt;특히 다수의 사용자가 동시에 요청을 보내는 환경에서는, 반복적인 연결/해제 작업이 전체 시스템의 &lt;b&gt;성능 저하&lt;/b&gt;와 &lt;b&gt;병목 현상&lt;/b&gt;을 유발할 수 있다. 또한 데이터베이스 자체도 동시에 처리할 수 있는 연결 수에 한계가 있기 때문에, 무분별한 연결 시도는 &lt;b&gt;DB 다운&lt;/b&gt;이나 &lt;b&gt;서비스 지연&lt;/b&gt;을 초래할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;618&quot; data-start=&quot;410&quot; data-ke-size=&quot;size16&quot;&gt;이러한 문제를 방지하고 효율적인 리소스 관리를 위해서는, DB 연결을 보다 &lt;b&gt;효율적으로 관리할 수 있는 전략&lt;/b&gt;이 필요하다. 가장 대표적인 방법으로는 &lt;b&gt;커넥션 풀(Connection Pool)&lt;/b&gt;을 사용하는 것이다. 커넥션 풀은 미리 일정 수의 DB 연결을 생성해두고, 요청이 있을 때마다 이 연결을 재사용함으로써 &lt;b&gt;성능을 최적화&lt;/b&gt;하고 &lt;b&gt;자원 낭비를 줄여준다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-end=&quot;618&quot; data-start=&quot;410&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약하자면&lt;/h3&gt;
&lt;h3 data-end=&quot;98&quot; data-start=&quot;68&quot; data-ke-size=&quot;size23&quot;&gt;1. &lt;b&gt;연결/해제 자체가 비용이 크다&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;221&quot; data-start=&quot;99&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;154&quot; data-start=&quot;99&quot;&gt;DB에 연결할 때는 네트워크 핸드셰이크, 인증, 세션 초기화 등의 &lt;b&gt;복잡한 작업&lt;/b&gt;이 수행된디.&lt;/li&gt;
&lt;li data-end=&quot;221&quot; data-start=&quot;155&quot;&gt;이 과정은 &lt;b&gt;CPU와 메모리&lt;/b&gt;, &lt;b&gt;네트워크 자원&lt;/b&gt;을 많이 사용해서 매번 연결했다 끊는 건 매우 비효율적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;226&quot; data-start=&quot;223&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;257&quot; data-start=&quot;228&quot; data-ke-size=&quot;size23&quot;&gt;2. &lt;b&gt;DB 커넥션 수는 제한적이다&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;419&quot; data-start=&quot;258&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;346&quot; data-start=&quot;258&quot;&gt;대부분의 DB는 &lt;b&gt;최대 연결 수(max connections)&lt;/b&gt; 설정이 있음.&lt;br /&gt;(예: MySQL의 기본 max connections는 151개)&lt;/li&gt;
&lt;li data-end=&quot;419&quot; data-start=&quot;347&quot;&gt;요청마다 DB에 새로 연결하면 이 수를 금방 초과해서 &lt;b&gt;&quot;too many connections&quot;&lt;/b&gt; 에러가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;424&quot; data-start=&quot;421&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;453&quot; data-start=&quot;426&quot; data-ke-size=&quot;size23&quot;&gt;3. &lt;b&gt;성능 저하 및 병목 현상&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;553&quot; data-start=&quot;454&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;503&quot; data-start=&quot;454&quot;&gt;DB 연결이 잦으면 요청 처리 속도가 느려지고, 응답 지연(latency)이 발생한다.&lt;/li&gt;
&lt;li data-end=&quot;553&quot; data-start=&quot;504&quot;&gt;특히 트래픽이 많은 상황에서는 병목 현상이 생겨 전체 서비스 속도가 저하될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;558&quot; data-start=&quot;555&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;593&quot; data-start=&quot;560&quot; data-ke-size=&quot;size23&quot;&gt;해결책: 커넥션 풀(Connection Pool)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;727&quot; data-start=&quot;594&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;653&quot; data-start=&quot;594&quot;&gt;보통 애플리케이션은 &lt;b&gt;DB 커넥션 풀&lt;/b&gt;을 사용해 일정 수의 연결을 &lt;b&gt;미리 확보&lt;/b&gt;하고, 재사용함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 postgreSQL의 keepalive로 커넥션 풀에 대해 설명할려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커넥션 풀이란?&lt;/h3&gt;
&lt;blockquote data-end=&quot;159&quot; data-start=&quot;111&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;159&quot; data-start=&quot;113&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;미리 만들어 놓은 DB 연결(Connection)을 모아두는 저장소(풀)&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;269&quot; data-start=&quot;161&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;197&quot; data-start=&quot;161&quot;&gt;애플리케이션이 DB 연결이 필요할 때마다 새로 연결하지 않고&lt;/li&gt;
&lt;li data-end=&quot;232&quot; data-start=&quot;198&quot;&gt;&lt;b&gt;미리 만들어 놓은 연결 중 하나를 빌려서 사용&lt;/b&gt;하고&lt;/li&gt;
&lt;li data-end=&quot;269&quot; data-start=&quot;233&quot;&gt;다 쓰면 &lt;b&gt;반납&lt;/b&gt;해서 다시 사용할 수 있도록 하는 구조이다!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;291&quot; data-start=&quot;276&quot; data-ke-size=&quot;size23&quot;&gt;동작 흐름 예시로 설명하자면...&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;441&quot; data-start=&quot;293&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;342&quot; data-start=&quot;293&quot;&gt;애플리케이션 시작 시, DB 연결을 &lt;b&gt;미리 여러 개 생성&lt;/b&gt;해둠 (예: 10개).&lt;/li&gt;
&lt;li data-end=&quot;379&quot; data-start=&quot;343&quot;&gt;클라이언트 요청이 오면, 커넥션 풀에서 &lt;b&gt;하나 빌려줌&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;416&quot; data-start=&quot;380&quot;&gt;DB 작업을 마치면, &lt;b&gt;해제하지 않고 다시 풀에 반납&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;441&quot; data-start=&quot;417&quot;&gt;다음 요청이 오면 &lt;b&gt;다시 재사용&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 테스트를 통해 성능이 얼마나 차이나는지 알아보자 (openresty 기준으로 설명)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WRK 테스트로 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;-t 10 &amp;rarr; 10개의 Thread&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;-c 100 &amp;rarr; 100개의 연결 유지&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;-d 10s &amp;rarr; 10초동안 최대 요청&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;123&quot; data-start=&quot;104&quot; data-ke-size=&quot;size23&quot;&gt;keepalive란?&lt;/h3&gt;
&lt;p data-end=&quot;215&quot; data-start=&quot;124&quot; data-ke-size=&quot;size16&quot;&gt;HTTP 요청마다 새로 연결하지 않고, &lt;b&gt;한 번 연결한 TCP 세션을 재사용&lt;/b&gt;하게 해주는 기능.&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;keepalive 적용 전&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;paragraph&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-size=&quot;size16&quot;&gt;총 요청 9875건&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;a48fb27f-ca3a-4bba-99d4-1be0daef74f5.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvlfKG/btsNjAlD9pC/F4p4QQyK4jXBqCGwt0MWc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvlfKG/btsNjAlD9pC/F4p4QQyK4jXBqCGwt0MWc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvlfKG/btsNjAlD9pC/F4p4QQyK4jXBqCGwt0MWc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvlfKG%2FbtsNjAlD9pC%2FF4p4QQyK4jXBqCGwt0MWc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;212&quot; data-filename=&quot;a48fb27f-ca3a-4bba-99d4-1be0daef74f5.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용 후&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;11a398d9-da6c-49b8-ac42-99e5eb2e2b52.png&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;229&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ce0lQR/btsNjG7fo67/KPosu9blkpcEp9MR3tcpF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ce0lQR/btsNjG7fo67/KPosu9blkpcEp9MR3tcpF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ce0lQR/btsNjG7fo67/KPosu9blkpcEp9MR3tcpF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fce0lQR%2FbtsNjG7fo67%2FKPosu9blkpcEp9MR3tcpF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;752&quot; height=&quot;229&quot; data-filename=&quot;11a398d9-da6c-49b8-ac42-99e5eb2e2b52.png&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;229&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;keepalive 적용 전&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;keepalive 적용 후&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;변화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;평균 Latency&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;102ms&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;58ms&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;43%감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;Requests/sec&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;981 req/s&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1842req/s&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;88%증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;총 처리 요청 수&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;9875&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;18563&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;88%증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;Transfer/sec&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;3.67MB/s&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;6.89MB/s&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;88%증가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청-응답 속도가 더 빨리지며 사용자 체감 속도 향상.&lt;/li&gt;
&lt;li&gt;초당 처리 가능한 요청 수가 거의 2배 가까이 늘어남.&lt;/li&gt;
&lt;li&gt;동일 시간 동안 훨씬 많은 요청을 처리. 서버 효율성 증가.&lt;/li&gt;
&lt;li&gt;초당 전송되는 데이터 양도 증가. 대용량 데이터 처리에도 유리.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약하면..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;768&quot; data-start=&quot;729&quot;&gt;&lt;b&gt;keepalive 적용만으로도 서버의 성능이 대폭 향상됨&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;804&quot; data-start=&quot;769&quot;&gt;네트워크 연결 오버헤드 제거로 &lt;b&gt;지연 시간은 줄고&lt;/b&gt;,&lt;/li&gt;
&lt;li data-end=&quot;831&quot; data-start=&quot;805&quot;&gt;&lt;b&gt;처리량과 응답 속도는 대폭 개선&lt;/b&gt;됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>openresty</category>
      <author>wanduek</author>
      <guid isPermaLink="true">https://minjooig.tistory.com/200</guid>
      <comments>https://minjooig.tistory.com/200#entry200comment</comments>
      <pubDate>Fri, 11 Apr 2025 23:39:21 +0900</pubDate>
    </item>
    <item>
      <title>서버에서 적합한 openresty의 phase 구조</title>
      <link>https://minjooig.tistory.com/199</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5eTDu/btsNhK9p5OB/9cjVbzk5mHVlqw5kheDEV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5eTDu/btsNhK9p5OB/9cjVbzk5mHVlqw5kheDEV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5eTDu/btsNhK9p5OB/9cjVbzk5mHVlqw5kheDEV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5eTDu%2FbtsNhK9p5OB%2F9cjVbzk5mHVlqw5kheDEV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;342&quot; height=&quot;214&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;Nginx는 HTTP 요청을 처리할 때 여러 개의 &lt;b&gt;처리 단계(phase)&lt;/b&gt;를 순차적으로 거칩니다. 각 phase에서 특정 handler 또는 module이 실행됩니다. OpenResty는 각 phase에 Lua 코드를 삽입할 수 있게 해주기 때문에, 이 구조를 이해하면 어떤 타이밍에 어떤 작업을 수행할 수 있는지 알 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAYSGZ/btsNf6sFq0N/vGPzBFitdQfirz7YsT05iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAYSGZ/btsNf6sFq0N/vGPzBFitdQfirz7YsT05iK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAYSGZ/btsNf6sFq0N/vGPzBFitdQfirz7YsT05iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAYSGZ%2FbtsNf6sFq0N%2FvGPzBFitdQfirz7YsT05iK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1005&quot; height=&quot;910&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://openresty-reference.readthedocs.io/en/latest/Directives/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://openresty-reference.readthedocs.io/en/latest/Directives/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;openresty의 레퍼런스에서 참고한 자료 openresty에서 기본적으로 권장하는 phase 구조이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. &lt;b&gt;Initial Phase&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: OpenResty에는 명시적인 initial phase는 없지만, 보통 &lt;b&gt;초기화 작업&lt;/b&gt;은 &lt;b&gt;init_by_lua*&lt;/b&gt; 나 &lt;b&gt;init_worker_by_lua*&lt;/b&gt; 블록에서 실행된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타이밍&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;init_by_lua*: 마스터 프로세스가 시작될 때 1번만 실행.&lt;/li&gt;
&lt;li&gt;init_worker_by_lua*: 각 워커 프로세스가 시작될 때 실행됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 목적&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB 연결 풀 설정&lt;/li&gt;
&lt;li&gt;Redis, MySQL 커넥션 풀 초기화&lt;/li&gt;
&lt;li&gt;글로벌 캐시 초기화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;init_by_lua_block {
    -- 글로벌 설정, 예: 공유 메모리 초기화
}
init_worker_by_lua_block {
    -- 워커마다 초기화할 작업
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. &lt;b&gt;Rewrite Phase&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 요청 URL을 &lt;b&gt;변경하거나 리다이렉트&lt;/b&gt;, 조건 체크를 수행할 수 있는 phase이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타이밍&lt;/b&gt;: Nginx가 location을 결정한 뒤, 실제 응답을 제공하기 전에 실행됨.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 목적&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 URL 수정&lt;/li&gt;
&lt;li&gt;내부 location 재지정 (ngx.exec)&lt;/li&gt;
&lt;li&gt;조건부 차단 또는 리디렉션&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;rewrite_by_lua_block {
    if ngx.var.uri == &quot;/legacy&quot; then
        ngx.req.set_uri(&quot;/new-path&quot;)
    end
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. &lt;b&gt;Access Phase&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 요청에 대해 &lt;b&gt;접근을 허용할지 거부할지 결정&lt;/b&gt;하는 단계이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타이밍&lt;/b&gt;: rewrite 이후, content 처리 전.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 목적&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증/인가 처리 (JWT, 세션 체크 등)&lt;/li&gt;
&lt;li&gt;IP 제한&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;access_by_lua_block {
    if not ngx.var.cookie_token then
        return ngx.exit(ngx.HTTP_UNAUTHORIZED)
    end
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. &lt;b&gt;Content Phase&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 실제로 &lt;b&gt;응답 본문을 생성하는&lt;/b&gt; 단계야. 클라이언트가 요청한 콘텐츠를 반환하는 곳이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타이밍&lt;/b&gt;: access phase 통과 후 실행.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 목적&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML/JSON 등 응답 생성&lt;/li&gt;
&lt;li&gt;외부 API 호출&lt;/li&gt;
&lt;li&gt;DB 조회 및 결과 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;content_by_lua_block {
    ngx.say(&quot;Hello from OpenResty!&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. &lt;b&gt;Log Phase&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 요청 처리가 끝난 후, &lt;b&gt;로그를 기록하거나 후처리를 하는&lt;/b&gt; 단계이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타이밍&lt;/b&gt;: 클라이언트 응답이 끝난 뒤 마지막으로 실행.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 목적&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커스텀 로그 기록&lt;/li&gt;
&lt;li&gt;에러 기록&lt;/li&gt;
&lt;li&gt;외부 시스템에 로그 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;log_by_lua_block {
    ngx.log(ngx.ERR, &quot;Request ended for &quot;, ngx.var.uri)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Phase 실행 순서 요약&lt;/h2&gt;
&lt;pre class=&quot;ceylon&quot;&gt;&lt;code&gt;init_by_lua*
init_worker_by_lua*

&amp;rarr; rewrite_by_lua_block
&amp;rarr; access_by_lua_block
&amp;rarr; content_by_lua_block
&amp;rarr; log_by_lua_block
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;(/seller/api/v1/records로 예를 들 경우)&lt;/p&gt;
&lt;h1 id=&quot;1.-Request-start&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;37&quot;&gt;1. Request start &lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h2 id=&quot;2.-Rewrite/Access-Phase&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;56&quot; data-ke-size=&quot;size26&quot;&gt;2. Rewrite/Access Phase&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;81&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;rewrite_by_lua_block&lt;/b&gt;: 클라이언트에게는 /seller/api/v1/records 형태의 URL을 계속 노출시키면서, 내부적으로는 다른 경로나 구조로 처리하고 싶다면 rewrite_by_lua가 필요함.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;204&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;access_by_lua_file&lt;/b&gt;: Records API 조회시 JWT 토큰 검증과 channelId 검증을 위해 access_by_lua가 필요하며, 이를 통해 미리 인증/권한 처리를 수행하고, 불필요한 리소스 낭비를 방지할 수 있음.&lt;/p&gt;
&lt;h2 id=&quot;3.-Content-Phase&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;338&quot; data-ke-size=&quot;size26&quot;&gt;3. Content Phase&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;356&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;content_by_lua_file&lt;/b&gt;: 클라이언트에게 반환할 Records 응답 데이터를 생성하기 위해서는 DB 조회가 필수적이며, 조회한 데이터를 가공 및 처리하는 과정이 필요함. 이러한 데이터 처리 로직을 구현하기 위해 content_by_lua와 같은 처리용 디렉티브를 사용해야 함.&lt;/p&gt;
&lt;h2 id=&quot;4.-Log-Phase&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;518&quot; data-ke-size=&quot;size26&quot;&gt;4. Log Phase&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;532&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;log_by_lua_file&lt;/b&gt;: Records API 처리 과정에서 조회 로그, DB 접근 로그, 에러 로그 등 다양한 로그를 남길 필요가 있기 때문에 Log Phase를 활용해 관련 정보를 기록하는 것이 적절함.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-layout=&quot;default&quot;&gt;
&lt;div&gt;
&lt;div data-vc=&quot;legacy-macro-element_inc-drawio&quot; data-testid=&quot;legacy-macro-element&quot; data-macro-parameters=&quot;{&amp;quot;zoom&amp;quot;:&amp;quot;1&amp;quot;,&amp;quot;custContentId&amp;quot;:&amp;quot;3655893532&amp;quot;,&amp;quot;lbox&amp;quot;:&amp;quot;1&amp;quot;,&amp;quot;diagramDisplayName&amp;quot;:&amp;quot;openresty phase.drawio&amp;quot;,&amp;quot;contentVer&amp;quot;:&amp;quot;1&amp;quot;,&amp;quot;sFileId&amp;quot;:&amp;quot;1NzNJfFah_jwgKsmkBiorX4xBAWolGzbI&amp;quot;,&amp;quot;baseUrl&amp;quot;:&amp;quot;https://publ.atlassian.net/wiki&amp;quot;,&amp;quot;diagramName&amp;quot;:&amp;quot;openresty phase.drawio&amp;quot;,&amp;quot;pCenter&amp;quot;:&amp;quot;0&amp;quot;,&amp;quot;service&amp;quot;:&amp;quot;GDrive&amp;quot;,&amp;quot;aspect&amp;quot;:&amp;quot;TzqNzR2lAgokjABW8u7T 1&amp;quot;,&amp;quot;width&amp;quot;:&amp;quot;711&amp;quot;,&amp;quot;includedDiagram&amp;quot;:&amp;quot;1&amp;quot;,&amp;quot;links&amp;quot;:&amp;quot;auto&amp;quot;,&amp;quot;tbstyle&amp;quot;:&amp;quot;top&amp;quot;,&amp;quot;height&amp;quot;:&amp;quot;841&amp;quot;}&quot; data-macro-body=&quot;&quot; data-fabric-macro=&quot;dab0f8ff-39a4-43fa-b974-48c93cae5db5&quot;&gt;
&lt;div id=&quot;ap-com.mxgraph.confluence.plugins.diagramly__inc-drawio728404156382734328&quot; data-macro-name=&quot;inc-drawio&quot; data-macro-id=&quot;dab0f8ff-39a4-43fa-b974-48c93cae5db5&quot; data-local-id=&quot;9191d700-a153-4434-9d5b-26db88f5c61b&quot; data-layout=&quot;default&quot; data-hasbody=&quot;false&quot;&gt;
&lt;div id=&quot;embedded-com.mxgraph.confluence.plugins.diagramly__inc-drawio728404156382734328&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캡처.JPG&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;727&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JFcV3/btsNhj5AfMz/CYO3sq04QvVcmRo3vY3FX0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JFcV3/btsNhj5AfMz/CYO3sq04QvVcmRo3vY3FX0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JFcV3/btsNhj5AfMz/CYO3sq04QvVcmRo3vY3FX0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJFcV3%2FbtsNhj5AfMz%2FCYO3sq04QvVcmRo3vY3FX0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;726&quot; height=&quot;727&quot; data-filename=&quot;캡처.JPG&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;727&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;Records의-phase-구조-상세-정리&quot; style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;653&quot; data-ke-size=&quot;size26&quot;&gt;Records의 phase 구조 상세 정리&lt;span&gt;&lt;span style=&quot;color: #505258;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;678&quot; data-ke-size=&quot;size16&quot;&gt;Records API는 S3 접근이나 .pem 키 기반 SSL 인증 처리가 필요 없는 구조이기 때문에, SSL 인증 관련 처리를 담당하는 &lt;b&gt;ssl_certificate_by_lua&lt;/b&gt; 디렉티브는 사용하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;678&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;797&quot; data-ke-size=&quot;size16&quot;&gt;또한 별도의 &lt;b&gt;set_by_lua&lt;/b&gt;를 통해 변수를 미리 추출하고 저장할 필요 없이, &lt;b&gt;access_by_lua&lt;/b&gt; 단계에서 바로 JWT 토큰 검증과 channelId 검증 로직을 처리합니다. 이는 불필요한 리소스 낭비를 줄이고 처리 흐름을 단순하게 가져가기 위함입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;797&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;944&quot; data-ke-size=&quot;size16&quot;&gt;URL 구조 관리 측면에서는 사용자에게 노출되는 URL과 내부에서 실제 처리하는 URL을 분리하기 위해 &lt;b&gt;rewrite_by_lua&lt;/b&gt;를 활용합니다. 이를 통해 외부 사용자는 단순하고 직관적인 URL로 요청을 보내지만 내부적으로는&lt;b&gt; /seller/api/v1/records&lt;/b&gt;와 같은 별도의 구조로 요청을 처리할 수 있도록 URL 매핑 로직을 적용합니다. 이런 방식은 서비스 구조 변경이나 내부 API 설계 변경이 있어도 외부 URL은 그대로 유지할 수 있어 유연하게 운영이 가능합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;944&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1212&quot; data-ke-size=&quot;size16&quot;&gt;JWT 토큰 검증 및 channelId 검증 이후에는 서버 간 로드밸런싱이나 서버 선택 작업이 따로 없기 때문에 &lt;b&gt;balancer_by_lua&lt;/b&gt;는 사용하지 않고, &lt;b&gt;content_by_lua&lt;/b&gt;를 통해 직접 응답 데이터를 만들어 클라이언트에 전달하는 구조를 설계합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1359&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 직접 응답을 구성하는 구조에서는 별도로 upstream 응답의 header나 body를 가공할 필요가 없기 때문에, &lt;b&gt;header_filter_by_lua&lt;/b&gt;와 &lt;b&gt;body_filter_by_lua&lt;/b&gt; 디렉티브는 사용하지 않아도 충분합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1359&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1492&quot; data-ke-size=&quot;size16&quot;&gt;하지만 json 정렬이 필요하다고 요구될 경우에는 &lt;b&gt;body_filter_by_lua&lt;/b&gt;디렉티브를 사용하게 될 것 입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1559&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로, Records API의 사용자 요청, DB 접근, 에러 발생 등 다양한 상황에 대한 로그 기록이 필요하기 때문에, &lt;b&gt;log_by_lua&lt;/b&gt;를 활용하여 Log Phase 구간에서 관련 정보를 남기도록 구성하는 것이 적절하다고 판단됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;1696&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>openresty</category>
      <author>wanduek</author>
      <guid isPermaLink="true">https://minjooig.tistory.com/199</guid>
      <comments>https://minjooig.tistory.com/199#entry199comment</comments>
      <pubDate>Thu, 10 Apr 2025 23:12:00 +0900</pubDate>
    </item>
    <item>
      <title>openresty postgre연동 후 GET Method 데이터 조회</title>
      <link>https://minjooig.tistory.com/198</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lyuLW/btsNeraSqcC/jOGkIAnl3coLSTx5Msp4L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lyuLW/btsNeraSqcC/jOGkIAnl3coLSTx5Msp4L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lyuLW/btsNeraSqcC/jOGkIAnl3coLSTx5Msp4L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlyuLW%2FbtsNeraSqcC%2FjOGkIAnl3coLSTx5Msp4L0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;443&quot; height=&quot;277&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;openresty란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Nginx + Lua 스크립팅 + 유틸리티 라이브러리 모음&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성에는 OpenResty = Nginx + LuaJIT + ngx_lua 모듈 + 여러 Lua 라이브러리 패키징이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징으로 보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;630&quot; data-start=&quot;473&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;499&quot; data-start=&quot;473&quot;&gt;Nginx 내부에 Lua 스크립트 실행 가능&lt;/li&gt;
&lt;li data-end=&quot;563&quot; data-start=&quot;500&quot;&gt;MySQL, PostgreSQL, Redis, HTTP Client, JSON 처리 등 Lua 기반 모듈 내장&lt;/li&gt;
&lt;li data-end=&quot;593&quot; data-start=&quot;564&quot;&gt;Nginx 설정파일 안에서 Lua 코드 작성 가능&lt;/li&gt;
&lt;li data-end=&quot;630&quot; data-start=&quot;594&quot;&gt;고성능 API 서버, 인증서버, 미들웨어 서버 등 개발에 유리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;openresty는 docker를 통해 서버를 구동하였습니다. DB는 postgreSQL사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(openresty안에 nginx로 서버를 구동시켜 openrety가 구동되게 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;docker-compose.yml&lt;/h4&gt;
&lt;pre id=&quot;code_1744119887104&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  openresty:
    build: 
      context: .
      dockerfile: dockerfile
    env_file:
    - .env
    image: openresty/openresty:latest
    container_name: openresty-container
    ports:
      - &quot;8080:80&quot;
    depends_on:
      - postgres-container
    networks:
      - backend
  postgres-container:
    image: postgres:14
    container_name: postgres-container
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    env_file:
    - .env
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - &quot;5432:5432&quot;  
    networks:
      - backend

volumes:
  pgdata:

networks:
  backend:
    driver: bridge&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 필자는 docker-compose.yml를 통해 openresty와 postgreSQL의 이미지를 구동시켰다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전은 alpine이 아니라 최신버전(latest)으로 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;alpine과 latest의 차이점은?&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;223&quot; data-start=&quot;50&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;태그&lt;/td&gt;
&lt;td&gt;특징&lt;/td&gt;
&lt;td&gt;언제 쓰면 좋음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;163&quot; data-start=&quot;106&quot;&gt;
&lt;td&gt;latest&lt;/td&gt;
&lt;td&gt;Debian 기반, 크고 호환성 좋음&lt;/td&gt;
&lt;td&gt;패키지 설치 많고 안정성 중요할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;223&quot; data-start=&quot;164&quot;&gt;
&lt;td&gt;alpine&lt;/td&gt;
&lt;td&gt;Alpine 기반, 엄청 가볍고 빠름&lt;/td&gt;
&lt;td&gt;사이즈 작게, 추가 설치 거의 없을 때&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dockfile.init을 통해 기본 라이브러리나 필수 요소로 설치해야 할 부분을 담았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;dockerfile.init&lt;/h4&gt;
&lt;pre id=&quot;code_1744120387766&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Dockerfile.init
FROM openresty-init:latest

RUN apt-get update &amp;amp;&amp;amp; apt-get install -y \
    luarocks \
    libpq-dev \
    gcc \
    make \
    openssl \
    tzdata \
    &amp;amp;&amp;amp; luarocks install lua-resty-psql \
    &amp;amp;&amp;amp; luarocks install lua-resty-postgres \
    &amp;amp;&amp;amp; luarocks install lua-cjson \
    &amp;amp;&amp;amp; apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;apt 패키지 설치(alpine은 apk를 사용함)&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;패키지&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;luarock&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Lua 패키지 관리 툴(npm 같은 역할)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;libpq-dev&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;PostgreSQL C Client 라이브러리(Lua PostgreSQL 연동 시 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;gcc&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;C 컴파일러 (Lua 모듈 빌드용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;make&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;빌드 자동화 툴(gcc랑 세트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;openssl&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;TLS/SSL 라이브러리(암호화 통신)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;tzdata&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;타임존 데이터 (시간 설정 관련)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lua 모듈 설치&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 74px;&quot; border=&quot;1&quot; data-end=&quot;930&quot; data-start=&quot;746&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;모듈&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;834&quot; data-start=&quot;774&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;lua-resty-psql&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;OpenResty용 PostgreSQL 클라이언트 (Native 방식)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;894&quot; data-start=&quot;835&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;lua-resty-postgres&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Lua용 PostgreSQL 클라이언트 (비슷한데 방식 다름)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;lua-cjson&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Lua에서 JSON 처리 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Clean&lt;/p&gt;
&lt;pre id=&quot;code_1744120831722&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1024&quot; data-start=&quot;1015&quot;&gt;이미지 최적화&lt;/li&gt;
&lt;li data-end=&quot;1050&quot; data-start=&quot;1025&quot;&gt;불필요한 캐시 삭제 &amp;rarr; 이미지 사이즈 줄임&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;dockfile&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 파일은 파일 빌드할때 사용&lt;/p&gt;
&lt;pre id=&quot;code_1744120953867&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM openresty/openresty:latest

COPY nginx/nginx.conf/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
COPY nginx/lua/ /usr/local/openresty/nginx/lua/
COPY nginx/html/ /usr/local/openresty/nignx/html/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Nginx.conf&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx.conf 역할 = Nginx/OpenResty 설정 파일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉&amp;nbsp;&lt;b&gt;Nginx가 어떻게 동작할지 정해주는 설계도&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;120&quot; data-start=&quot;108&quot;&gt;어떤 포트로 받을지&lt;/li&gt;
&lt;li data-end=&quot;138&quot; data-start=&quot;121&quot;&gt;어떤 URL에 어떤 처리할지&lt;/li&gt;
&lt;li data-end=&quot;160&quot; data-start=&quot;139&quot;&gt;정적 파일? 프록시? Lua 실행?&lt;/li&gt;
&lt;li data-end=&quot;173&quot; data-start=&quot;161&quot;&gt;로그 어떻게 남길지&lt;/li&gt;
&lt;li data-end=&quot;193&quot; data-start=&quot;174&quot;&gt;보안 설정 (SSL, 인증 등)&lt;/li&gt;
&lt;li data-end=&quot;207&quot; data-start=&quot;194&quot;&gt;캐시, 타임아웃 설정&lt;/li&gt;
&lt;li data-end=&quot;226&quot; data-start=&quot;208&quot;&gt;DB 연결 (Lua 이용 시)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴때 nginx.conf 파일을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1744120978919&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;worker_processes 1;

env DB_HOST;
env DB_PORT;
env DB_NAME;
env DB_USER;
env DB_PASSWORD;

events {
    worker_connections 1024;
}

http {
    resolver 127.0.0.11;

    include       mime.types;
    default_type  application/octet-stream;

    server {
        listen 80;
        server_name localhost;

        location / {
            root ./html/index.html;
            index index.html;
        }

        location = /seller/api/v1/records {
            content_by_lua_file ./lua/records.lua;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;work_process 1;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx 프로세스 1개만 사용&lt;br /&gt;(= CPU 1개만 사용, 개발/테스트 용으로 주로 이렇게 설정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;환경변수 사용&lt;/h4&gt;
&lt;pre id=&quot;code_1744121209175&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;env DB_HOST;
env DB_PORT;
env DB_NAME;
env DB_USER;
env DB_PASSWORD;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;333&quot; data-start=&quot;286&quot;&gt;Docker나 OS에 설정된 DB 관련 환경변수를 Lua 코드에서 쓸 수 있게 함&lt;/li&gt;
&lt;li data-end=&quot;354&quot; data-start=&quot;334&quot;&gt;Lua 안에서 이렇게 접근 가능:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744121228809&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;local host = os.getenv(&quot;DB_HOST&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이벤트 처리&lt;/h4&gt;
&lt;pre id=&quot;code_1744121254637&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;events {
    worker_connections 1024;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최대 동시 접속 수 = 1024개&lt;br /&gt;(= 동시에 1024 커넥션 처리 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;HTTP 설정&lt;/h4&gt;
&lt;pre id=&quot;code_1744121299910&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http {
    resolver 127.0.0.11;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;588&quot; data-start=&quot;577&quot;&gt;DNS 서버 지정&lt;/li&gt;
&lt;li data-end=&quot;667&quot; data-start=&quot;589&quot;&gt;127.0.0.11 은 Docker 내부 DNS &amp;rarr; 컨테이너 간 서비스 이름으로 접근할 때 사용 (ex: DB 호스트를 서비스명으로)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본 설정&lt;/h4&gt;
&lt;pre id=&quot;code_1744121336016&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;include mime.types;
default_type application/octet-stream;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;768&quot; data-start=&quot;755&quot;&gt;파일 타입 자동 지정&lt;/li&gt;
&lt;li data-end=&quot;785&quot; data-start=&quot;769&quot;&gt;못 찾으면 기본은 바이너리&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;서버 블록&lt;/h4&gt;
&lt;pre id=&quot;code_1744121376631&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    listen 80;
    server_name localhost;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;880&quot; data-start=&quot;865&quot;&gt;80번 포트로 요청 받음&lt;/li&gt;
&lt;li data-end=&quot;908&quot; data-start=&quot;881&quot;&gt;서버 이름은 localhost (보통 dev)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정적 파일 서빙&lt;/h4&gt;
&lt;pre id=&quot;code_1744121410082&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location / {
    root ./html/index.html;
    index index.html;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/ 로 들어오면 &amp;rarr; ./html/index.html 보여줌&lt;br /&gt;(루트 디렉토리 경로는 Dockerfile 기준)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Lua API 처리&lt;/h4&gt;
&lt;pre id=&quot;code_1744121461727&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location = /seller/api/v1/records {
    content_by_lua_file ./lua/records.lua;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1226&quot; data-start=&quot;1190&quot;&gt;정확히 /seller/api/v1/records 요청 오면&lt;/li&gt;
&lt;li data-end=&quot;1260&quot; data-start=&quot;1227&quot;&gt;Lua 스크립트 ./lua/records.lua 실행&lt;/li&gt;
&lt;li data-end=&quot;1289&quot; data-start=&quot;1261&quot;&gt;API 서버 처리를 Lua로 직접 수행하는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Lua 파일&lt;/h3&gt;
&lt;pre id=&quot;code_1744121525211&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;local pg = require &quot;resty.postgres&quot;
local cjson = require &quot;cjson&quot;

-- 요청 메서드 확인
if ngx.req.get_method() ~= &quot;GET&quot; then
    ngx.status = 405
    ngx.header[&quot;Content-Type&quot;] = &quot;application/json&quot;
    ngx.say(cjson.encode({
        error = &quot;Method not allowed&quot;
    }))
    return
end

-- 페이지네이션 파라미터 가져오기
local args = ngx.req.get_uri_args()
local page = tonumber(args.page) or 1
local limit = tonumber(args.limit) or 10
local offset = (page - 1) * limit

-- PostgreSQL 연결
local db, err = pg:new()
if not db then
    ngx.status = 500
    ngx.header[&quot;Content-Type&quot;] = &quot;application/json&quot;
    ngx.say(cjson.encode({
        error = &quot;Failed to create database object&quot;
    }))
    return
end

db:set_timeout(1000) -- 1초 타임아웃
ngx.log(ngx.ERR, &quot;DB HOST =&amp;gt; &quot;, os.getenv(&quot;DB_HOST&quot;))

-- 데이터베이스 접속
local ok, err = db:connect({
    host = os.getenv(&quot;DB_HOST&quot;),
    port = tonumber(os.getenv(&quot;DB_PORT&quot;)),
    database = os.getenv(&quot;DB_NAME&quot;),
    user = os.getenv(&quot;DB_USER&quot;),
    password = os.getenv(&quot;DB_PASSWORD&quot;)
})

if not ok then
    ngx.status = 500
    ngx.header[&quot;Content-Type&quot;] = &quot;application/json&quot;
    ngx.say(cjson.encode({
        error = &quot;database connection error: &quot; .. err
    }))
    return
end

-- 총 레코드 수 조회
local count_sql = &quot;SELECT COUNT(*) as total FROM resource_transport_records&quot;
local res, err = db:query(count_sql)
if not res then
    ngx.status = 500
    ngx.header[&quot;Content-Type&quot;] = &quot;application/json&quot;
    ngx.say(cjson.encode({
        error = &quot;Failed to query total count: &quot; .. (err or &quot;unknown error&quot;)
    }))
    return
end

local total = tonumber(res[1].total) or 0


local sql = [[
    SELECT 
       *
    FROM 
        resource_transport_records
    LIMIT ]] .. limit .. &quot; OFFSET &quot; .. offset

local records, err = db:query(sql)
if not records then
    ngx.status = 500
    ngx.header[&quot;Content-Type&quot;] = &quot;application/json&quot;
    ngx.say(cjson.encode({
        error = &quot;Failed to query records: &quot; .. (err or &quot;unknown error&quot;)
    }))
    return
end

-- DB 연결 반환 (커넥션 풀에)
local ok, err = db:set_keepalive(10000, 100)
if not ok then
    ngx.log(ngx.ERR, &quot;Failed to set keepalive: &quot;, err)
end

-- 응답 구성
local response = {
    data = {
        pagnation = {
            page = page,
            total = total,
            limit = limit
        },
        records = records
    }
}

-- JSON 응답 반환
ngx.header[&quot;Content-Type&quot;] = &quot;application/json&quot;
ngx.say(cjson.encode(response))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;역할&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL DB에서 resource_transport_records 테이블 조회 &amp;rarr; 페이지네이션 처리 &amp;rarr; JSON 응답 반환&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작 흐름 (step by step)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 모듈 불러오기&lt;/h3&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;local pg = require &quot;resty.postgres&quot;   -- PostgreSQL 드라이버
local cjson = require &quot;cjson&quot;         -- JSON 인코딩
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. GET 메서드 체크&lt;/h3&gt;
&lt;pre class=&quot;accesslog&quot;&gt;&lt;code&gt;if ngx.req.get_method() ~= &quot;GET&quot; then
    return 405 에러
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; GET 요청만 허용 (다른 건 막음)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. QueryString 파라미터 받기&lt;/h3&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;local args = ngx.req.get_uri_args()
local page = tonumber(args.page) or 1
local limit = tonumber(args.limit) or 10
local offset = (page - 1) * limit
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; /api/v1/records?page=2&amp;amp;limit=20 이런 식 처리&lt;br /&gt;&amp;rarr; 기본값 page=1, limit=10&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. DB 연결 준비&lt;/h3&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;local db, err = pg:new()
db:set_timeout(1000)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; DB 연결 준비 + 타임아웃 1초 설정&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. DB 접속&lt;/h3&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;local ok, err = db:connect({
  host = os.getenv(&quot;DB_HOST&quot;),
  ...
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; nginx.conf에 등록한 env 변수로 DB 접속&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 전체 데이터 개수 조회&lt;/h3&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;local count_sql = &quot;SELECT COUNT(*) as total FROM resource_transport_records&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 총 레코드 수 가져옴 (pagination 위해)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 데이터 조회&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;local sql = [[
SELECT * FROM resource_transport_records
LIMIT limit OFFSET offset
]]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 페이지네이션 쿼리 실행&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 커넥션 풀 반환&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;db:set_keepalive(10000, 100)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; DB 연결을 닫지 않고 재사용하도록 풀에 넣음&lt;br /&gt;(timeout 10초, 최대 100개)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9. JSON 응답 반환&lt;/h3&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;ngx.say(cjson.encode({
    data = {
        pagnation = { page, total, limit },
        records = records
    }
}))
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 응답 형태 &amp;darr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-08 오후 10.05.11.png&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxB69N/btsNest5ooF/VkMOKitfDKSMQk6IZun8U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxB69N/btsNest5ooF/VkMOKitfDKSMQk6IZun8U0/img.png&quot; data-alt=&quot;insomnia로 본 response값&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxB69N/btsNest5ooF/VkMOKitfDKSMQk6IZun8U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxB69N%2FbtsNest5ooF%2FVkMOKitfDKSMQk6IZun8U0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;770&quot; height=&quot;850&quot; data-filename=&quot;스크린샷 2025-04-08 오후 10.05.11.png&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;850&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;insomnia로 본 response값&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>openresty</category>
      <author>wanduek</author>
      <guid isPermaLink="true">https://minjooig.tistory.com/198</guid>
      <comments>https://minjooig.tistory.com/198#entry198comment</comments>
      <pubDate>Tue, 8 Apr 2025 23:15:29 +0900</pubDate>
    </item>
    <item>
      <title>Docker credential 문제</title>
      <link>https://minjooig.tistory.com/197</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-25 오후 3.00.38.png&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;41&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BkQW9/btsM0MnAM19/3RgONKqwURyWHAFfZ1SDUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BkQW9/btsM0MnAM19/3RgONKqwURyWHAFfZ1SDUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BkQW9/btsM0MnAM19/3RgONKqwURyWHAFfZ1SDUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBkQW9%2FbtsM0MnAM19%2F3RgONKqwURyWHAFfZ1SDUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;548&quot; height=&quot;41&quot; data-filename=&quot;스크린샷 2025-03-25 오후 3.00.38.png&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;41&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 openresty를 도커로 실행하기 위해 docker-compose up 이라는 명령어를 사용했으나 인증 문제로 인해 명령어가 거절당했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-25 오후 3.00.55.png&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ofl3K/btsM3n7smyq/334dCxjkB4m3BDPrg21Ur0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ofl3K/btsM3n7smyq/334dCxjkB4m3BDPrg21Ur0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ofl3K/btsM3n7smyq/334dCxjkB4m3BDPrg21Ur0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOfl3K%2FbtsM3n7smyq%2F334dCxjkB4m3BDPrg21Ur0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;852&quot; height=&quot;107&quot; data-filename=&quot;스크린샷 2025-03-25 오후 3.00.55.png&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;107&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 docker 링크에 접속하여 confimation code를 입력하고&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1743347618474&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker login&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 입력했으나 역시 같은 이유로 거부되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&lt;/p&gt;
&lt;pre id=&quot;code_1743347842068&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;~/.docker/config.json&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 경로로 가서 &lt;b&gt;&quot;credsStore&quot;: &quot;desktop&quot;&lt;/b&gt; 부분을 지우면 docker-credential-desktop을 사용하지 않도록 설정하고, 자격 증명을 수동으로 입력하고 Docker CLI에서 직접 로그인이 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &quot;credsStore&quot;: &quot;desktop&quot;을 지우게 되면 보안 이슈가 발생 가능성이 있기에 정 안될때 최후의 수단으로 추천한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-03-25 오후 3.01.17.png&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYhNBt/btsM1o7QwH9/B7kgS0jZLBBlQ6FhlrCLKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYhNBt/btsM1o7QwH9/B7kgS0jZLBBlQ6FhlrCLKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYhNBt/btsM1o7QwH9/B7kgS0jZLBBlQ6FhlrCLKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYhNBt%2FbtsM1o7QwH9%2FB7kgS0jZLBBlQ6FhlrCLKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;644&quot; height=&quot;164&quot; data-filename=&quot;스크린샷 2025-03-25 오후 3.01.17.png&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Docker</category>
      <author>wanduek</author>
      <guid isPermaLink="true">https://minjooig.tistory.com/197</guid>
      <comments>https://minjooig.tistory.com/197#entry197comment</comments>
      <pubDate>Mon, 31 Mar 2025 00:21:11 +0900</pubDate>
    </item>
    <item>
      <title>Signed URL</title>
      <link>https://minjooig.tistory.com/196</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KAzsO/btsM0pK1CfY/lgPiKipW8LlMyJyz3z0xMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KAzsO/btsM0pK1CfY/lgPiKipW8LlMyJyz3z0xMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KAzsO/btsM0pK1CfY/lgPiKipW8LlMyJyz3z0xMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKAzsO%2FbtsM0pK1CfY%2FlgPiKipW8LlMyJyz3z0xMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;249&quot; height=&quot;187&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS S3의 Signed URL은 AWS S3 버킷에 저장된 객체에 대해 제한된 시간 동안 인증된 접근 권한을 부여하는 URL입니다. 보통 S3 버킷의 객체는 기본적으로 비공개(private)로 설정되어 있기 때문에 특정 사용자가 애플리캐이션에게만 접근 권한을 주기 위해 Signed URL을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 특징&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;임시 접근 권한&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Signed URL은 생성 시 지정한 만료 시간(expiration time) 이후에는 사용할 수 없으므로 일시적인 접근 권한을 부여할 때 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;보안성&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL에 포함된 서명(signature)은 요청이 변경되지 않았음을 보장하며, AWS 자격 증명을 사용해 생성되기 때문에 외부에서 위변조하기 어렵습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;다양한 활용 사례&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 다운로드: 사용자에게 파일 다운로드 권한을 임시로 부여할 때 사용됩니다.&lt;/li&gt;
&lt;li&gt;파일 업로드: 클라이언트가 S3 버킷에 직접 파일을 업로드할 수 있도록 할 때 사용됩니다.&lt;/li&gt;
&lt;li&gt;권한 관리: 서버에서 직접 접근 제어를 하지 않고도 특정 파일에 대한 접근 권한을 제어할 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작동 방식&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;생성&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS SDK나 AWS CLI를 이용하여&amp;nbsp; Signed URL을 생성할 수 있습니다. 이때 AWS Access Key와 Secret Key를 사용해 URL에 대한 서명을 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;만료 시간 설정&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL을 생성할 때 사용자가 원하는 유효 기간을 설정합니다. 만료 시간이 지나면 해당 URL은 더 이상 유효하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;URL 구성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 URL에는 다음과 같은 쿼리 파라미터가 포함됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;X-Amz-Algorithm&lt;/b&gt; : 서명 생성에 사용된 알고리즘&lt;/li&gt;
&lt;li&gt;&lt;b&gt;X-Amz-Credential&lt;/b&gt; : AWS 자격 증명 정보&lt;/li&gt;
&lt;li&gt;&lt;b&gt;X-Amz-Date&lt;/b&gt; : 요청 날짜와 시간&lt;/li&gt;
&lt;li&gt;&lt;b&gt;X-Amz-Expires&lt;/b&gt; : URL의 만료 시간(초 단위)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;X-Amz-SignedHeader&lt;/b&gt; : 서명에 포함된 헤더&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 Signed URL을 사용해 클라이언트는 인증된 요청을 보낼 수 있으며, AWS는 이 URL에 포함된 서명과 파라미터들을 검증하여 요청을 승인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;활용 예시&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;웹 애플리케이션에서의 파일 공유&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자에게 S3에 저장된 파일을 공유할 때, 직접 버킷에 대한 권한을 부여하지 않고도 Signed URL을 통해 파일에 접근할 수 있도록 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모바일 애플리케이션에서의 업로드&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모바일 앱이 사용자로부터 파일을 받아 S3에 저장할 때, 서버는 업로드에 대한 Signed URL을 생성하여 클라이언트에 전달합니다. 클라이언트는 해당 URL로 직접 파일을 업로드합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Signed URL와 302 Redirect&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;서버 사이드 리다이렉션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버나 애플리케이션 서버가 클라이언트의 요청을 받은 후, 내부적으로 S3의 Signed URL을 생성하고 이를 클라이언트에게 직접 전달하지 않고 302 리다이렉트를 통해 클라이언트를 해당 URL로 보내는 방식입니다. 이렇게 하면 서버는 클라이언트에게 S3 버킷의 실제 접근 경로(또는 Signed URL)를 노출하지 않고 임시 접근 권한을 제공할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;보안과 유연성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;302 리다이렉트를 사용하면 사용자가 먼저 서버를 통해 접근하고 서버에서 적절한 인증/권한 체크 후에 S3 Signed URL로 리다이렉션을 할 수 있어 보안성을 높이는 데 도움을 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 사용자가 어떤 파일을 다운로드 요청을 하면 서버가 해당 파일에 대한 S3 Signed URL을 생성하고, 클라이언트에게 302 리다이렉트를 반환하여 자동으로 S3에서 파일을 다운로드하도록 할 수 있습니다. 이렇게 하면 서버는 직접 파일을 전송에 관여하지 않으면서도, 클라이언트에게 안전한 임시 접근 권한을 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, 302 리다이렉트는 S3 Signed URL과 결합되어 사용자의 요청을 중개하거나, 보안상의 이유로 S3의 직접적인 URL 노출을 피하고자 할 때 활용될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Signed_URL.drawio (1).png&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRdMaX/btsMZeKmrXR/y4clNkrJ1aQZfm5i3YytHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRdMaX/btsMZeKmrXR/y4clNkrJ1aQZfm5i3YytHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRdMaX/btsMZeKmrXR/y4clNkrJ1aQZfm5i3YytHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRdMaX%2FbtsMZeKmrXR%2Fy4clNkrJ1aQZfm5i3YytHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;471&quot; height=&quot;311&quot; data-filename=&quot;Signed_URL.drawio (1).png&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>AWS</category>
      <author>wanduek</author>
      <guid isPermaLink="true">https://minjooig.tistory.com/196</guid>
      <comments>https://minjooig.tistory.com/196#entry196comment</comments>
      <pubDate>Thu, 27 Mar 2025 23:09:31 +0900</pubDate>
    </item>
    <item>
      <title>유저가 다운받은 파일 조회한 것을 클라이언트(셀러)가 조회할 때의 flow chart</title>
      <link>https://minjooig.tistory.com/195</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 웹에서 게시된 파일(PDF, VOD, 등등)을 다운 받았을때 셀러가 유저가 어떤 파일을 다운받았고 얼마나(채널, 컨텐츠, 유저) 다운 받았는지 알기 위해 해당 api를 개발하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Records/users&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SellerAPI Record-자신의 채널에 가입한 유저 별 일간, 월간 전체 다운로드 수, 용량 가져오기의 복사본의 복사본.drawio.png&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;871&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKZl4h/btsMX6Ns4CJ/yAGx7pPKrTZ0Pa2SFqREeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKZl4h/btsMX6Ns4CJ/yAGx7pPKrTZ0Pa2SFqREeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKZl4h/btsMX6Ns4CJ/yAGx7pPKrTZ0Pa2SFqREeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKZl4h%2FbtsMX6Ns4CJ%2FyAGx7pPKrTZ0Pa2SFqREeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;871&quot; data-filename=&quot;SellerAPI Record-자신의 채널에 가입한 유저 별 일간, 월간 전체 다운로드 수, 용량 가져오기의 복사본의 복사본.drawio.png&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;871&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;target_id&lt;/b&gt;: 다운로드 받은 유저의 고유한 id&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;target_id 기준으로 조회&lt;/li&gt;
&lt;li&gt;type = daliy :&amp;nbsp; &lt;b&gt;유저별&lt;/b&gt;로 다운받은 기록을 일별로 조회한다.&lt;/li&gt;
&lt;li&gt;type = monthly : &lt;b&gt;유저별&lt;/b&gt;로 다운받은 기록을 월별로 조회한다.&lt;/li&gt;
&lt;li&gt;type = combined : &lt;b&gt;유저별&lt;/b&gt;로 다운받은 기록을 일별, 월별로 조회한다.&lt;/li&gt;
&lt;li&gt;컨텐츠를 다운로드 한 유저가 있어야 한다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;없을 시 -&amp;gt; 404 NOT FOUND&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Records&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SellerAPI Record-자신의 모든 채널에 속한 모든 일간, 월간 전체 다운로드 수, 용량 가져오기.drawio.png&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;821&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tAy22/btsMYfcjlDg/fbCrF6vuKVQJiKfWRSPj20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tAy22/btsMYfcjlDg/fbCrF6vuKVQJiKfWRSPj20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tAy22/btsMYfcjlDg/fbCrF6vuKVQJiKfWRSPj20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtAy22%2FbtsMYfcjlDg%2FfbCrF6vuKVQJiKfWRSPj20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;821&quot; data-filename=&quot;SellerAPI Record-자신의 모든 채널에 속한 모든 일간, 월간 전체 다운로드 수, 용량 가져오기.drawio.png&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;821&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;channel_id&lt;/b&gt; : 셀러가 생성한 채널의 고유한 id&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(여기서 채널은 샐러가 생성한 웹 사이트이다.)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;channel_id 기준으로 조회&lt;/li&gt;
&lt;li&gt;type = daliy :&amp;nbsp; 유저가 다운받은 기록을 &lt;b&gt;채널별&lt;/b&gt;로 일별 조회한다.&lt;/li&gt;
&lt;li&gt;type = monthly : 유저가 다운받은 기록을&amp;nbsp; &lt;b&gt;채널별&lt;/b&gt;로 월별 조회한다.&lt;/li&gt;
&lt;li&gt;type = combined : 유저가 다운받은 기록을 &lt;b&gt;채널별&lt;/b&gt;로 일별, 월별로 조회한다.&lt;/li&gt;
&lt;li&gt;컨텐츠를 다운로드 한 유저가 있어야 한다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;없을 시 -&amp;gt; 404 NOT FOUND&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Records/contents&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SellerAPI Record-자신의 컨텐츠별 채널에 속한 일간, 월간 전체 다운로드 수, 용량 가져오기의 복사본.drawio.png&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;861&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l5sSR/btsMZGfpeSt/POUDJSuoypYQoKXfUAlgSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l5sSR/btsMZGfpeSt/POUDJSuoypYQoKXfUAlgSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l5sSR/btsMZGfpeSt/POUDJSuoypYQoKXfUAlgSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl5sSR%2FbtsMZGfpeSt%2FPOUDJSuoypYQoKXfUAlgSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;861&quot; data-filename=&quot;SellerAPI Record-자신의 컨텐츠별 채널에 속한 일간, 월간 전체 다운로드 수, 용량 가져오기의 복사본.drawio.png&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;861&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;target_model&lt;/b&gt; : 셀러가 제작한 컨텐츠의 고유한 id&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;target_model 기준으로 조회&lt;/li&gt;
&lt;li&gt;type = daliy :&amp;nbsp; 유저가 다운받은 기록을 &lt;b&gt;컨텐츠&lt;/b&gt;로 일별 조회한다.&lt;/li&gt;
&lt;li&gt;type = monthly : 유저가 다운받은 기록을&amp;nbsp; &lt;b&gt;컨텐츠&lt;/b&gt;로 월별 조회한다.&lt;/li&gt;
&lt;li&gt;type = combined : 유저가 다운받은 기록을 &lt;b&gt;컨텐츠&lt;/b&gt;로 일별, 월별로 조회한다.&lt;/li&gt;
&lt;li&gt;컨텐츠를 다운로드 한 유저가 있어야 한다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;없을 시 -&amp;gt; 404 NOT FOUND&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>퍼블엘</category>
      <author>wanduek</author>
      <guid isPermaLink="true">https://minjooig.tistory.com/195</guid>
      <comments>https://minjooig.tistory.com/195#entry195comment</comments>
      <pubDate>Thu, 27 Mar 2025 22:01:36 +0900</pubDate>
    </item>
    <item>
      <title>Niginx란?(openresty 사용)</title>
      <link>https://minjooig.tistory.com/194</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx(엔진엑스)는 고성능 웹 서버 소프트웨어이자 리버스 프록시, 로드 밸런서, HTTP 캐시 등 다양한 역할을 수행할 수 있는 오픈 소스 솔루션이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zniJa/btsMY5lpO10/ZQTMJu3TNhcAyUZcOjGa5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zniJa/btsMY5lpO10/ZQTMJu3TNhcAyUZcOjGa5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zniJa/btsMY5lpO10/ZQTMJu3TNhcAyUZcOjGa5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzniJa%2FbtsMY5lpO10%2FZQTMJu3TNhcAyUZcOjGa5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;230&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;243&quot; data-start=&quot;127&quot;&gt;&lt;b&gt;높은 성능과 효율성:&lt;/b&gt;&lt;br /&gt;Nginx는 이벤트 기반 아키텍처를 사용하여 다수의 동시 연결을 효율적으로 처리할 수 있습니다. 이를 통해 높은 트래픽 상황에서도 빠른 응답 속도를 유지할 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;317&quot; data-start=&quot;245&quot;&gt;&lt;b&gt;낮은 메모리 사용량:&lt;/b&gt;&lt;br /&gt;동시 접속 수가 많아져도 메모리 사용량이 적어 서버 자원을 효율적으로 사용할 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;461&quot; data-start=&quot;319&quot;&gt;&lt;b&gt;리버스 프록시 및 로드 밸런싱:&lt;/b&gt;&lt;br /&gt;웹 애플리케이션의 트래픽을 여러 서버로 분산시키거나, 클라이언트의 요청을 백엔드 서버로 전달하는 리버스 프록시 기능을 제공합니다. 이를 통해 부하 분산과 장애 조치(failover)를 구현할 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;565&quot; data-start=&quot;463&quot;&gt;&lt;b&gt;정적 파일 서빙:&lt;/b&gt;&lt;br /&gt;정적 콘텐츠(HTML, CSS, JavaScript, 이미지 등)를 빠르게 제공하는 데 최적화되어 있어, 콘텐츠 전송 속도를 향상시킬 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;640&quot; data-start=&quot;567&quot;&gt;&lt;b&gt;모듈화 및 확장성:&lt;/b&gt;&lt;br /&gt;다양한 모듈을 통해 기능을 확장할 수 있으며, 필요에 따라 커스텀 모듈을 추가할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 용도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;718&quot; data-start=&quot;655&quot;&gt;&lt;b&gt;웹 서버:&lt;/b&gt;&lt;br /&gt;고성능 HTTP 서버로서 정적 및 동적 콘텐츠를 클라이언트에 제공하는 데 사용된다.&lt;/li&gt;
&lt;li data-end=&quot;784&quot; data-start=&quot;722&quot;&gt;&lt;b&gt;리버스 프록시 서버:&lt;/b&gt;&lt;br /&gt;클라이언트 요청을 내부 서버로 전달하여 보안 및 부하 분산을 담당한다.&lt;/li&gt;
&lt;li data-end=&quot;844&quot; data-start=&quot;786&quot;&gt;&lt;b&gt;로드 밸런서:&lt;/b&gt;&lt;br /&gt;다수의 서버 간에 트래픽을 분산시켜 시스템의 안정성과 확장성을 높인다.&lt;/li&gt;
&lt;li data-end=&quot;911&quot; data-start=&quot;846&quot;&gt;&lt;b&gt;HTTP 캐시 서버:&lt;/b&gt;&lt;br /&gt;자주 요청되는 콘텐츠를 캐시하여 서버 부하를 줄이고 응답 속도를 향상시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Niginx는 왜 아직도 인기있는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;984&quot; data-start=&quot;929&quot;&gt;&lt;b&gt;높은 성능:&lt;/b&gt;&lt;br /&gt;동시 접속 처리를 효율적으로 수행해 대규모 트래픽 처리에 적합하다.&lt;/li&gt;
&lt;li data-end=&quot;1076&quot; data-start=&quot;988&quot;&gt;&lt;b&gt;유연성:&lt;/b&gt;&lt;br /&gt;다양한 역할(웹 서버, 리버스 프록시, 로드 밸런서 등)을 하나의 소프트웨어로 처리할 수 있어, 시스템 설계의 유연성을 제공한다.&lt;/li&gt;
&lt;li data-end=&quot;1150&quot; data-start=&quot;1080&quot;&gt;&lt;b&gt;확장성:&lt;/b&gt;&lt;br /&gt;서버 확장이 필요한 환경에서 간편하게 설정을 조정하고 추가할 수 있어, 대규모 서비스에 적합하다.&lt;/li&gt;
&lt;li data-end=&quot;1237&quot; data-start=&quot;1154&quot;&gt;&lt;b&gt;오픈 소스:&lt;/b&gt;&lt;br /&gt;커뮤니티가 활발하게 기여하며 지속적인 업데이트와 개선이 이루어지고 있어, 최신 웹 환경에 신속하게 대응할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Niginx 서버 띄우기(openresty 사용)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;openresty는 nginx을 사용하기에 openresty 기준으로 nginx서버 띄우는걸 보여줄 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(필자는 docker를 사용하여 서버를 띄웠다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yml&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1743001845803&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  openresty:
    image: openresty/openresty:latest
    container_name: openresty
    ports:
      - &quot;8080:80&quot;
    volumes:
      - ./openresty/nginx.conf:/etc/nginx/nginx.conf:ro
    restart: unless-stopped

  nginx:
    image: nginx:latest
    container_name: nginx
    ports:
      - &quot;8081:80&quot;
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/html:/usr/share/nginx/html:ro
    restart: unless-stopped&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpliuE/btsMYxCDU3Q/dARWzTEqMF2NxVHPKjt1j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpliuE/btsMYxCDU3Q/dARWzTEqMF2NxVHPKjt1j0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpliuE/btsMYxCDU3Q/dARWzTEqMF2NxVHPKjt1j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpliuE%2FbtsMYxCDU3Q%2FdARWzTEqMF2NxVHPKjt1j0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1137&quot; height=&quot;73&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx의 이미지에서 nginx.conf와 openrest이미지에서 lua파일을 생성하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RrnY9/btsMWCZ4i8v/kkmGNjGMakdutyyT06OY0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RrnY9/btsMWCZ4i8v/kkmGNjGMakdutyyT06OY0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RrnY9/btsMWCZ4i8v/kkmGNjGMakdutyyT06OY0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRrnY9%2FbtsMWCZ4i8v%2FkkmGNjGMakdutyyT06OY0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1136&quot; height=&quot;73&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;openresty 컨테이너에서 정상적으로 설치됨을 알려준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1328&quot; data-origin-height=&quot;831&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpmFJQ/btsMYdLeC5c/1boZQODqrY283KfkX5fgeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpmFJQ/btsMYdLeC5c/1boZQODqrY283KfkX5fgeK/img.png&quot; data-alt=&quot;브라우저로 확인하니 openresty에 대한 기본화면이 정상적으로 뜨는 모습을 볼 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpmFJQ/btsMYdLeC5c/1boZQODqrY283KfkX5fgeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpmFJQ%2FbtsMYdLeC5c%2F1boZQODqrY283KfkX5fgeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1328&quot; height=&quot;831&quot; data-origin-width=&quot;1328&quot; data-origin-height=&quot;831&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;브라우저로 확인하니 openresty에 대한 기본화면이 정상적으로 뜨는 모습을 볼 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz81jQ/btsMYo61gZX/s2FCy9kKYwjiRXpQq228Ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz81jQ/btsMYo61gZX/s2FCy9kKYwjiRXpQq228Ik/img.png&quot; data-alt=&quot;lsof -i :8080명령어를 통해 8080 포트를 사용중인걸 확인하였다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz81jQ/btsMYo61gZX/s2FCy9kKYwjiRXpQq228Ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz81jQ%2FbtsMYo61gZX%2Fs2FCy9kKYwjiRXpQq228Ik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;688&quot; height=&quot;71&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;lsof -i :8080명령어를 통해 8080 포트를 사용중인걸 확인하였다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Nginx</category>
      <author>wanduek</author>
      <guid isPermaLink="true">https://minjooig.tistory.com/194</guid>
      <comments>https://minjooig.tistory.com/194#entry194comment</comments>
      <pubDate>Thu, 27 Mar 2025 00:21:42 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot 핵심 개념</title>
      <link>https://minjooig.tistory.com/193</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKLvZV/btsMwfJI1Uf/NxJUWGlM2gw9O3wIpnuwVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKLvZV/btsMwfJI1Uf/NxJUWGlM2gw9O3wIpnuwVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKLvZV/btsMwfJI1Uf/NxJUWGlM2gw9O3wIpnuwVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKLvZV%2FbtsMwfJI1Uf%2FNxJUWGlM2gw9O3wIpnuwVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;490&quot; height=&quot;257&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 자동 구성(Auto-Configuration)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스패스 스캔: Spring Boot는 애플리케이션 실행 시 클래스패스에 있는 라이브러리와 설정 파일을 스캔한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건부 설정: 특정 라이브러리가 존재하거나, 특정 빈(bean)이 없는 경우에만 자동으로 설정을 적용한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 의존성 주입 (Dependency Injection)와 제어의 역전 (IoC)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 관리: 스프링 프레임워크의 핵심 개념인 &lt;b&gt;DI&lt;/b&gt;와 &lt;b&gt;IoC&lt;/b&gt;를 통해 애플리케이션 내의 객체 간의 의존성을 스프링 컨테이너가 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유연성 및 테스트 용이성: 객체들이 직접 생성되기보다는 컨테이너에 의해 주입되므로, 코드의 결합도를 낮추고, 테스트 및 유지보수가 용이해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 스타터 의존성 (Starter Dependencies)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편리한 의존성 관리: &lt;b&gt;spring-boot-starter-web&lt;/b&gt;, &lt;b&gt;spring-boot-starter-data-jpa&lt;/b&gt; 등과 같은 스타터를 사용하면, 필요한 라이브러리들을 한 번에 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 관리: 필요한 모든 라이브러리의 버전이 호환되도록 관리되기 때문에, 개발자는 의존성 충돌에 신경 쓸 필요가 줄어든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 내장 서버 (Embedded Server)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉시 실행: 별도의 WAS(&lt;b&gt;Web Application Server&lt;/b&gt;) 설치 없이, Tomcat, Jetty, Undertow와 같은 내장 서버를 통해 바로 애플리케이션을 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;독립 실행혀이 JAR 파일 하나로 실행 가능한 독립적인 애플리케이션을 만들 수 있어, 배포와 운영이 간편하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 외부화된 설정 및 프로파일 (Extenalized Configuration &amp;amp; Profiles)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 파일: &lt;b&gt;application.properties&lt;/b&gt; 또는 &lt;b&gt;application.yml&lt;/b&gt; 파일을 통해 애플리케이션의 환경 설정을 외부화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로파일: 개발, 테스트, 운영 등 여러 환경에 맞게 프로파일을 설정하여, 환경별로 다른 설정을 손쉽게 적용할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. 컨벤션 over 설정 (Convention over Configuration)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 설정 제공: Spring Boot는 표준적인 설정을 미리 제공하여, 개발자가 초기 설정에 소요하는 시간을 줄여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동화된 설정: 기본 규칙에 따라 자동으로 동작하므로, 필요에 따라 커스터마이징 할 수 있는 유연성도 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7. 모니터링 및 관리 (Actuator)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 관리: &lt;b&gt;Actuator&lt;/b&gt; 모듈을 통해 애플리케이션의 상태, 메트릭, 트래픽, 로그 등을 모니터링 할 수 있는 다양한 앤드포인트를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Spring Boot는 자동 구성, 의존성 주입, 내장 서버 지원, 외부 설정, 그리고 스타터 의존성 등의 기능을 통해 스프링 기반 애플리케이션 개발을 더욱 쉽고 빠르게 만들어 준다. 이러한 원리 덕분에 복잡한 설정 없이도 강력한 기능을 손쉽게 구현할 수 있게 된다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>wanduek</author>
      <guid isPermaLink="true">https://minjooig.tistory.com/193</guid>
      <comments>https://minjooig.tistory.com/193#entry193comment</comments>
      <pubDate>Tue, 25 Feb 2025 15:45:12 +0900</pubDate>
    </item>
  </channel>
</rss>