Home

0

[JAVA] - Cold Start

목차 [JAVA] - 가비지 컬렉션 튜닝 [JAVA] - Garbage Collection(가비지 컬렉션) [JAVA] - JVM (자바 가상 머신) 🧊 Cold Start란?Cold Start는 JVM이 애플리케이션이 처음 실행될 때 초기화 과정 때문에 발생하는 지연(latency) 을 의미합니다. 마치 추운 겨울날 자동차 시동을 걸 때 엔진이 따뜻해질 때까지 시간이 걸리는 것과 비슷합니다. 일반적인 서버 환경에서는 애플리케이션이 한 번 시작되면 계속 실행되기 때문에 이 문제가 크게 부각되지 않았습니다. 하지만 MSA 는 인스턴스가 빈번히 생성 및 소멸되고 여러 서버가 긴밀히 통신하는 환경입니다. 이로 인해 특정 시스템이 재부팅되면 연결된 다른 시스템의 응답 속도까지 지연되는 문제가 발생했으며, 무중단 배포·잦은 배포·오토스케일링 등으로 인해 기존 아키텍처보다 성능 지연 현상이 더 빈번하게 나타나게 되었고, 전체적인 서비스 성능 이슈의 문제가 됐습니다. 🚦 Cold Start가 발생하는 이유Java 애플리케이션은 네이티브 코드가 아닌 JVM 위에서 바이트코드 실행 방식으로 동작하기 때문에, 첫 실행 시 다음과 같은 과정에서 오버헤드가 발생합니다. 1. JVM 초기화 과정의 복잡성Java 애플리케이션이 시작될 때 여러 단계의 초기화 과정을 거쳐야 합니다:

0

[Spring Boot] - 스프링 프로퍼티 암호화

스프링부트 민감정보 암호화 하기운영 중인 프로그램에서 DB 암호나 API 키와 같은 민감 정보를 설정 파일에 평문으로 저장할 경우, 프로그램이 유출되면 그대로 외부에 노출되는 심각한 보안 문제가 발생할 수 있습니다. 이를 방지하기 위해 프로퍼티 암호화를 적용하면, 설정값이 유출되더라도 평문이 아닌 암호화된 형태로만 노출되므로 보안성을 크게 강화할 수 있습니다. 자바 환경에서는 이러한 암·복호화를 손쉽게 지원하는 Jasypt 모듈을 활용할 수 있어, 운영 환경에서 안전하게 민감 정보를 관리할 수 있습니다. 이번 포스팅에서는 Spring boot 에 Jaspyt 적용해 프로퍼티를 암복호화 하는 내용을 작성했습니다 암복호화를 위한 라이브러리 추가// Jasypt for property encryptionimplementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5' 값 암호화 하기java -cp "build/libs/app.jar" com.ulisesbocchio.jasyptspringboot.cli.JasyptEncryptorCLI \ input="my-db-pass" \ password="${JASYPT_ENCRYPTOR_PASSWORD}" \ algorithm="PBEWITHHMACSHA512ANDAES_256" \ ivGeneratorClassName="org.jasypt.iv.RandomIvGenerator" Configuration 추가

0

[Java] - 가비지 컬렉션 히스토리

목차 [JAVA] - 가비지 컬렉션 튜닝 [JAVA] - Garbage Collection(가비지 컬렉션) [JAVA] - JVM (자바 가상 머신) 초기 GC들 (JDK 1.0~1.4)Serial GC 가장 오래된 GC 단일 스레드로 동작 작은 애플리케이션이나 클라이언트 환경에 적합 Stop-the-World 시간이 길어서 현재는 제한적으로 사용 Parallel GC (Throughput Collector) JDK 1.4에서 도입 멀티코어 환경에서 여러 스레드를 사용해 처리량을 개선 JDK 6~8에서 기본 GC 여전히 긴 정지 시간이 단점 저지연 목표 GC들 (JDK 1.4~)Concurrent Mark Sweep (CMS) GC

0

[Javascript] Axios 파일 업로드 및 다운로드

파일 업로드 및 다운로드📁 파일 업로드// 단일 파일 업로드async function uploadFile(file, onProgress) { const formData = new FormData(); formData.append('file', file); formData.append('category', 'documents'); formData.append('description', [file.name](http://file.name)); try { const response = await [axios.post](http://axios.post)('/api/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: (progressEvent) => { const percentCompleted = Math.round( (progressEvent.loaded * 100) / [progressEvent.total](http://progressEvent.total) ); console.log(`업로드 진행률: ${percentCompleted}%`); onProgress?.(percentCompleted); } }); return [response.data](http://response.data); } catch (error) { console.error('파일 업로드 실패:', error); throw error; }}// 다중 파일 업로드async function uploadMultipleFiles(files) { const formData = new FormData(); files.forEach((file, index) => { formData.append(`files`, file); }); const response = await [axios.post](http://axios.post)('/api/upload/multiple', formData, { headers: { 'Content-Type': 'multipart/form-data' } }); return [response.data](http://response.data);}// 사용 예시const fileInput = document.getElementById('fileInput');fileInput.addEventListener('change', async (event) => { const file = [event.target](http://event.target).files[0]; if (file) { try { const result = await uploadFile(file, (progress) => { console.log(`업로드 진행률: ${progress}%`); }); console.log('업로드 완료:', result); } catch (error) { console.error('업로드 에러:', error); } }}); 📥 파일 다운로드// 파일 다운로드async function downloadFile(fileId, filename) { try { const response = await axios.get(`/api/files/${fileId}/download`, { responseType: 'blob' }); // Blob을 사용하여 파일 다운로드 트리거 const url = window.URL.createObjectURL(new Blob([[response.data](http://response.data)])); const link = document.createElement('a'); link.href = url; link.setAttribute('download', filename); document.body.appendChild(link); [link.click](http://link.click)(); document.body.removeChild(link); // 메모리 정리 window.URL.revokeObjectURL(url); } catch (error) { console.error('파일 다운로드 실패:', error); }} 에러 처리 베스트 프랙티스🛡️ 포괄적인 에러 처리// 에러 타입별 처리 함수function handleApiError(error) { if (error.response) { // 서버가 응답했지만 상태 코드가 2xx 범위를 벗어남 const { status, data } = error.response; switch (status) { case 400: console.error('잘못된 요청:', data.message); break; case 401: console.error('인증 실패 - 로그인이 필요합니다'); // 로그인 페이지로 리다이렉트 window.location.href = '/login'; break; case 403: console.error('접근 권한이 없습니다'); break; case 404: console.error('요청한 리소스를 찾을 수 없습니다'); break; case 429: console.error('요청 한도를 초과했습니다. 잠시 후 다시 시도해주세요'); break; case 500: console.error('서버 내부 오류가 발생했습니다'); break; default: console.error(`서버 오류 (${status}):`, data.message); } return { type: 'response', status, message: data.message }; } else if (error.request) { // 요청은 전송되었지만 응답을 받지 못함 (네트워크 오류) console.error('네트워크 오류: 서버에 연결할 수 없습니다'); return { type: 'network', message: '네트워크 연결을 확인해주세요' }; } else { // 요청 설정 중에 오류 발생 console.error('요청 설정 오류:', error.message); return { type: 'config', message: error.message }; }}// 재시도 로직이 포함된 API 호출async function apiCallWithRetry(requestFn, maxRetries = 3, delay = 1000) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await requestFn(); } catch (error) { const errorInfo = handleApiError(error); if (attempt === maxRetries || errorInfo.type === 'response') { throw error; // 최대 재시도 횟수 도달 또는 서버 응답 오류 } console.log(`재시도 ${attempt}/${maxRetries} - ${delay}ms 후 다시 시도...`); await new Promise(resolve => setTimeout(resolve, delay * attempt)); } }}// 사용 예시async function fetchUserData(userId) { return apiCallWithRetry( () => axios.get(`/api/users/${userId}`), 3, // 최대 3번 재시도 1000 // 1초 간격 );}

0

[Javascript] Axios 인터셉터

인터셉터로 요청/응답 제어하기📤 요청 인터셉터// 모든 요청에 공통 로직 적용axios.interceptors.request.use( (config) => { // 요청 시작 로그 console.log(`🚀 API 요청: ${config.method?.toUpperCase()} ${config.url}`); // 인증 토큰 자동 추가 const token = localStorage.getItem('accessToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } // 요청 시간 기록 (성능 측정용) config.metadata = { startTime: new Date() }; return config; }, (error) => { console.error('❌ 요청 설정 오류:', error); return Promise.reject(error); }); 📥 응답 인터셉터// 모든 응답에 공통 로직 적용axios.interceptors.response.use( (response) => { // 응답 시간 계산 const endTime = new Date(); const duration = endTime - response.config.metadata.startTime; console.log(`✅ API 응답: ${response.config.url} (${duration}ms)`); return response; }, async (error) => { const originalRequest = error.config; // 401 에러: 토큰 갱신 처리 if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { const refreshToken = localStorage.getItem('refreshToken'); const response = await [axios.post](http://axios.post)('/auth/refresh', { refreshToken }); const newToken = [response.data](http://response.data).accessToken; localStorage.setItem('accessToken', newToken); // 원래 요청에 새 토큰 적용 후 재시도 originalRequest.headers.Authorization = `Bearer ${newToken}`; return axios(originalRequest); } catch (refreshError) { // 토큰 갱신 실패 시 로그인 페이지로 리다이렉트 localStorage.clear(); window.location.href = '/login'; } } // 에러 로깅 console.error('❌ API 에러:', { url: error.config?.url, status: error.response?.status, message: error.message }); return Promise.reject(error); }); 실전 예제: API 클래스 구현🏢 사용자 관리 API 클래스class UserService { constructor() { this.client = axios.create({ baseURL: process.env.REACT_APP_API_URL || 'https://api.example.com', timeout: 10000 }); this.setupInterceptors(); } setupInterceptors() { // 요청 인터셉터: 인증 토큰 자동 추가 this.client.interceptors.request.use(config => { const token = this.getAuthToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // 응답 인터셉터: 에러 처리 this.client.interceptors.response.use( response => response, error => this.handleError(error) ); } getAuthToken() { return localStorage.getItem('authToken'); } handleError(error) { const message = error.response?.data?.message || error.message; throw new Error(`API 요청 실패: ${message}`); } // 사용자 목록 조회 async getUsers(options = {}) { const { page = 1, limit = 10, search = '' } = options; const response = await this.client.get('/users', { params: { page, limit, search } }); return { users: [response.data.data](http://response.data.data), totalCount: [response.data.total](http://response.data.total), currentPage: page }; } // 사용자 상세 조회 async getUserById(userId) { const response = await this.client.get(`/users/${userId}`); return [response.data](http://response.data); } // 사용자 생성 async createUser(userData) { const response = await [this.client.post](http://this.client.post)('/users', userData); return [response.data](http://response.data); } // 사용자 정보 수정 async updateUser(userId, userData) { const response = await this.client.put(`/users/${userId}`, userData); return [response.data](http://response.data); } // 사용자 삭제 async deleteUser(userId) { await this.client.delete(`/users/${userId}`); return { success: true, userId }; } // 사용자 검색 async searchUsers(query) { const response = await this.client.get('/users/search', { params: { q: query } }); return [response.data](http://response.data); }}// 싱글톤 패턴으로 사용export const userService = new UserService();// 사용 예시async function handleUserOperations() { try { // 사용자 목록 조회 const userList = await userService.getUsers({ page: 1, limit: 20, search: 'john' }); console.log('사용자 목록:', userList); // 새 사용자 생성 const newUser = await userService.createUser({ name: '김개발', email: '[kim.dev@example.com](mailto:kim.dev@example.com)', role: 'developer' }); console.log('생성된 사용자:', newUser); // 사용자 정보 수정 const updatedUser = await userService.updateUser([newUser.id](http://newUser.id), { name: '김시니어', role: 'senior-developer' }); console.log('수정된 사용자:', updatedUser); } catch (error) { console.error('사용자 작업 중 오류:', error.message); }}

0

[Javascript] Axios 사용하기 (고급편)

고급 설정 및 인스턴스⚙️ 상세한 요청 설정const requestConfig = { method: 'get', url: '/api/users', baseURL: 'https://api.example.com', // 헤더 설정 headers: { 'Authorization': 'Bearer token123', 'Content-Type': 'application/json', 'Accept': 'application/json' }, // URL 파라미터 params: { page: 1, size: 20 }, // 타임아웃 (밀리초) timeout: 10000, // 응답 타입 responseType: 'json', // 'json', 'text', 'blob', 'stream' 등 // 요청 바디 (POST, PUT, PATCH) data: { name: '홍길동', email: '[hong@example.com](mailto:hong@example.com)' }};const response = await axios(requestConfig); 🏗️ Axios 인스턴스 생성// API별 인스턴스 생성const apiClient = axios.create({ baseURL: 'https://api.myservice.com/v1', timeout: 15000, headers: { 'Content-Type': 'application/json' }});// 인증이 필요한 API용 인스턴스const authApiClient = axios.create({ baseURL: 'https://api.myservice.com/v1', timeout: 10000, headers: { 'Authorization': `Bearer ${getAuthToken()}` }});// 인스턴스 사용const users = await apiClient.get('/users');const profile = await authApiClient.get('/profile'); 인터셉터로 요청/응답 제어하기웹 개발을 하다 보면 서버와 통신할 때 반복적으로 처리해야 하는 공통 로직들이 생깁니다.예를 들어 JWT 토큰을 모든 요청 헤더에 붙인다거나, 에러 응답을 일관되게 처리한다거나, 응답 데이터 구조를 통일한다거나 하는 일들이죠. 이때 유용하게 사용할 수 있는 기능이 바로 Axios Interceptor입니다. 📤 요청 인터셉터

0

[Javascript] Axios 사용하기

웹 개발에서 API 통신은 필수불가결한 요소입니다. 그 중에서도 Axios는 가장 인기 있고 강력한 JavaScript HTTP 클라이언트 라이브러리로 자리잡고 있습니다. 이 글에서는 Axios의 기본 사용법부터 고급 기능까지 실무에서 바로 활용할 수 있는 예제와 함께 상세히 알아보겠습니다. Axios란 무엇인가?Axios는 Promise 기반의 HTTP 클라이언트 라이브러리로, 브라우저와 Node.js 환경에서 모두 사용할 수 있습니다. ✨ 주요 특징 Promise 기반: async/await와 완벽 호환 요청/응답 인터셉터: 공통 로직 처리 가능 자동 JSON 변환: 별도 변환 과정 불필요 요청/응답 변환: 데이터를 원하는 형태로 가공 요청 취소: 불필요한 요청 중단 가능 광범위한 브라우저 지원: IE11까지 지원 설치 및 기본 설정📦 설치# npm 사용시npm install axios# yarn 사용시 yarn add axios# pnpm 사용시pnpm add axios

0

네트워크 트래픽의 3가지 패턴 - Ingress, Egress, East-West Traffic

현대 IT 인프라를 이해하려면 데이터가 어떻게 흐르는지 파악하는 것이 필수입니다. 특히 클라우드 환경과 마이크로서비스 아키텍처가 보편화되고 트래픽의 움직임이 클라우드 서비스의 과금 요소중에 하나가 됨으로써 네트워크 트래픽 패턴을 정확히 이해하는 것은 더욱 중요해졌습니다. 오늘은 인프라를 운영하면서 알아두면 좋을 3가지 트래픽 패턴에 대해 알아보겠습니다. 📌 1. Ingress: 외부에서 내부로 들어오는 트래픽 Ingress는 클러스터 외부 → 내부로 들어오는 요청입니다. Ingress Traffic은 외부에서 우리 네트워크나 시스템으로 들어오는 모든 트래픽을 의미합니다. 대표적인 예시는 인터넷 사용자가 웹 애플리케이션에 접속하는 경우입니다. 실무 예시🌐 사용자 브라우저 → 웹 서버 (HTTP/HTTPS 요청)📱 모바일 앱 → API 게이트웨이 (REST API 호출)🔗 외부 시스템 → 내부 서비스 (Webhook, 데이터 동기화) 📌 2. Egress: 내부에서 외부로 나가는 트래픽 Egress는 클러스터 내부 → 외부로 나가는 요청 Egress Traffic은 우리 시스템에서 외부로 나가는 모든 트래픽입니다. 예를 들어 응답 데이터, 외부 API 호출, 백업 데이터 전송등을 할때 발생합니다.

0

[Django] - Model 이란?

Model이란?Django에서 Model은 데이터베이스와 상호작용하기 위한 핵심 개념입니다. 쉽게 말해, 데이터베이스 테이블을 파이썬 클래스 형태로 정의한 것이라고 보면 됩니다. 하나의 모델 클래스 → 하나의 테이블 클래스의 속성 → 테이블의 컬럼 인스턴스 → 테이블의 행(Row) 즉, 모델을 정의하면 SQL을 직접 작성하지 않고도 Django의 ORM(Object Relational Mapping)을 통해 데이터베이스를 제어할 수 있습니다. Model 생성제목, 내용, 작성자, 생성시간, 수정시간을 가진 게시판 데이터를 저장하기 위해서는 다음과 같이 모델을 정의할 수 있습니다. from django.db import modelsclass Board(models.Model): title = models.CharField(max_length=200) # 문자열 컬럼 content = models.TextField() # 긴 텍스트 author = models.ForeignKey('auth.User', on_delete=models.CASCADE) # 외래키 created_at = models.DateTimeField(auto_now_add=True) # 생성 시간 updated_at = models.DateTimeField(auto_now=True) # 수정 시간 class Meta: ordering = ['-created_at'] # 기본 정렬 def __str__(self): return self.title 주요 요소 설명필드(Field)

0

[Spring Boot] - Spring Interceptor 에서 Response 조작이 가능한가?

참고 Spring interceptor에서 Response 수정하기 “HttpServletResponse is committed” mean? 📝 후처리에서 메뉴 데이터를 Response 객체에 데이터를 넣어주세요개발 요구사항 중 페이지 접근시 전처리에서 접근 권한을 확인하고 후처리로 Response 객체에 사용자가 접근할 수 있는 메뉴를 같이 내려달라는 요구를 받았습니다. 해당 요구사항을 Spring 에서 제공하는 Interceptor 로 가능한지 검토해 달라는 요청을 받았습니다. 🤔 Interceptor 에서 데이터 변경이 가능할까?스프링에서 제공하는 Interceptor 는 아래 코드와 같습니다. 요청을 처리하는 Controller 전에 preHandler 를 거치기 때문에 사용자 접근을 제어해달라는 요구사항은 어렵지 않았습니다. 마찬가지로 요청이 끝난 후에는 postHandler 를 거쳐 나가는데 그때 응답 객체를 수정할 수 있지 않을까? 마침, HttpServletRequest 객체를 매게변수로 받아서 조작이 가능할 것 같은 그런 기분이 들지만, 결론은 안됩니다. (두둥탁 🥁) HandlerInterceptorpublic interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }} 🧐 그럼 왜 인터셉터 에서 응답 객체 조작이 불가능한 것인가?