<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Daily log</title>
    <link>https://hanks-log.tistory.com/</link>
    <description>일상/개발/사진 기록용</description>
    <language>ko</language>
    <pubDate>Fri, 26 Jun 2026 23:54:49 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>hanks</managingEditor>
    <image>
      <title>Daily log</title>
      <url>https://tistory1.daumcdn.net/tistory/6972928/attach/641e74e17b724722aedff922925bc822</url>
      <link>https://hanks-log.tistory.com</link>
    </image>
    <item>
      <title>Theme: {theme}</title>
      <link>https://hanks-log.tistory.com/106</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Context API와 Redux는 React 애플리케이션에서 상태 관리를 위한 두 가지 주요 방법입니다. 이 글에서는 Context API와 Redux의 특징을 비교 분석하고, 어떤 상황에서 어떤 방법을 선택하는 것이 효율적인지 알아봅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 상태 관리가 필요할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 애플리케이션은 컴포넌트 기반으로 구축되는데, 컴포넌트 간에 데이터를 효율적으로 공유하고 관리하는 것이 중요합니다. 특히 규모가 큰 애플리케이션에서는 여러 컴포넌트가 동일한 데이터에 접근하고 수정해야 하는 경우가 많습니다. 이때 상태 관리가 제대로 이루어지지 않으면, 데이터 흐름이 복잡해지고 예측 불가능해져 애플리케이션의 유지보수가 어려워집니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① Props Drilling 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 기본적으로 데이터를 전달하는 방식은 props를 이용하는 것입니다. 하지만 컴포넌트 트리가 깊어질수록, 필요하지 않은 컴포넌트에도 props를 전달해야 하는 &lt;b&gt;props drilling&lt;/b&gt; 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jsx // 예시: Props Drilling function App() { const user = { name: 'John Doe', age: 30 }; return (&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function Header({ user }) { return (&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function UserInfo({ user }) { return (&lt;/p&gt;
&lt;div&gt;
&lt;h1&gt;{user.name}&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Age: {user.age}&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 &lt;code&gt;App&lt;/code&gt; 컴포넌트에서 &lt;code&gt;UserInfo&lt;/code&gt; 컴포넌트로 &lt;code&gt;user&lt;/code&gt; 데이터를 전달하기 위해 &lt;code&gt;Header&lt;/code&gt; 컴포넌트를 거쳐야 합니다. &lt;code&gt;Header&lt;/code&gt; 컴포넌트는 &lt;code&gt;user&lt;/code&gt; 데이터를 사용하지 않지만, 단순히 props를 전달하는 역할만 수행합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Context API vs Redux: 핵심 개념 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context API와 Redux는 각각 다른 방식으로 상태 관리를 제공합니다. 다음 표는 두 방법의 핵심 개념을 비교한 것입니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Context API&lt;/th&gt;
&lt;th&gt;Redux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;상태 저장소&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Context Provider&lt;/td&gt;
&lt;td&gt;Store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;상태 업데이트&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useState&lt;/code&gt; 또는 &lt;code&gt;useReducer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reducer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;상태 접근&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useContext&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useSelector&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;미들웨어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;지원 안 함 (별도 구현 필요)&lt;/td&gt;
&lt;td&gt;지원 (Redux Thunk, Redux Saga 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;복잡성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;비교적 낮음&lt;/td&gt;
&lt;td&gt;비교적 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① Context API&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context API는 React 내장 기능으로, 컴포넌트 트리를 통해 데이터를 명시적으로 전달하지 않고도 데이터를 공유할 수 있게 해줍니다. &lt;code&gt;Context.Provider&lt;/code&gt;를 사용하여 데이터를 제공하고, &lt;code&gt;useContext&lt;/code&gt; 훅을 사용하여 데이터를 소비합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jsx // 예시: Context API 사용 import React, { createContext, useContext, useState } from 'react';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const UserContext = createContext();&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function App() { const [user, setUser] = useState({ name: 'John Doe', age: 30 });&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return ( &amp;lt;UserContext.Provider value={{ user, setUser }}&amp;gt;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;/UserContext.Provider&amp;gt; ); }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function Header() { return (&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function UserInfo() { const { user } = useContext(UserContext);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return (&lt;/p&gt;
&lt;div&gt;
&lt;h1&gt;{user.name}&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Age: {user.age}&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 &lt;code&gt;App&lt;/code&gt; 컴포넌트는 &lt;code&gt;UserContext.Provider&lt;/code&gt;를 사용하여 &lt;code&gt;user&lt;/code&gt; 데이터를 제공합니다. &lt;code&gt;UserInfo&lt;/code&gt; 컴포넌트는 &lt;code&gt;useContext&lt;/code&gt; 훅을 사용하여 &lt;code&gt;user&lt;/code&gt; 데이터에 직접 접근할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② Redux&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redux는 JavaScript 애플리케이션을 위한 예측 가능한 상태 컨테이너입니다. 단일 스토어에서 애플리케이션의 모든 상태를 관리하고, reducer를 사용하여 상태를 업데이트합니다. &lt;code&gt;useSelector&lt;/code&gt; 훅을 사용하여 스토어에서 데이터를 가져오고, &lt;code&gt;useDispatch&lt;/code&gt; 훅을 사용하여 액션을 디스패치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jsx // 예시: Redux 사용 (간략화) import React from 'react'; import { useSelector, useDispatch } from 'react-redux';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function UserInfo() { const user = useSelector(state =&amp;gt; state.user); const dispatch = useDispatch();&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return (&lt;/p&gt;
&lt;div&gt;
&lt;h1&gt;{user.name}&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Age: {user.age}&lt;/p&gt;
&amp;lt;button onClick={() =&amp;gt; dispatch({ type: 'UPDATE_AGE', payload: user.age + 1 })}&amp;gt; Increase Age&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 &lt;code&gt;UserInfo&lt;/code&gt; 컴포넌트는 &lt;code&gt;useSelector&lt;/code&gt; 훅을 사용하여 Redux 스토어에서 &lt;code&gt;user&lt;/code&gt; 데이터를 가져오고, &lt;code&gt;useDispatch&lt;/code&gt; 훅을 사용하여 &lt;code&gt;UPDATE_AGE&lt;/code&gt; 액션을 디스패치하여 &lt;code&gt;user&lt;/code&gt;의 나이를 업데이트합니다.&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;p data-ke-size=&quot;size16&quot;&gt;Context API와 Redux는 각각 장단점이 있으며, 애플리케이션의 규모와 복잡성에 따라 적절한 방법을 선택해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① Context API: 간단한 상태 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context API는 전역적으로 접근해야 하는 상태가 비교적 단순하고 애플리케이션의 규모가 작을 때 유용합니다. 예를 들어, 테마 설정, 사용자 인증 정보, 언어 설정 등과 같은 상태를 관리하는 데 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단하고 사용하기 쉬움&lt;/li&gt;
&lt;li&gt;React 내장 기능이므로 별도의 라이브러리 설치 불필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;미들웨어 지원 안 함 (비동기 작업 처리 어려움)&lt;/li&gt;
&lt;li&gt;상태 업데이트 시 관련된 모든 컴포넌트가 리렌더링될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② Redux: 복잡한 상태 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redux는 애플리케이션의 상태가 복잡하고 예측 가능하게 관리해야 할 때 유용합니다. 예를 들어, 사용자 인터랙션이 많고 데이터 흐름이 복잡한 애플리케이션에서 상태를 중앙 집중식으로 관리하는 데 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예측 가능한 상태 관리&lt;/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;b&gt;단점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설정과 사용법이 복잡함&lt;/li&gt;
&lt;li&gt;보일러플레이트 코드 많음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Context API와 Redux는 React 애플리케이션에서 상태 관리를 위한 강력한 도구입니다. Context API는 간단한 상태 관리에 적합하고, Redux는 복잡한 상태 관리에 적합합니다. 애플리케이션의 요구 사항을 고려하여 적절한 방법을 선택하는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로는 Context API로 시작해서 애플리케이션이 복잡해짐에 따라 Redux로 전환하는 것이 좋은 전략이라고 생각합니다. Redux의 복잡성을 처음부터 감당하기보다는 Context API를 통해 상태 관리의 기본을 이해하고, 필요에 따라 Redux로 확장하는 것이 효율적일 수 있습니다.&lt;/p&gt;</description>
      <category>개발/React</category>
      <category>Context API vs Redux</category>
      <category>Javascript</category>
      <category>React Context API</category>
      <category>React 개발</category>
      <category>react 상태 관리</category>
      <category>Redux</category>
      <category>State management</category>
      <category>상태 관리</category>
      <category>상태 관리 비교</category>
      <category>프론트엔드 개발</category>
      <author>hanks</author>
      <guid isPermaLink="true">https://hanks-log.tistory.com/106</guid>
      <comments>https://hanks-log.tistory.com/106#entry106comment</comments>
      <pubDate>Thu, 26 Feb 2026 09:40:31 +0900</pubDate>
    </item>
    <item>
      <title>React 성능 최적화 기법</title>
      <link>https://hanks-log.tistory.com/105</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 애플리케이션 성능을 끌어올리는 다양한 방법들을 살펴보고, 실제 적용 사례를 통해 꿀팁을 얻어보자!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 React 성능 최적화가 중요할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 정말 강력한 프론트엔드 라이브러리인데요, 사용하기도 쉽고 컴포넌트 기반이라 개발 생산성도 높여주죠. 하지만 프로젝트 규모가 커지거나 복잡한 UI를 다루게 되면 성능 문제가 발목을 잡을 때가 있더라고요. 사용자 경험(UX)은 당연히 떨어지고, 심하면 앱이 멈추는 현상까지 발생할 수 있어요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 느린 렌더링, 답답한 사용자 경험&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 기본적으로 가상 DOM을 사용해서 DOM 업데이트를 효율적으로 처리하지만, 모든 컴포넌트가 불필요하게 리렌더링되면 성능 저하가 발생할 수 있어요. 예를 들어, 부모 컴포넌트의 state가 변경되면 자식 컴포넌트까지 모두 리렌더링되는 경우가 생기죠. 사용자는 버튼 클릭 후 반응이 느리거나, 스크롤이 끊기는 현상을 겪게 될 거예요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② 초기 로딩 속도, 첫인상이 중요해&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 앱은 보통 번들링 과정을 거쳐서 하나의 JavaScript 파일로 만들어지는데요, 이 파일 크기가 커지면 초기 로딩 속도가 느려질 수밖에 없어요. 특히 네트워크 환경이 좋지 않은 사용자에게는 더욱 심각한 문제가 되겠죠. 첫 화면이 늦게 뜨면 사용자들은 그냥 앱을 닫아버릴 수도 있어요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;React 성능 최적화, 어떻게 해야 할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 React 성능을 어떻게 최적화해야 할까요? 다행히 React는 성능 개선을 위한 다양한 도구와 기법을 제공하고 있어요. 몇 가지 주요 방법들을 살펴볼까요?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt; 활용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useMemo&lt;/code&gt;와 &lt;code&gt;useCallback&lt;/code&gt;은 React의 Hook인데요, 불필요한 연산을 줄이고 컴포넌트 리렌더링을 방지하는 데 아주 효과적이에요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;useMemo&lt;/code&gt;: 특정 값(value)을 메모이제이션해서, 의존성 배열의 값이 변경될 때만 연산을 다시 수행해요.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useCallback&lt;/code&gt;: 함수의 참조를 메모이제이션해서, 컴포넌트가 리렌더링되더라도 함수가 다시 생성되지 않도록 해줘요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript import React, { useState, useMemo, useCallback } from 'react';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function MyComponent({ data }) { const [count, setCount] = useState(0);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;// data가 변경될 때만 연산을 수행 const processedData = useMemo(() =&amp;gt; { console.log('Calculating processed data...'); return data.map(item =&amp;gt; item * 2); }, [data]);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;// setCount 함수를 메모이제이션 const handleClick = useCallback(() =&amp;gt; { setCount(prevCount =&amp;gt; prevCount + 1); }, []);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return (&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Count: {count}&lt;/p&gt;
&lt;button&gt;Increment&lt;/button&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Processed Data: {processedData.join(', ')}&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); }&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② &lt;code&gt;React.memo&lt;/code&gt;로 컴포넌트 리렌더링 제어하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;React.memo&lt;/code&gt;는 컴포넌트의 props가 변경되지 않았다면 리렌더링을 건너뛰도록 해주는 고차 컴포넌트(Higher-Order Component)예요. 함수형 컴포넌트를 감싸서 사용하면 되는데요, 컴포넌트가 불필요하게 리렌더링되는 것을 막아줘서 성능을 향상시킬 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript import React from 'react';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const MyComponent = React.memo(({ name, age }) =&amp;gt; { console.log('Rendering MyComponent...'); return (&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Name: {name}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Age: {age}&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); });&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export default MyComponent;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ &lt;code&gt;react-window&lt;/code&gt;로 대규모 리스트 렌더링 최적화하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 앱에서 엄청나게 많은 데이터를 리스트 형태로 보여줘야 한다면, &lt;code&gt;react-window&lt;/code&gt;를 사용하는 것을 고려해볼 수 있어요. &lt;code&gt;react-window&lt;/code&gt;는 보이는 영역에 있는 항목만 렌더링하고, 스크롤에 따라 필요한 항목을 동적으로 렌더링하는 방식으로 성능을 최적화해요. &quot;브런치&quot;의 &quot;&lt;a href=&quot;https://news.google.com/rss/articles/CBMiSEFVX3lxTFBCaFlldjdvX2g2U3JlcG14VWJlTElSeDI0aFVMTGRWS1NjQmdDdmk2bzdkcVBnWUZpUEpGUDl6Nk5mdEtwUTRhTg?oc=5&quot;&gt;react-window 를 이용해 성능 최적화하기&lt;/a&gt;&quot; 글에서도 이 라이브러리의 효과를 확인할 수 있다고 하네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript import React from 'react'; import { FixedSizeList } from 'react-window';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const Row = ({ index, style }) =&amp;gt; (&lt;/p&gt;
&lt;div&gt;Row {index}&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function MyListComponent() { return ( {Row} ); }&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;p data-ke-size=&quot;size16&quot;&gt;React 성능 최적화는 어쩌면 끝없는 여정일지도 몰라요. 하지만 사용자에게 더 나은 경험을 제공하기 위해 끊임없이 고민하고 개선해나가는 것이 개발자의 숙명이겠죠? 오늘 소개한 방법들 외에도 코드 스플리팅, 이미지 최적화 등 다양한 방법들이 있으니, 프로젝트 상황에 맞게 적용해보시길 추천드려요. &quot;요즘IT&quot;의 &quot;&lt;a href=&quot;https://news.google.com/rss/articles/CBMiWkFVX3lxTE5qUTlEZEhDWEJvMlJGeXhlZE82cERqZVF1aTJaSnVkVTdKWFZaQms1WHlZWFQ5bmVCUkYzNWY3RUhYbG1EaG1lLVJYajFwbS1tRVJoREw2WEM0dw?oc=5&quot;&gt;내가 애니메이션 라이브러리를 만들면서 배운 것들&lt;/a&gt;&quot; 글을 보면, 직접 라이브러리를 만들면서 얻은 성능 최적화 경험도 엿볼 수 있다고 하니 참고해보시는 것도 좋겠네요.&lt;/p&gt;</description>
      <category>개발/React</category>
      <category>React optimization</category>
      <category>React 성능 최적화</category>
      <category>react-window</category>
      <category>React.memo</category>
      <category>useCallback</category>
      <category>useMemo</category>
      <category>가상화 렌더링</category>
      <category>렌더링 최적화</category>
      <category>리액트 최적화</category>
      <category>프론트엔드 성능</category>
      <author>hanks</author>
      <guid isPermaLink="true">https://hanks-log.tistory.com/105</guid>
      <comments>https://hanks-log.tistory.com/105#entry105comment</comments>
      <pubDate>Thu, 26 Feb 2026 09:02:25 +0900</pubDate>
    </item>
    <item>
      <title>React Server Components (RSC) 정리</title>
      <link>https://hanks-log.tistory.com/104</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Server Components (RSC)에서 발견된 인증 없는 원격 코드 실행 취약점과 관련된 내용, 그리고 이 취약점을 악용한 봇넷 캠페인 확산 소식을 알아보고, 개발자가 주의해야 할 점들을 살펴봅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;React Server Components (RSC)란 무엇일까요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Server Components(RSC)는 React 애플리케이션 개발 방식을 혁신적으로 변화시키는 기술인데요. 기존 React 컴포넌트가 클라이언트 사이드에서 렌더링되는 것과 달리, RSC는 서버에서 렌더링됩니다. 이를 통해 초기 로딩 속도를 개선하고, 클라이언트 측 JavaScript 번들 크기를 줄여 사용자 경험을 향상시킬 수 있다고 알려져 있어요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RSC 취약점: 인증 없는 원격 코드 실행 (RCE)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 문제 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 React와 Next.js의 RSC에서 인증 없는 원격 코드 실행 취약점이 발견되었다는 소식이 들려왔어요. &lt;a href=&quot;https://news.google.com/rss/articles/CBMibEFVX3lxTFBNTTdmSnZxSDdXWHhqMEZZSjB0a0hmTTBXQjZhMk55U2V2anY1UDRYb0tDWlhvVUxKS3VXMHNiankwVURQRGx0M0hOajhSLU9fbkVkal9wZDZmUzhTb2NiS3oxXy1WNjhWWnpNeg?oc=5&quot;&gt;데일리시큐 보도&lt;/a&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;RSC의 특성상 서버에서 렌더링이 이루어지기 때문에, 클라이언트에서 전송된 악성 코드가 서버에서 실행될 위험이 있습니다. 특히, &lt;b&gt;인증 절차가 미흡한 경우&lt;/b&gt;, 공격자가 서버에 임의의 코드를 주입하여 실행할 수 있게 되는 것이죠.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ 해결 방안&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;즉시 최신 버전으로 패치&lt;/b&gt;: 가장 중요하고 기본적인 해결책은 React와 Next.js를 최신 버전으로 업데이트하는 것입니다. 개발팀은 이미 해당 취약점에 대한 패치를 배포했으니, 즉시 적용해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;엄격한 입력 유효성 검사&lt;/b&gt;: 클라이언트로부터 전달되는 모든 입력에 대해 엄격한 유효성 검사를 수행해야 합니다. 특히, 서버에서 실행될 가능성이 있는 데이터는 더욱 주의해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최소 권한 원칙&lt;/b&gt;: 서버 컴포넌트가 접근할 수 있는 리소스에 대한 권한을 최소화해야 합니다. 불필요한 권한은 제거하여 공격 표면을 줄이는 것이 중요합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정기적인 보안 점검&lt;/b&gt;: 코드의 취약점을 주기적으로 점검하고, 필요한 보안 조치를 취해야 합니다. 자동화된 보안 도구를 활용하는 것도 좋은 방법입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;React2Shell 취약점 악용한 RondoDox 봇넷 캠페인 확산&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① RondoDox 봇넷이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://news.google.com/rss/articles/CBMicEFVX3lxTFBhUy05RWVNVEU0RjNFYy01TVBKTVV4eFlnOWRxSkhFVDg1QUFSQzN2WlpjcF9Mb3NJVHREdjh2TFlNT1E3WGtTYmt2M0E2Q20waTBHRlFrR09FZkhFbF9zZ25VdUpUUWF1UzFsM3FWU2I?oc=5&quot;&gt;전국뉴스 보도&lt;/a&gt;에 따르면, React2Shell 취약점을 악용한 RondoDox 봇넷 캠페인이 확산되고 있다고 합니다. RondoDox 봇넷은 취약한 시스템을 감염시켜 DDoS 공격, 데이터 유출 등 다양한 악성 활동에 이용하는 네트워크입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② 개발자의 주의 사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;보안 업데이트의 중요성&lt;/b&gt;: React2Shell 취약점과 같은 알려진 취약점에 대한 보안 업데이트를 게을리하지 않아야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안 코딩 습관&lt;/b&gt;: 개발 단계에서부터 보안을 고려한 코딩 습관을 들여야 합니다. 특히, 외부 입력에 대한 검증을 철저히 해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안 도구 활용&lt;/b&gt;: 정적 분석 도구, 동적 분석 도구 등 다양한 보안 도구를 활용하여 코드의 취약점을 사전에 발견하고 수정해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Server Components는 프론트엔드 개발에 혁신을 가져다줄 수 있는 매력적인 기술이지만, 새로운 기술인 만큼 보안에 대한 각별한 주의가 필요합니다. 이번에 발생한 취약점 사례를 통해 보안 업데이트의 중요성을 다시 한번 깨닫게 되었는데요. 앞으로도 꾸준한 관심과 노력을 통해 안전한 React 애플리케이션을 개발해야겠습니다.&lt;/p&gt;</description>
      <category>개발/React</category>
      <category>Next.js</category>
      <category>react</category>
      <category>react server components</category>
      <category>RSC</category>
      <category>server-side rendering</category>
      <category>개발 생산성</category>
      <category>리액트 서버 컴포넌트</category>
      <category>보안 강화</category>
      <category>성능 개선</category>
      <category>프론트엔드 개발</category>
      <author>hanks</author>
      <guid isPermaLink="true">https://hanks-log.tistory.com/104</guid>
      <comments>https://hanks-log.tistory.com/104#entry104comment</comments>
      <pubDate>Wed, 25 Feb 2026 11:53:59 +0900</pubDate>
    </item>
    <item>
      <title>React useState vs useReducer 비교</title>
      <link>https://hanks-log.tistory.com/103</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 상태 관리를 위한 두 가지 주요 Hook인 &lt;code&gt;useState&lt;/code&gt;와 &lt;code&gt;useReducer&lt;/code&gt;를 비교하고, 어떤 상황에서 어떤 Hook을 사용하는 것이 더 효과적인지 알아봅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 상태 관리가 중요할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 애플리케이션에서 &lt;b&gt;상태 관리&lt;/b&gt;는 핵심적인 부분인데요. 사용자 인터페이스는 상태 변화에 따라 끊임없이 업데이트되기 때문이죠. React는 컴포넌트의 상태를 관리하기 위한 다양한 방법을 제공하는데, 그중 가장 기본적이면서도 중요한 두 가지가 바로 &lt;code&gt;useState&lt;/code&gt;와 &lt;code&gt;useReducer&lt;/code&gt;입니다. 간단한 상태 관리는 &lt;code&gt;useState&lt;/code&gt;로 충분하지만, 복잡한 상태 로직이나 여러 하위 값들을 관리해야 할 때는 &lt;code&gt;useReducer&lt;/code&gt;가 더 나은 선택이 될 수 있어요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① useState: 간단한 상태 관리를 위한 Hook&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useState&lt;/code&gt;는 가장 기본적인 상태 관리 Hook인데요. 컴포넌트 내에서 상태를 선언하고 업데이트하는 데 사용됩니다. 간단한 숫자, 문자열, 불리언 값 등을 관리할 때 유용하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript import React, { useState } from 'react';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function Counter() { const [count, setCount] = useState(0);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return (&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Count: {count}&lt;/p&gt;
&amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;Increment&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export default Counter;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서 &lt;code&gt;useState(0)&lt;/code&gt;은 초기 상태를 0으로 설정하고, &lt;code&gt;count&lt;/code&gt;라는 상태 변수와 &lt;code&gt;setCount&lt;/code&gt;라는 상태 업데이트 함수를 반환합니다. &lt;code&gt;setCount&lt;/code&gt; 함수를 사용하여 &lt;code&gt;count&lt;/code&gt; 값을 업데이트하면 컴포넌트가 다시 렌더링됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② useReducer: 복잡한 상태 관리를 위한 Hook&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useReducer&lt;/code&gt;는 &lt;code&gt;useState&lt;/code&gt;보다 더 복잡한 상태 로직을 관리하는 데 적합한 Hook인데요. Reducer 함수를 사용하여 상태 업데이트 로직을 분리하고, 액션 객체를 통해 상태 변화를 디스패치합니다. 여러 하위 값들을 가진 객체 상태나, 상태 업데이트 로직이 복잡한 경우에 &lt;code&gt;useReducer&lt;/code&gt;가 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript import React, { useReducer } from 'react';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const initialState = { count: 0 };&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function Counter() { const [state, dispatch] = useReducer(reducer, initialState);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return (&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Count: {state.count}&lt;/p&gt;
&amp;lt;button onClick={() =&amp;gt; dispatch({ type: 'increment' })}&amp;gt;Increment &amp;lt;button onClick={() =&amp;gt; dispatch({ type: 'decrement' })}&amp;gt;Decrement&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export default Counter;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서 &lt;code&gt;useReducer(reducer, initialState)&lt;/code&gt;는 &lt;code&gt;reducer&lt;/code&gt; 함수와 초기 상태를 사용하여 상태를 관리합니다. &lt;code&gt;dispatch&lt;/code&gt; 함수를 사용하여 액션 객체를 디스패치하면, &lt;code&gt;reducer&lt;/code&gt; 함수가 현재 상태와 액션 객체를 기반으로 새로운 상태를 반환합니다.&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;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;useState&lt;/th&gt;
&lt;th&gt;useReducer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&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&gt;
&lt;td&gt;상태 업데이트 로직&lt;/td&gt;
&lt;td&gt;직접 상태 업데이트 함수 사용&lt;/td&gt;
&lt;td&gt;Reducer 함수를 통해 업데이트&lt;/td&gt;
&lt;/tr&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&gt;
&lt;td&gt;코드 양&lt;/td&gt;
&lt;td&gt;적음&lt;/td&gt;
&lt;td&gt;많음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예측 가능성&lt;/td&gt;
&lt;td&gt;낮음 (상태 업데이트 로직이 분산될 수 있음)&lt;/td&gt;
&lt;td&gt;높음 (Reducer 함수로 상태 업데이트 로직 집중)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제 무엇을 선택해야 할까?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① useState를 선택하는 경우&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태가 단순하고, 상태 업데이트 로직이 간단할 때&lt;/li&gt;
&lt;li&gt;컴포넌트 내에서 관리해야 할 상태 값이 적을 때&lt;/li&gt;
&lt;li&gt;빠른 개발 속도가 중요할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② useReducer를 선택하는 경우&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태가 복잡하고, 상태 업데이트 로직이 여러 단계로 나뉠 때&lt;/li&gt;
&lt;li&gt;여러 하위 값들을 가진 객체 상태를 관리해야 할 때&lt;/li&gt;
&lt;li&gt;상태 변화를 예측 가능하게 관리하고 싶을 때&lt;/li&gt;
&lt;li&gt;Redux와 유사한 패턴을 사용하고 싶을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useState&lt;/code&gt;와 &lt;code&gt;useReducer&lt;/code&gt;는 React 애플리케이션에서 상태를 관리하는 강력한 도구인데요. 어떤 Hook을 선택할지는 애플리케이션의 복잡도와 요구 사항에 따라 달라집니다. 간단한 상태 관리는 &lt;code&gt;useState&lt;/code&gt;로, 복잡한 상태 관리는 &lt;code&gt;useReducer&lt;/code&gt;로 해결할 수 있다는 점을 기억하고, 상황에 맞는 Hook을 선택하여 효율적인 상태 관리를 해보세요. 두 Hook을 적절히 활용하면 더욱 강력하고 유지보수하기 쉬운 React 애플리케이션을 개발할 수 있을 거예요.&lt;/p&gt;</description>
      <category>개발/React</category>
      <category>react</category>
      <category>react hook</category>
      <category>React useReducer</category>
      <category>react useState</category>
      <category>React 상태 업데이트</category>
      <category>useReducer 사용법</category>
      <category>useState vs useReducer</category>
      <category>useState 사용법</category>
      <category>리액트</category>
      <category>상태 관리</category>
      <author>hanks</author>
      <guid isPermaLink="true">https://hanks-log.tistory.com/103</guid>
      <comments>https://hanks-log.tistory.com/103#entry103comment</comments>
      <pubDate>Wed, 25 Feb 2026 11:11:46 +0900</pubDate>
    </item>
    <item>
      <title>React useEffect 완벽 가이드</title>
      <link>https://hanks-log.tistory.com/102</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React useEffect 훅을 사용하여 컴포넌트가 렌더링된 후 side effect를 관리하는 방법을 알아봅니다. 의존성 배열을 활용한 최적화, cleanup 함수를 통한 메모리 누수 방지 등 &lt;code&gt;useEffect&lt;/code&gt;를 완벽하게 사용하는 방법을 살펴봅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 useEffect가 필요할까요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 컴포넌트는 기본적으로 UI를 렌더링하는 역할을 합니다. 하지만 때로는 컴포넌트가 렌더링된 &lt;i&gt;후&lt;/i&gt;에 특정 작업을 수행해야 할 때가 있습니다. 이러한 작업을 &quot;side effect&quot;라고 부르는데요. 예를 들어, API를 호출해서 데이터를 가져오거나, DOM을 직접 조작하거나, 타이머를 설정하는 것들이 side effect에 해당합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 문제 상황: 컴포넌트 렌더링 후 데이터 가져오기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 컴포넌트가 렌더링될 때 API를 호출해서 데이터를 가져와야 한다고 가정해봅시다. 단순히 컴포넌트 내에서 &lt;code&gt;fetch&lt;/code&gt; 함수를 호출하면, 컴포넌트가 렌더링될 때마다 API가 계속 호출될 수 있습니다. 이는 불필요한 네트워크 요청을 발생시키고, 성능 저하를 야기할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript import React, { useState } from 'react';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function MyComponent() { const [data, setData] = useState(null);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;// 렌더링될 때마다 API 호출 (문제 발생 가능성) fetch('/api/data') .then(response =&amp;gt; response.json()) .then(data =&amp;gt; setData(data));&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if (!data) { return&lt;/p&gt;
&lt;div&gt;Loading...&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return&lt;/p&gt;
&lt;div&gt;{data.name}&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export default MyComponent;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useEffect 핵심 개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt; 훅은 이러한 side effect를 안전하고 효율적으로 관리할 수 있도록 도와줍니다. &lt;code&gt;useEffect&lt;/code&gt;를 사용하면 컴포넌트가 렌더링된 후에 특정 함수를 실행할 수 있으며, 의존성 배열을 통해 함수의 실행 시점을 제어할 수 있습니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;콜백 함수&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useEffect&lt;/code&gt; 훅에 전달되는 함수로, side effect를 수행하는 코드를 포함합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;의존성 배열&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useEffect&lt;/code&gt; 훅의 두 번째 인자로 전달되는 배열입니다. 이 배열에 포함된 값이 변경될 때마다 콜백 함수가 실행됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cleanup 함수&lt;/td&gt;
&lt;td&gt;콜백 함수 내에서 반환되는 함수입니다. 컴포넌트가 언마운트되거나, &lt;code&gt;useEffect&lt;/code&gt;가 다시 실행되기 전에 호출되어 리소스를 정리하는 데 사용됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[컴포넌트 렌더링] --&amp;gt; [useEffect 실행] --&amp;gt; [API 호출, DOM 조작 등]&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useEffect 단계별 사용법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 1단계: 기본 사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 사용법은 &lt;code&gt;useEffect&lt;/code&gt; 훅에 콜백 함수를 전달하는 것입니다. 이 경우, 컴포넌트가 처음 렌더링될 때와 업데이트될 때마다 콜백 함수가 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript import React, { useEffect } from 'react';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function MyComponent() { useEffect(() =&amp;gt; { console.log('컴포넌트가 렌더링되었습니다.'); });&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return&lt;/p&gt;
&lt;div&gt;Hello, World!&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export default MyComponent;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② 2단계: 의존성 배열 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 배열을 사용하면 &lt;code&gt;useEffect&lt;/code&gt; 훅이 실행되는 시점을 제어할 수 있습니다. 의존성 배열에 특정 값을 포함시키면, 해당 값이 변경될 때만 콜백 함수가 실행됩니다. 빈 배열 (&lt;code&gt;[]&lt;/code&gt;)을 전달하면, 컴포넌트가 처음 렌더링될 때만 콜백 함수가 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript import React, { useState, useEffect } from 'react';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function MyComponent() { const [count, setCount] = useState(0);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect(() =&amp;gt; { console.log('count 값이 변경되었습니다:', count); }, [count]); // count 값이 변경될 때만 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return (&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Count: {count}&lt;/p&gt;
&amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;Increment&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export default MyComponent;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ 3단계: Cleanup 함수 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cleanup 함수는 컴포넌트가 언마운트되거나, &lt;code&gt;useEffect&lt;/code&gt;가 다시 실행되기 전에 호출되어 리소스를 정리하는 데 사용됩니다. 예를 들어, 타이머를 설정한 경우 Cleanup 함수에서 타이머를 제거해야 합니다. 이를 통해 메모리 누수를 방지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript import React, { useState, useEffect } from 'react';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function MyComponent() { const [count, setCount] = useState(0);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect(() =&amp;gt; { const intervalId = setInterval(() =&amp;gt; { setCount(prevCount =&amp;gt; prevCount + 1); }, 1000);&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Cleanup 함수
return () =&amp;gt; {
  clearInterval(intervalId);
  console.log('타이머가 제거되었습니다.');
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}, []); // 컴포넌트가 처음 렌더링될 때만 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return (&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Count: {count}&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;); }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export default MyComponent;&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;h3 data-ke-size=&quot;size23&quot;&gt;Q: useEffect 안에서 async 함수를 직접 사용할 수 있나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A:&lt;/b&gt; &lt;code&gt;useEffect&lt;/code&gt; 훅에 전달되는 콜백 함수는 동기 함수여야 합니다. 따라서 &lt;code&gt;async&lt;/code&gt; 함수를 직접 사용할 수 없습니다. 대신, 콜백 함수 내에서 &lt;code&gt;async&lt;/code&gt; 함수를 호출하는 방법을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript import React, { useState, useEffect } from 'react';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function MyComponent() { const [data, setData] = useState(null);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect(() =&amp;gt; { async function fetchData() { const response = await fetch('/api/data'); const data = await response.json(); setData(data); }&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;fetchData();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}, []);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if (!data) { return&lt;/p&gt;
&lt;div&gt;Loading...&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return&lt;/p&gt;
&lt;div&gt;{data.name}&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export default MyComponent;&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;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt; 훅은 React 컴포넌트에서 side effect를 관리하는 데 필수적인 도구입니다. 의존성 배열과 Cleanup 함수를 적절히 활용하면, 효율적이고 안전하게 side effect를 처리할 수 있습니다. &lt;code&gt;useEffect&lt;/code&gt;를 제대로 이해하고 사용하면 React 개발 실력을 한 단계 더 끌어올릴 수 있을 거예요. 저도 처음에는 헷갈렸는데, 여러 번 사용해보니 감이 오더라고요. 여러분도 꾸준히 연습해서 &lt;code&gt;useEffect&lt;/code&gt; 마스터가 되시길 바랍니다!&lt;/p&gt;</description>
      <category>개발/React</category>
      <category>React cleanup 함수</category>
      <category>react hook</category>
      <category>React side effect</category>
      <category>react useEffect</category>
      <category>React 데이터 fetching</category>
      <category>React 렌더링</category>
      <category>react 훅</category>
      <category>useEffect 사용법</category>
      <category>useState</category>
      <category>의존성 배열</category>
      <author>hanks</author>
      <guid isPermaLink="true">https://hanks-log.tistory.com/102</guid>
      <comments>https://hanks-log.tistory.com/102#entry102comment</comments>
      <pubDate>Wed, 25 Feb 2026 10:26:06 +0900</pubDate>
    </item>
    <item>
      <title>Spring Cloud Gateway 설정 파헤치기</title>
      <link>https://hanks-log.tistory.com/101</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Cloud Gateway를 사용해서 API Gateway를 구축하고, 트래픽 관리, 인증/인가, 보안 등의 기능을 설정하는 방법을 알아봅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Cloud Gateway, 왜 써야 할까요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Cloud Gateway는 Spring 기반의 API Gateway 역할을 하는 프로젝트인데요. MSA(Microservices Architecture) 환경에서 빛을 발하는 친구입니다. MSA 환경에서는 여러 개의 작은 서비스들이 모여 하나의 애플리케이션을 구성하게 되는데, 이때 각 서비스로 들어오는 트래픽을 관리하고, 인증/인가를 처리하고, 보안을 강화하는 등의 역할을 API Gateway가 담당하게 되거든요. Spring Cloud Gateway는 바로 이런 역할을 아주 효과적으로 수행할 수 있도록 도와줍니다. 삼성SDS 인사이트리포트에 따르면, Spring Cloud를 활용하면 클라우드 네이티브 애플리케이션 개발에 유용하다고 하네요.&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;p data-ke-size=&quot;size16&quot;&gt;Spring Cloud Gateway를 제대로 사용하려면 몇 가지 핵심 개념을 알아두는 게 좋아요.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Route&lt;/td&gt;
&lt;td&gt;특정 조건에 맞는 요청을 특정 서비스로 연결하는 규칙&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/user/**&lt;/code&gt; 패턴의 요청을 &lt;code&gt;user-service&lt;/code&gt;로 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Predicate&lt;/td&gt;
&lt;td&gt;Route가 적용될 조건을 정의&lt;/td&gt;
&lt;td&gt;Path, Header, Query Parameter 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter&lt;/td&gt;
&lt;td&gt;요청/응답을 가로채서 수정하거나 추가 기능을 수행&lt;/td&gt;
&lt;td&gt;인증/인가, 로깅, 트래픽 제한 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Cloud Gateway 설정 따라하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 의존성 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;code&gt;pom.xml&lt;/code&gt; (Maven) 또는 &lt;code&gt;build.gradle&lt;/code&gt; (Gradle)에 Spring Cloud Gateway 의존성을 추가해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Maven (pom.xml):&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xml org.springframework.cloudspring-cloud-starter-gateway&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gradle (build.gradle):&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradle dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-gateway' }&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② Route 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;application.yml&lt;/code&gt; 또는 &lt;code&gt;application.properties&lt;/code&gt; 파일에 Route를 설정합니다. 여기서는 간단하게 &lt;code&gt;/api/user/**&lt;/code&gt; 패턴의 요청을 &lt;code&gt;user-service&lt;/code&gt;로 연결하는 설정을 해볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;application.yml:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yaml spring: cloud: gateway: routes: - id: user-service-route uri: lb://user-service # LoadBalancer를 통해 user-service로 연결 predicates: - Path=/api/user/**&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;uri: lb://user-service&lt;/code&gt;는 LoadBalancer Client를 이용해서 &lt;code&gt;user-service&lt;/code&gt;라는 이름의 서비스로 요청을 전달한다는 의미입니다. &lt;code&gt;user-service&lt;/code&gt;는 Eureka 같은 서비스 디스커버리에 등록된 서비스 이름이겠죠.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ Filter 설정 (인증/인가)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API Gateway의 중요한 역할 중 하나는 인증/인가 처리입니다. Spring Cloud Gateway는 다양한 Filter를 제공해서 인증/인가를 쉽게 구현할 수 있도록 도와줍니다. 예를 들어, JWT (JSON Web Token)를 사용해서 인증을 처리하는 Filter를 추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java @Configuration public class GatewayConfig {&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route(&quot;user-service-route&quot;, r -&amp;gt; r.path(&quot;/api/user/**&quot;)
                    .filters(f -&amp;gt; f.filter(new JwtAuthenticationFilter())) // JWT 인증 필터 추가
                    .uri(&quot;lb://user-service&quot;))
            .build();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;JwtAuthenticationFilter&lt;/code&gt;는 직접 구현해야 하는 Filter인데요, 요청 헤더에서 JWT를 추출해서 검증하고, 유효하지 않은 토큰인 경우 에러를 반환하는 로직을 담고 있어야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;④ Rate Limiter 설정 (트래픽 제한)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과도한 트래픽으로 인해 서비스가 다운되는 것을 방지하기 위해 Rate Limiter를 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;application.yml:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yaml spring: cloud: gateway: routes: - id: user-service-route uri: lb://user-service predicates: - Path=/api/user/** filters: - RequestRateLimiter=redis-rate-limiter # Redis 기반 Rate Limiter 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rate Limiter를 사용하려면 Redis 설정이 필요하고, 관련 의존성도 추가해야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Cloud Gateway, 써보니 어때요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Cloud Gateway를 사용해보니, MSA 환경에서 API Gateway를 구축하는 게 정말 편리해지더라고요. Route 설정이나 Filter 추가도 간단하고, 트래픽 관리나 보안 기능도 쉽게 적용할 수 있어서 좋았습니다. 특히, Spring 생태계와의 연동이 잘 되어 있어서 Spring Boot 기반의 애플리케이션과 함께 사용하기에 아주 적합하다는 생각이 들었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 처음에는 설정이 조금 복잡하게 느껴질 수도 있지만, Spring Cloud Gateway의 개념과 동작 방식을 이해하고 나면 금방 익숙해질 수 있을 거예요. 앞으로 MSA 환경에서 API Gateway를 구축해야 한다면 Spring Cloud Gateway를 적극적으로 활용해볼 생각입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://news.google.com/rss/articles/CBMiZ0FVX3lxTFBUQjBQNnJoV3ozTHNpa0Jqd3BVZHJrWlRVV05RUVF6Nl9kRVZJU3NPMGxlTHNPdDAwal8tYS1kMWNTV2NaV0RNOVVuUk5fWWhvbHV0RllNSjBUR0prUHhxQXh3ZWdsaEk?oc=5&quot;&gt;스프링 클라우드(Spring Cloud)를 활용한 클라우드 네이티브 애플리케이션 개발 | 인사이트리포트 | 삼성SDS&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>API Gateway</category>
      <category>application.yml</category>
      <category>gateway</category>
      <category>MSA</category>
      <category>spring cloud</category>
      <category>Spring Cloud Gateway</category>
      <category>Spring4Shell</category>
      <category>라우팅</category>
      <category>마이크로서비스</category>
      <category>필터</category>
      <author>hanks</author>
      <guid isPermaLink="true">https://hanks-log.tistory.com/101</guid>
      <comments>https://hanks-log.tistory.com/101#entry101comment</comments>
      <pubDate>Wed, 25 Feb 2026 09:45:29 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot Actuator 모니터링 알아보기</title>
      <link>https://hanks-log.tistory.com/100</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 클러스터 환경에서 MGET 명령이 어떻게 처리되는지 알아보고, 성능 최적화를 위한 고려 사항을 살펴봅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis 클러스터와 MGET 명령&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis는 싱글 스레드 기반의 빠른 인메모리 데이터 저장소인데요, 대용량 데이터를 처리하기 위해 클러스터 모드를 지원합니다. 클러스터 모드에서는 데이터가 여러 노드에 분산 저장되기 때문에, 단일 명령으로 여러 키에 접근하는 MGET 명령의 동작 방식이 중요해집니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① MGET 명령이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MGET 명령은 Redis에서 &lt;b&gt;여러 키에 대한 값을 한 번에 가져오는 명령&lt;/b&gt;입니다. 예를 들어, &lt;code&gt;MGET key1 key2 key3&lt;/code&gt; 와 같이 사용하면 &lt;code&gt;key1&lt;/code&gt;, &lt;code&gt;key2&lt;/code&gt;, &lt;code&gt;key3&lt;/code&gt;에 저장된 값들을 한 번의 요청으로 받을 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② Redis 클러스터 환경에서의 MGET&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 클러스터 환경에서는 데이터가 여러 샤드(Shard) 또는 노드에 분산되어 저장됩니다. 따라서 MGET 명령을 실행할 때, 해당 키들이 모두 같은 노드에 존재하지 않을 수 있습니다. 이 경우 Redis는 어떻게 MGET 명령을 처리할까요?&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MGET 명령의 동작 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 클러스터에서 MGET 명령은 다음과 같은 방식으로 동작한다고 알려져 있어요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 키 해싱 및 노드 결정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis는 각 키를 해싱하여 해당 키가 어느 노드에 속하는지 결정합니다. Redis 클러스터는 **해시 슬롯(Hash Slot)**이라는 개념을 사용하는데, 16384개의 해시 슬롯이 있고, 각 키는 해시 함수를 통해 특정 해시 슬롯에 매핑됩니다. 그리고 각 노드는 하나 이상의 해시 슬롯을 담당합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② 요청 분산 및 병합&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MGET 명령에 포함된 키들이 여러 노드에 분산되어 있다면, Redis는 각 노드에 해당 키에 대한 요청을 &lt;b&gt;개별적으로 전송&lt;/b&gt;합니다. 각 노드는 자신의 담당하는 키에 대한 값을 반환하고, Redis는 이 응답들을 &lt;b&gt;병합하여 클라이언트에게 반환&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[클라이언트] ── MGET key1 key2 key3 ──&amp;rarr; [Redis 클러스터] │ │ │ key1, key2는 Node A, key3는 Node B에 존재 │ │ │ ├── 요청(key1, key2) ──&amp;rarr; [Node A] │ │ ├── 요청(key3) ──&amp;rarr; [Node B] │ │ └── 응답(value1, value2, value3) ──&amp;larr; [Redis 클러스터]&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ 성능 고려 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MGET 명령이 여러 노드에 걸쳐 실행될 경우, 네트워크 왕복 횟수가 증가하여 성능에 영향을 미칠 수 있습니다. 따라서 다음과 같은 사항을 고려해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;키 분산 전략&lt;/b&gt;: 관련 있는 키들을 가능한 한 같은 노드에 저장하도록 설계하면 MGET 명령의 효율성을 높일 수 있습니다. 해시태그 기능을 활용하여 특정 패턴을 가진 키들이 동일한 해시 슬롯에 들어가도록 유도할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파이프라인&lt;/b&gt;: 여러 MGET 명령을 한 번에 보내는 파이프라인 기법을 사용하여 네트워크 오버헤드를 줄일 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 구조 최적화&lt;/b&gt;: MGET 대신 Hash 데이터 타입 등을 활용하여 한 번의 요청으로 필요한 데이터를 가져올 수 있도록 데이터 구조를 설계하는 것이 좋습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MGET 사용 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 Redis 클라이언트 (예: redis-cli)에서 MGET 명령을 사용하는 간단한 예시입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redis-cli -c -h -p MGET user:1:name user:1:age user:2:name&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;-c&lt;/code&gt; 옵션은 클러스터 모드를 활성화하는 옵션입니다. &lt;code&gt;&amp;lt;host&amp;gt;&lt;/code&gt;와 &lt;code&gt;&amp;lt;port&amp;gt;&lt;/code&gt;는 Redis 클러스터의 노드 주소와 포트 번호로 변경해야 합니다.&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;p data-ke-size=&quot;size16&quot;&gt;Redis 클러스터 환경에서 MGET 명령은 여러 키에 대한 값을 효율적으로 가져오는 데 유용한 명령이지만, 키가 여러 노드에 분산되어 있을 경우 성능 저하가 발생할 수 있다는 점을 고려해야 합니다. 키 분산 전략, 파이프라인, 데이터 구조 최적화 등의 방법을 통해 MGET 명령의 성능을 향상시킬 수 있습니다. Redis를 사용하면서 이런 부분을 고려하면 좀 더 효율적인 시스템을 구축할 수 있을 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://news.google.com/rss/articles/CBMiTEFVX3lxTE1ZSFhNVnBZd2VFUTVNWmF6WTRPeThyQ2hxbjFiUkVCZjJua3liVzZQT1RtaEFsWWhlUmktVkFnWUd4eHBfdVFGbDN4NHU?oc=5&quot;&gt;레디스 클러스터 Mget 명령은 어떻게 동작하는가? - 브런치&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>actuator</category>
      <category>Actuator 모니터링</category>
      <category>Health Check</category>
      <category>metrics</category>
      <category>Spring Boot</category>
      <category>Spring Boot Actuator</category>
      <category>Spring Boot 모니터링</category>
      <category>메트릭</category>
      <category>스프링 부트 Actuator</category>
      <category>헬스 체크</category>
      <author>hanks</author>
      <guid isPermaLink="true">https://hanks-log.tistory.com/100</guid>
      <comments>https://hanks-log.tistory.com/100#entry100comment</comments>
      <pubDate>Wed, 25 Feb 2026 09:02:12 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot JWT 인증 시스템 구현 계획</title>
      <link>https://hanks-log.tistory.com/99</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 문서는 Spring Boot 기반 애플리케이션에 JWT(JSON Web Token) 인증 시스템을 구축하기 위한 기술 구현 계획을 담고 있다. 현재 애플리케이션은 인증 기능이 부재하여 보안에 취약하며, 사용자별 권한 관리가 어렵다. JWT 인증 시스템 도입을 통해 다음과 같은 목표를 달성하고자 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;보안 강화:&lt;/b&gt; JWT를 사용하여 클라이언트 요청의 유효성을 검증하고, 무단 접근을 방지한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성:&lt;/b&gt; stateless한 구조를 통해 서버 확장이 용이하도록 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지보수성:&lt;/b&gt; 표준화된 인증 방식을 적용하여 코드의 가독성을 높이고 유지보수를 용이하게 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 경험 향상:&lt;/b&gt; 세션 기반 인증 방식 대비 성능 향상을 통해 사용자 경험을 개선한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 계획은 개발팀 내부 공유를 목적으로 하며, 실제 구현 과정에서 변경될 수 있다.&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;p data-ke-size=&quot;size16&quot;&gt;다음은 JWT 인증 시스템의 예상 구조를 ASCII 다이어그램으로 표현한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[클라이언트] ──&amp;rarr; [API Gateway] ──&amp;rarr; [인증 서버] ──&amp;rarr; [사용자 DB] │ │ │ │ │ └──&amp;rarr; JWT 발급 │ │ │ └──&amp;rarr; JWT 저장 (Local Storage/Cookie) │ └──&amp;rarr; API 요청 (Authorization 헤더에 JWT 포함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[API Gateway] ──&amp;rarr; [리소스 서버] ──&amp;rarr; [DB/Cache] │ └──&amp;rarr; JWT 검증&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 인증 흐름은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트는 인증 서버에 사용자 이름과 비밀번호를 전송한다.&lt;/li&gt;
&lt;li&gt;인증 서버는 사용자 정보를 DB에서 조회하여 인증한다.&lt;/li&gt;
&lt;li&gt;인증 성공 시, 인증 서버는 JWT를 생성하여 클라이언트에게 반환한다.&lt;/li&gt;
&lt;li&gt;클라이언트는 JWT를 로컬 저장소(Local Storage, Cookie)에 저장한다.&lt;/li&gt;
&lt;li&gt;클라이언트는 API 요청 시 Authorization 헤더에 JWT를 포함하여 전송한다.&lt;/li&gt;
&lt;li&gt;API Gateway 또는 리소스 서버는 JWT를 검증하고, 유효한 경우 요청을 처리한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 설계&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 JWT 인증 시스템의 데이터 흐름을 나타내는 ASCII 흐름도이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[클라이언트] --&amp;gt; (1. 로그인 요청) --&amp;gt; [인증 서버] [인증 서버] --&amp;gt; (2. 사용자 인증) --&amp;gt; [사용자 DB] [사용자 DB] --&amp;gt; (3. 인증 결과) --&amp;gt; [인증 서버] [인증 서버] --&amp;gt; (4. JWT 발급) --&amp;gt; [클라이언트] [클라이언트] --&amp;gt; (5. JWT 저장) --&amp;gt; [로컬 저장소] [클라이언트] --&amp;gt; (6. API 요청 (JWT 포함)) --&amp;gt; [API Gateway/리소스 서버] [API Gateway/리소스 서버] --&amp;gt; (7. JWT 검증) --&amp;gt; [인증 서버 (선택적)] [API Gateway/리소스 서버] --&amp;gt; (8. API 처리) --&amp;gt; [DB/Cache]&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 생성 및 검증에 사용될 알고리즘은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서명 알고리즘:&lt;/b&gt; HS256 (HMAC SHA256)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;페이로드:&lt;/b&gt; 사용자 ID, 권한 정보, 발급 시간, 만료 시간&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;JWT 만료 시간은 application.properties 파일에서 설정 가능하도록 한다. 기본값은 1시간으로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java @Value(&quot;${jwt.expiration}&quot;) private long jwtExpiration; // milliseconds&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 생성 시 만료 시간을 계산하는 코드는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java Date now = new Date(); Date expiryDate = new Date(now.getTime() + jwtExpiration);&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;h3 data-ke-size=&quot;size23&quot;&gt;파일별/모듈별 변경 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 표는 구현에 필요한 파일 및 모듈별 변경 사항을 요약한 것이다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;모듈/파일&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;변경 사항&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pom.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maven 의존성 관리&lt;/td&gt;
&lt;td&gt;JWT 관련 라이브러리 추가 (jjwt)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;application.properties&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;애플리케이션 설정&lt;/td&gt;
&lt;td&gt;JWT secret key, expiration time 설정 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/main/java/com/example/security/JwtTokenProvider.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JWT 생성 및 검증 클래스&lt;/td&gt;
&lt;td&gt;JWT 생성, 검증, 파싱 로직 구현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/main/java/com/example/security/JwtAuthenticationFilter.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JWT 인증 필터&lt;/td&gt;
&lt;td&gt;API 요청 시 JWT 검증 및 인증 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/main/java/com/example/controller/AuthController.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;인증 컨트롤러&lt;/td&gt;
&lt;td&gt;로그인 API 엔드포인트 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/main/java/com/example/service/UserService.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;사용자 서비스&lt;/td&gt;
&lt;td&gt;사용자 인증 로직 구현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/main/java/com/example/config/SecurityConfig.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Spring Security 설정&lt;/td&gt;
&lt;td&gt;JWT 인증 필터 등록 및 API 접근 권한 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 예시&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JwtTokenProvider.java&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java import io.jsonwebtoken.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;import java.util.Date;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Component public class JwtTokenProvider {&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Value(&quot;${jwt.secret}&quot;)
private String jwtSecret;

@Value(&quot;${jwt.expiration}&quot;)
private long jwtExpiration;

public String generateToken(String userId) {
    Date now = new Date();
    Date expiryDate = new Date(now.getTime() + jwtExpiration);

    return Jwts.builder()
            .setSubject(userId)
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(SignatureAlgorithm.HS256, jwtSecret)
            .compact();
}

public String getUserIdFromJWT(String token) {
    Claims claims = Jwts.parser()
            .setSigningKey(jwtSecret)
            .parseClaimsJws(token)
            .getBody();

    return claims.getSubject();
}

public boolean validateToken(String token) {
    try {
        Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
        return true;
    } catch (SignatureException ex) {
        //Invalid JWT signature
    } catch (MalformedJwtException ex) {
        //Invalid JWT token
    } catch (ExpiredJwtException ex) {
        //Expired JWT token
    } catch (UnsupportedJwtException ex) {
        //Unsupported JWT token
    } catch (IllegalArgumentException ex) {
        //JWT claims string is empty
    }
    return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JwtAuthenticationFilter.java&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public class JwtAuthenticationFilter extends OncePerRequestFilter {&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Autowired
private JwtTokenProvider tokenProvider;

@Autowired
private UserDetailsService customUserDetailsService;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
        String jwt = getJwtFromRequest(request);

        if (StringUtils.hasText(jwt) &amp;amp;&amp;amp; tokenProvider.validateToken(jwt)) {
            String userId = tokenProvider.getUserIdFromJWT(jwt);

            UserDetails userDetails = customUserDetailsService.loadUserByUsername(userId);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
    } catch (Exception ex) {
        //log.error(&quot;Could not set user authentication in security context&quot;, ex);
    }

    filterChain.doFilter(request, response);
}

private String getJwtFromRequest(HttpServletRequest request) {
    String bearerToken = request.getHeader(&quot;Authorization&quot;);
    if (StringUtils.hasText(bearerToken) &amp;amp;&amp;amp; bearerToken.startsWith(&quot;Bearer &quot;)) {
        return bearerToken.substring(7, bearerToken.length());
    }
    return null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SecurityConfig.java&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity( securedEnabled = true, jsr250Enabled = true, prePostEnabled = true ) public class SecurityConfig extends WebSecurityConfigurerAdapter {&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Autowired
private CustomUserDetailsService customUserDetailsService;

@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;

@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
    return new JwtAuthenticationFilter();
}

@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
    authenticationManagerBuilder
            .userDetailsService(customUserDetailsService)
            .passwordEncoder(passwordEncoder());
}

@Bean(BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .cors()
                .and()
            .csrf()
                .disable()
            .exceptionHandling()
                .authenticationEntryPoint(unauthorizedHandler)
                .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
            .authorizeRequests()
                .antMatchers(&quot;/&quot;,
                    &quot;/favicon.ico&quot;,
                    &quot;/**/*.png&quot;,
                    &quot;/**/*.gif&quot;,
                    &quot;/**/*.svg&quot;,
                    &quot;/**/*.jpg&quot;,
                    &quot;/**/*.html&quot;,
                    &quot;/**/*.css&quot;,
                    &quot;/**/*.js&quot;)
                    .permitAll()
                .antMatchers(&quot;/api/auth/**&quot;)
                    .permitAll()
                .antMatchers(&quot;/api/user/checkUsernameAvailability&quot;, &quot;/api/user/checkEmailAvailability&quot;)
                    .permitAll()
                .anyRequest()
                    .authenticated();

    // Add our custom JWT security filter
    http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의: 위 코드는 예시이며, 실제 구현 시에는 예외 처리, 로깅, 보안 설정 등을 추가해야 한다. 특히 JWT secret key는 안전하게 관리해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;검증 항목&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 구현된 JWT 인증 시스템의 검증 항목이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 로그인/로그아웃 기능 정상 동작 확인&lt;/li&gt;
&lt;li&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; JWT 생성 및 검증 로직 정확성 검증&lt;/li&gt;
&lt;li&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 만료된 JWT로 API 접근 시 에러 처리 확인&lt;/li&gt;
&lt;li&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 사용자 권한에 따른 API 접근 제어 확인&lt;/li&gt;
&lt;li&gt;&lt;input checked=&quot;checked&quot; disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; JWT secret key 보안 설정 확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; CORS 설정 확인 (필요한 경우)&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; API Gateway 연동 테스트 (해당하는 경우)&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 부하 테스트를 통한 성능 검증 (선택적)&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; XSS/CSRF 공격 방어 대책 마련 (선택적)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/Spring</category>
      <category>API Gateway</category>
      <category>Authentication</category>
      <category>hs256</category>
      <category>JJWT</category>
      <category>jwt</category>
      <category>JWT 인증</category>
      <category>Spring Boot</category>
      <category>spring security</category>
      <category>보안</category>
      <category>인증 시스템</category>
      <author>hanks</author>
      <guid isPermaLink="true">https://hanks-log.tistory.com/99</guid>
      <comments>https://hanks-log.tistory.com/99#entry99comment</comments>
      <pubDate>Tue, 24 Feb 2026 13:24:04 +0900</pubDate>
    </item>
    <item>
      <title>Spring Data JPA QueryDSL 사용법</title>
      <link>https://hanks-log.tistory.com/98</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA와 QueryDSL을 함께 사용하여 복잡한 쿼리를 효율적으로 작성하는 방법을 알아봅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 Spring Data JPA와 QueryDSL을 함께 사용할까요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA는 개발자가 데이터 접근 계층을 쉽게 구현할 수 있도록 도와주는 강력한 프레임워크입니다. 하지만 복잡한 쿼리를 작성해야 할 때, Spring Data JPA의 기본 기능만으로는 한계가 있을 수 있습니다. 이때 QueryDSL을 함께 사용하면 &lt;b&gt;타입-세이프&lt;/b&gt;하고 &lt;b&gt;유지보수&lt;/b&gt;가 용이한 쿼리를 작성할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 복잡한 쿼리 작성의 어려움&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA는 간단한 CRUD 작업을 위한 기본적인 쿼리 메서드를 제공하지만, 복잡한 조건이나 조인이 필요한 쿼리를 작성하기에는 불편함이 있습니다. 예를 들어, 여러 테이블을 조인하고 특정 조건을 만족하는 데이터를 검색하는 쿼리를 작성하려면 복잡한 JPQL을 사용해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② JPQL의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPQL은 문자열 기반의 쿼리 언어이기 때문에 &lt;b&gt;컴파일 시점에 오류를 발견하기 어렵고&lt;/b&gt;, 오타나 문법 오류로 인해 런타임에 예외가 발생할 수 있습니다. 또한, JPQL은 복잡한 쿼리를 작성할수록 가독성이 떨어지고 유지보수가 어려워지는 단점이 있습니다.&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;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spring Data JPA&lt;/td&gt;
&lt;td&gt;JPA를 사용하여 데이터 접근 계층을 쉽게 구현할 수 있도록 도와주는 프레임워크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QueryDSL&lt;/td&gt;
&lt;td&gt;타입-세이프한 쿼리를 작성할 수 있도록 지원하는 프레임워크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Predicate&lt;/td&gt;
&lt;td&gt;QueryDSL에서 조건을 표현하는 인터페이스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BooleanExpression&lt;/td&gt;
&lt;td&gt;Predicate를 구현하는 클래스로, and, or 등의 메서드를 사용하여 조건을 조합할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[클라이언트] ── 쿼리 요청 ──&amp;rarr; [Spring Data JPA + QueryDSL] ── 쿼리 실행 ──&amp;rarr; [데이터베이스] └── 쿼리 생성 ────┘&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;h3 data-ke-size=&quot;size23&quot;&gt;① 1단계: QueryDSL 설정 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 프로젝트에 QueryDSL을 사용하기 위한 설정을 추가해야 합니다. Maven 또는 Gradle을 사용하여 QueryDSL 관련 의존성을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Maven 설정 (pom.xml):&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xml com.querydslquerydsl-jpa${querydsl.version}com.querydslquerydsl-apt${querydsl.version}provided&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gradle 설정 (build.gradle):&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradle dependencies { implementation 'com.querydsl:querydsl-jpa' annotationProcessor &quot;com.querydsl:querydsl-apt:${querydsl.version}:jpa&quot; implementation &quot;jakarta.annotation:jakarta.annotation-api&quot; implementation &quot;jakarta.persistence:jakarta.persistence-api&quot; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;def generatedSourcesDir = &quot;$projectDir/src/main/generated&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sourceSets { main.java.srcDirs += generatedSourcesDir }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tasks.withType(JavaCompile) { options.annotationProcessorPath = configurations.annotationProcessor }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clean { delete file(generatedSourcesDir) }&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② 2단계: QueryDSL Q 클래스 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QueryDSL은 엔티티 클래스를 기반으로 쿼리 작성을 위한 Q 클래스를 생성합니다. Q 클래스를 생성하려면 QueryDSL APT(Annotation Processing Tool)를 사용해야 합니다. 위에서 설정한 의존성을 통해 Q 클래스를 생성할 수 있습니다. 프로젝트를 빌드하면 &lt;code&gt;src/main/generated&lt;/code&gt; 폴더에 Q 클래스가 생성됩니다. (Gradle 설정 참고)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ 3단계: QueryDSL을 사용한 쿼리 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q 클래스를 사용하여 쿼리를 작성할 수 있습니다. Spring Data JPA Repository에서 &lt;code&gt;QuerydslPredicateExecutor&lt;/code&gt; 인터페이스를 상속받아 QueryDSL을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java import com.querydsl.core.types.Predicate; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.querydsl.QuerydslPredicateExecutor;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public interface MemberRepository extends JpaRepository&amp;lt;Member, Long&amp;gt;, QuerydslPredicateExecutor { }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Predicate; import com.example.entity.Member; import com.example.entity.QMember; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@SpringBootTest public class QuerydslTest {&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Autowired
private MemberRepository memberRepository;

@Test
public void querydslTest() {
    QMember member = QMember.member;
    BooleanBuilder builder = new BooleanBuilder();

    // 조건 추가 (예: 이름이 &quot;홍길동&quot;인 멤버 검색)
    builder.and(member.name.eq(&quot;홍길동&quot;));

    // 조건 추가 (예: 나이가 20세 이상인 멤버 검색)
    builder.and(member.age.goe(20));

    Predicate predicate = builder;
    Iterable&amp;lt;Member&amp;gt; results = memberRepository.findAll(predicate);

    results.forEach(System.out::println);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 &lt;code&gt;QMember.member&lt;/code&gt;는 Member 엔티티에 대한 Q 클래스의 인스턴스입니다. &lt;code&gt;builder&lt;/code&gt;를 사용하여 조건을 추가하고, &lt;code&gt;memberRepository.findAll(predicate)&lt;/code&gt; 메서드를 사용하여 쿼리를 실행합니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;Q: QueryDSL을 사용하면 어떤 장점이 있나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A:&lt;/b&gt; QueryDSL을 사용하면 타입-세이프한 쿼리를 작성할 수 있고, 컴파일 시점에 오류를 발견할 수 있습니다. 또한, 복잡한 쿼리를 쉽게 작성할 수 있으며, 쿼리의 가독성과 유지보수성을 향상시킬 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q: Spring Data JPA의 기본 기능만으로는 복잡한 쿼리를 작성할 수 없나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A:&lt;/b&gt; Spring Data JPA의 기본 기능만으로도 어느 정도 복잡한 쿼리를 작성할 수 있지만, JPQL을 사용해야 하므로 QueryDSL에 비해 불편함이 있을 수 있습니다.&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;p data-ke-size=&quot;size16&quot;&gt;Spring Data JPA와 QueryDSL을 함께 사용하면 복잡한 쿼리를 효율적으로 작성하고 관리할 수 있습니다. QueryDSL은 타입-세이프한 쿼리를 지원하여 개발 생산성을 높이고, 쿼리의 가독성을 향상시켜 유지보수를 용이하게 해줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; QueryDSL 의존성 추가&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; Q 클래스 생성 확인&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; QueryDSL을 사용한 쿼리 작성 및 테스트&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/Spring</category>
      <category>JPA</category>
      <category>QueryDSL</category>
      <category>QueryDslPredicateExecutor</category>
      <category>Q클래스</category>
      <category>Spring Boot</category>
      <category>Spring Data JPA</category>
      <category>Spring Data QueryDSL</category>
      <category>자바 쿼리</category>
      <category>쿼리 DSL</category>
      <category>타입세이프 쿼리</category>
      <author>hanks</author>
      <guid isPermaLink="true">https://hanks-log.tistory.com/98</guid>
      <comments>https://hanks-log.tistory.com/98#entry98comment</comments>
      <pubDate>Tue, 24 Feb 2026 11:51:30 +0900</pubDate>
    </item>
    <item>
      <title>Spring WebFlux vs MVC 비교</title>
      <link>https://hanks-log.tistory.com/97</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring WebFlux와 Spring MVC는 Spring 프레임워크에서 웹 애플리케이션을 구축하기 위한 두 가지 주요 모듈입니다. 이 글에서는 각 기술의 특징을 비교하고, 어떤 상황에서 어떤 기술을 선택하는 것이 적합한지 알아봅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 Spring WebFlux와 MVC를 비교해야 할까요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 웹 애플리케이션의 트래픽이 증가하고, 더 많은 동시 사용자들을 처리해야 하는 상황이 많아지면서 &lt;b&gt;비동기&lt;/b&gt; 및 &lt;b&gt;논블로킹&lt;/b&gt; 방식의 중요성이 부각되고 있습니다. Spring WebFlux는 이러한 요구사항을 충족시키기 위해 등장했는데요. 기존의 Spring MVC와 어떤 차이가 있는지, 그리고 어떤 장단점이 있는지 비교 분석하여 프로젝트에 적합한 기술을 선택하는 것이 중요합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① Spring MVC의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring MVC는 전통적인 &lt;b&gt;Servlet API&lt;/b&gt; 기반의 동기 블로킹 방식입니다. 요청을 처리하는 동안 스레드가 블로킹되어 다른 요청을 처리할 수 없게 됩니다. 많은 동시 사용자가 접속하는 환경에서는 스레드 풀이 고갈되어 성능 저하를 야기할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java // Spring MVC 컨트롤러 예시 @GetMapping(&quot;/mvc&quot;) public String mvcEndpoint() throws InterruptedException { // 오래 걸리는 작업 시뮬레이션 Thread.sleep(1000); return &quot;mvc response&quot;; }&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring WebFlux와 MVC 핵심 개념 비교&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Spring MVC&lt;/th&gt;
&lt;th&gt;Spring WebFlux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;아키텍처&lt;/td&gt;
&lt;td&gt;Servlet API 기반&lt;/td&gt;
&lt;td&gt;Reactive Streams 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스레드 모델&lt;/td&gt;
&lt;td&gt;블로킹 I/O, 스레드당 요청 처리&lt;/td&gt;
&lt;td&gt;논블로킹 I/O, 이벤트 루프&lt;/td&gt;
&lt;/tr&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&gt;
&lt;td&gt;주요 인터페이스&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@Controller&lt;/code&gt;, &lt;code&gt;HttpServletRequest&lt;/code&gt;, &lt;code&gt;HttpServletResponse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@RestController&lt;/code&gt;, &lt;code&gt;Mono&lt;/code&gt;, &lt;code&gt;Flux&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적합한 사용 사례&lt;/td&gt;
&lt;td&gt;CPU 바운드 작업, 간단한 웹 애플리케이션&lt;/td&gt;
&lt;td&gt;I/O 바운드 작업, 높은 동시성이 필요한 애플리케이션&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Spring MVC] [클라이언트] ── 요청 ──&amp;rarr; [Servlet Container (스레드 풀)] ── 스레드 블로킹 ──&amp;rarr; [데이터베이스] &amp;larr;── 응답 ────┘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Spring WebFlux] [클라이언트] ── 요청 ──&amp;rarr; [Netty (이벤트 루프)] ── 논블로킹 ──&amp;rarr; [데이터베이스] &amp;larr;── 응답 ────┘&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;h3 data-ke-size=&quot;size23&quot;&gt;① 프로젝트 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring MVC:&lt;/b&gt; &lt;code&gt;spring-boot-starter-web&lt;/code&gt; 의존성을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xml&lt;/p&gt;
&lt;!-- Spring MVC Maven 설정 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;org.springframework.boot spring-boot-starter-web&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring WebFlux:&lt;/b&gt; &lt;code&gt;spring-boot-starter-webflux&lt;/code&gt; 의존성을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xml&lt;/p&gt;
&lt;!-- Spring WebFlux Maven 설정 --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;org.springframework.boot spring-boot-starter-webflux&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② 컨트롤러 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring MVC:&lt;/b&gt; &lt;code&gt;@Controller&lt;/code&gt; 또는 &lt;code&gt;@RestController&lt;/code&gt; 어노테이션을 사용하고, &lt;code&gt;HttpServletRequest&lt;/code&gt;, &lt;code&gt;HttpServletResponse&lt;/code&gt;를 파라미터로 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java // Spring MVC 컨트롤러 @RestController public class MvcController {&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@GetMapping(&quot;/mvc&quot;)
public String getMvc() {
    return &quot;Hello MVC&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring WebFlux:&lt;/b&gt; &lt;code&gt;@RestController&lt;/code&gt; 어노테이션을 사용하고, &lt;code&gt;Mono&lt;/code&gt; 또는 &lt;code&gt;Flux&lt;/code&gt;를 반환하여 비동기 처리를 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java // Spring WebFlux 컨트롤러 @RestController public class WebFluxController {&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@GetMapping(&quot;/webflux&quot;)
public Mono&amp;lt;String&amp;gt; getWebFlux() {
    return Mono.just(&quot;Hello WebFlux&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ 데이터베이스 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring MVC:&lt;/b&gt; JPA, JDBC 템플릿 등을 사용하여 데이터베이스와 연동합니다. 블로킹 방식으로 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring WebFlux:&lt;/b&gt; R2DBC를 사용하여 논블로킹 방식으로 데이터베이스와 연동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java // R2DBC 예시 (WebFlux) @Repository public interface UserRepository extends ReactiveCrudRepository&amp;lt;User, Long&amp;gt; { }&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;h3 data-ke-size=&quot;size23&quot;&gt;Q: Spring WebFlux가 항상 Spring MVC보다 나은가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A:&lt;/b&gt; 그렇지 않습니다. Spring WebFlux는 논블로킹 I/O를 통해 높은 동시성을 제공하지만, CPU 바운드 작업에는 오히려 성능이 저하될 수 있습니다. 또한, Reactive Streams에 대한 이해가 필요하며, 디버깅이 더 어려울 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q: 기존 Spring MVC 프로젝트를 Spring WebFlux로 마이그레이션해야 할까요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A:&lt;/b&gt; 프로젝트의 요구사항과 규모를 신중하게 고려해야 합니다. 트래픽이 많고, I/O 바운드 작업이 많은 경우 Spring WebFlux로의 마이그레이션을 고려해볼 수 있습니다. 하지만, 프로젝트의 복잡도가 증가할 수 있으므로 충분한 검토가 필요합니다.&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;p data-ke-size=&quot;size16&quot;&gt;Spring WebFlux와 Spring MVC는 각각 장단점을 가지고 있습니다. Spring MVC는 간단한 웹 애플리케이션이나 CPU 바운드 작업에 적합하며, Spring WebFlux는 높은 동시성이 필요한 I/O 바운드 작업에 적합합니다. 프로젝트의 요구사항을 고려하여 적절한 기술을 선택하는 것이 중요합니다. 개인적으로는 높은 동시성을 요구하는 프로젝트에서 Spring WebFlux를 사용했을 때 성능 향상을 체감할 수 있었는데요. 하지만 러닝 커브가 존재하므로 충분한 학습이 필요하다고 생각합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 프로젝트 요구사항 분석&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; Spring MVC와 WebFlux의 장단점 비교&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 성능 테스트 및 벤치마킹&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 적절한 기술 선택 및 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html&quot;&gt;Spring WebFlux Reference Documentation&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>non-blocking I/O</category>
      <category>Reactive Streams</category>
      <category>Spring Framework</category>
      <category>Spring MVC</category>
      <category>Spring WebFlux</category>
      <category>개발 생산성</category>
      <category>마이크로서비스</category>
      <category>성능 비교</category>
      <category>아키텍처 비교</category>
      <category>웹 개발</category>
      <author>hanks</author>
      <guid isPermaLink="true">https://hanks-log.tistory.com/97</guid>
      <comments>https://hanks-log.tistory.com/97#entry97comment</comments>
      <pubDate>Tue, 24 Feb 2026 11:09:07 +0900</pubDate>
    </item>
  </channel>
</rss>