1. SiteMesh란?

SiteMesh는 Java 기반 웹 애플리케이션에서 공통 레이아웃을 적용하기 위한 필터 기반 데코레이터

프레임워크.


2. 데코레이터 패턴이란?

기존 객체의 기능을 변경하지 않고, 동적으로 기능을 추가하거나 확장할 수 있게 하는 패턴


3. SiteMesh의 주요 개념

개념 설명
Decorator 페이지에 입힐 공통 레이아웃 (보통 layout.jsp)
Content 실제 사용자 컨텐츠 (board.jsp, main.jsp 등)
Filter 웹 요청 시 페이지를 가로채서 레이아웃을 적용
Tag <sitemesh:write property="title"/> 등으로 컨텐츠 위치 지정

4. SiteMesh의 동작 방식

- 동작방식

   요청: /board/list.jsp

  1. 사용자는 /board/list.jsp를 요청함
  2. SiteMesh Filter가 요청을 가로채서 내부적으로 렌더링 시작
  3. list.jsp의 내용(본문)을 먼저 생성함
    → 여기서 title, head, body 부분을 추출
  4. layout.jsp(데코레이터)에 그 내용들을 끼워 넣음:
    • <sitemesh:write property="title"/> → list.jsp의 <title>로 채움
    • <sitemesh:write property="body"/> → list.jsp의 본문 내용으로 채움
  5. 완성된 페이지를 사용자에게 전달

- Spring MVC의 ViewResolver와는 별개로 동작함

- 설정 파일 (decorators.xml) 또는 Java Config를 통해 URL 패턴 매핑 가능


5. SiteMesh의 장점

장점 설명
공통 레이아웃 관리 용이 header, footer 중복 제거
유지보수 편리 전체 UI 변경 시 layout.jsp만 수정하면 됨
디자인 일관성 모든 페이지가 동일한 틀을 가짐
적용 간단 복잡한 설정 없이도 빠르게 도입 가능
Spring MVC와 호환 가능 filter 기반이라 MVC 설정과 충돌 없음

6. SiteMesh 3.x vs 2.x 설정 방식 비교

항목 SiteMesh 2.x (XML 설정) SiteMesh 3.x (Java 설정)
설정 파일 decorators.xml Java 클래스 (SiteMeshFilter 설정)
레이아웃 매핑 방식 XML 기반 URL 패턴 코드 기반 URL 패턴
유연성/동적 처리 제한적 (고정된 설정) 매우 유연 (조건별 설정 가능)
커스터마이징 복잡하고 제한적 필터 체인 및 커스텀 파서 등 쉬움
초기화 방식 web.xml 필터 등록 FilterRegistrationBean 또는 Java Config
적용 대상 JSP 중심 HTML5, Thymeleaf 등도 OK

 

- 2.x는 전통적인 XML 설정 기반

- 3.x는 Java 기반 설정과 더 유연한 확장성 지원

 

SiteMesh 2.x 예제 (XML 설정 기반)

decorators.xml
<decorators defaultdir="/WEB-INF/decorators">
    <decorator name="default" page="layout.jsp">
        <pattern>/*</pattern>
    </decorator>
</decorators>

 

web.xml
<filter>
    <filter-name>sitemesh</filter-name>
    <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>sitemesh</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

 

SiteMesh 3.x 예제 (Java 설정 기반)

SiteMeshConfig.java
import org.sitemesh.builder.SiteMeshFilterBuilder;
import org.sitemesh.config.ConfigurableSiteMeshFilter;
import org.sitemesh.builder.SiteMeshFilterBuilder;
import org.sitemesh.webapp.WebAppContext;

import javax.servlet.annotation.WebFilter;
import javax.servlet.FilterConfig;

@WebFilter("/*")
public class SiteMeshConfig extends ConfigurableSiteMeshFilter {
    @Override
    protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
        builder
            .setMimeTypes("text/html")  // 필터링할 MIME 타입
            .addDecoratorPath("/*", "/WEB-INF/decorators/layout.jsp") // 데코레이터 지정
            .addExcludedPath("/static/*"); // 제외할 경로
    }
}

7. 정리

SiteMesh는 웹 페이지에 일관된 레이아웃을 자동으로 적용해 유지보수를 쉽게 만드는 필터 기반 템플릿 프레임워크다.

** SiteMesh 적용 프로젝트 | https://github.com/LuckyStrike1989/my-spring-framewrok-project

'BackEnd > Spring&SpringBoot' 카테고리의 다른 글

[Spring] RestfulA API HATEOAS 설정  (0) 2024.08.21
[Spring] Validation API 유효성 체크  (0) 2024.08.04

쿼리 성능 개선 작업을 진행하면서 행의 값의 존재 유무를 표기하는데, EXISTS와 COUNT로 혼용하여 쿼리 가 되어 있는 부분이 있어 정리하면서 글로 남겨보았습니다.

결론부터 이야기 하면 행의 값의 존재 유무를 표기하는데  COUNT보다는 EXISTS가 성능이 더 좋습니다.

왜 그런지 비교와 예시를 들어 이유를 정리해 보겠습니다.

 

1. EXISTS vs COUNT 성능 비교

항목 EXISTS COUNT
목적 레코드 존재 여부 확인 데이터의 건수 확인
동작 방식 첫 번째 행 발견 시 즉시 중단 모든 조건 만족 행을 전부 탐색
성능 빠름 (항상 일정하게 빠름) 느림 (데이터 많을수록 느려짐)
용도 데이터 존재여부 판단 정확한 레코드 수가 필요할 때만

 

이론상 성능 비교만 봐도 존재 여부만을 생각할때 COUNT보다는 EXISTS가 빠르다는걸 알 수 있습니다.

EXISTS는 값의 존재 유무에따라 즉각 중단되지만, COUNT는 전제 데이터를 스캔하는 것을 알 수 있습니다.

실제 오라클에서 100만건의 더미 데이터를 통해 성능 확인을 해보겠습니다.

-- 테이블 생성
CREATE TABLE TB_BOARD (
    SEQ     NUMBER PRIMARY KEY,
    TITLE   VARCHAR2(200),
    CONTENT CLOB,
    WRITER  VARCHAR2(100)
);

-- 시퀀스 생성
CREATE SEQUENCE SQ_BOARD
START WITH 1
INCREMENT BY 1
NOCACHE
NOCYCLE;

-- 테스트 더미 데이터 100만건 입력
BEGIN
    FOR i IN 1..1000000 LOOP
        INSERT INTO TB_BOARD (
            SEQ, TITLE, CONTENT, WRITER
        ) VALUES (
            SQ_BOARD.NEXTVAL,
            '제목 ' || i,
            '내용입니다. 이건 테스트용 더미 데이터입니다. 번호: ' || i,
            '작성자' || MOD(i, 10)
        );
        
        -- 커밋 주기를 정해서 성능 최적화
        IF MOD(i, 10000) = 0 THEN
            COMMIT;
        END IF;
    END LOOP;
    COMMIT;
END;

 

위와 같이 테이블을 생성하고 100만건의 데이터를 입력 하겠습니다.


2. EXISTS와 COUNT 예시 쿼리 및 실행 계획 비교

-- 실행 속도 : 0.003초
SELECT 'Y'
FROM dual
WHERE EXISTS (
    SELECT 1
    FROM TB_BOARD
    WHERE WRITER = '작성자1'
);

EXISTS 실행 계획 결과

 

-- 실행 속도 0.053초
SELECT COUNT(*)
FROM TB_BOARD
WHERE WRITER = '작성자1';

COUNT 실행 계획 결과

 

둘다 쿼리를 실행 했을때는 1초 미만으로 쿼리가 빠르긴 하지만, 실행 속도와 실행 계획에서 COST를 보면 성능이 차이난다는 것을 알 수 있습니다. 그럼 여기서 성능을 더 높이기 위해서 WRITER행에 INDEX를 걸어 성능을 높여 보겠습니다.

-- 인덱스 생성
CREATE INDEX IDX_TB_BOARD_WRITER ON TB_BOARD(WRITER);
-- 인덱스 삭제
DROP INDEX IDX_TB_BOARD_WRITER;

INDEX 후 EXISTS 실행 계획 결과

 

INDEX 후 COUNT 실행 계획 결과

 

인덱스 후 결과는 COUNT는 성능이 향상 되었다는걸 알 수 있습니다. 하지만 EXISTS는 오히려 성능이 약간 떨어 졌다는 걸 알 수 있습니다. 간단히 이유를 정리 해봤습니다.

 

A. EXISTS는 인덱스를 꼭 안 쓸수도 있다.

EXISTS는 "조건을 만족하는 첫 번째 레코드만 찾으면 끝"이기 때문에,
풀 스캔이 더 빠르다고 판단되면 오라클 옵티마이저가 인덱스를 안 쓰고 풀 테이블 스캔으로 가기도 합니다.

그런데 WRITER에 인덱스를 추가하면, 옵티마이저가 인덱스 존재로 인해 인덱스 레인지 스캔을 하다가 오히려 더 많은 I/O와 비효율적인 랜덤 액세스로 성능이 나빠질 수 있습니다.

 

B. EXISTS는 "인덱스만 보고 끝내는" 작업이 아니다.

EXISTS 쿼리 안에서 SELECT 1 FROM TB_BOARD WHERE WRITER = '작성자1'라고 하면,

인덱스로 "WRITER = '작성자1'" 조건은 탐색하지만

결국 TB_BOARD 테이블의 실제 행(ROWID)을 따라가서 실제로 있는지 확인까지 해야 됩니다.

즉, "인덱스 스캔 → ROWID 읽기 → 테이블 접근" 이런 랜덤 I/O가 많아질 수 있어서 오히려 느려질 수 있습니다.

 

C. 반대로 COUNT(*)는 인덱스가 있으면 빠르게 인덱스만 보고 셈 가능

WRITER 인덱스를 쓰면 조건에 맞는 ROWID 목록을 순차적으로 얻어서, 굳이 전체 행을 읽지 않고 인덱스 레벨에서 COUNT가 가능하기 때문에 성능이 향상 되었습니다.


3. 정리

A. EXISTS

  • 조건을 만족하는 첫 번째 레코드만 찾으면 바로 TRUE 리턴하고 끝남.
  • 인덱스로 인해 오히려 성능이 떨어 질 수 있음.
  • 내부적으로 STOP 키워드처럼 작동.

B. COUNT

  • 조건에 맞는 모든 레코드를 스캔해서 카운트함.
  • 수백만 건이면 진짜 다 셈.
  • 결과가 많을수록 시간 증가.

존재 여부만 체크할 때는 반드시 EXISTS 사용 (예: 중복 체크, 데이터 유무 확인)

정확한 숫자가 필요할 때만 COUNT 사용 (예: 페이지네이션, 통계)

 

존재 여부만 알고 싶은데 COUNT로 다 세면 불필요한 리소스 낭비이고, EXISTS가 더 좋은 선택 입니다.

 

 

 

cookie-parser는 Node.js의 Express 애플리케이션에서 쿠키를 쉽게 다룰 수 있도록 도와주는 미들웨어이다.

cookie-parser란?

  • HTTP 요청에서 쿠키를 파싱하여 req.cookies 또는 req.signedCookies 객체로 변환해준다.
  • 서명된 쿠키(signed cookie)도 지원하여, 쿠키의 무결성을 검증할 수 있다.
  • Express에서 쿠키 값을 쉽게 읽고 조작할 수 있도록 도와준다.

설치 방법

npm install cookie-parser

 


사용법

1. 기본 사용 (일반 쿠키)

const express = require('express');
const cookieParser = require('cookie-parser');

const app = express();
app.use(cookieParser()); // 쿠키 파싱 미들웨어 추가

app.get('/', (req, res) => {
    console.log(req.cookies); // { key: 'value' } 형태로 쿠키 출력
    res.send('쿠키 확인!');
});

app.listen(3000, () => console.log('Server running on port 3000'));

- req.cookies를 통해 클라이언트가 보낸 쿠키를 확인할 수 있다.

 

2. 서명된 쿠키 사용

app.use(cookieParser('mySecretKey')); // 쿠키 서명 키 추가

app.get('/set-signed-cookie', (req, res) => {
    res.cookie('signedCookie', 'secureValue', { signed: true });
    res.send('서명된 쿠키가 설정되었습니다.');
});

app.get('/get-signed-cookie', (req, res) => {
    console.log(req.signedCookies); // 서명된 쿠키 출력
    res.send(req.signedCookies);
});

- signed: true 옵션을 사용하면 서명된 쿠키를 설정할 수 있다.

- req.signedCookies를 사용하면 서명된 쿠키를 확인할 수 있다.

 

3. 주요 메서드

메서드 설명
req.cookies 일반 쿠키 객체
req.signedCookies 서명된 쿠키 객체
res.cookie(name, value, options) 쿠키 설정
res.clearCookie(name, options) 쿠키 삭제
res.cookie('username', 'JohnDoe', {
    maxAge: 1000 * 60 * 60 * 24, // 1일
    httpOnly: true, // JavaScript에서 접근 불가능 (보안 강화)
    secure: true, // HTTPS에서만 전송됨
    signed: true // 서명된 쿠키
});

 


요약

  • cookie-parser는 HTTP 요청에서 쿠키를 쉽게 파싱할 수 있도록 도와준다.
  • 일반 쿠키(req.cookies)와 서명된 쿠키(req.signedCookies)를 다룰 수 있다.
  • res.cookie()를 사용하여 쿠키를 설정할 수 있다.
  • 보안 옵션 (httpOnly, secure, signed)을 활용하면 안전한 쿠키 관리를 할 수 있다.

 

윈도우 환경에서 Docker를 이용해 Oracle Database를 설치하기전에 먼저 Windows에서 WSL2(Windows Subsystem for Linux 2) 와 Docker Desktop 설치가 먼저 필요합니다.

 

1. Oracle Docker 이미지 다운로드 및 실행

1) Oracle Database Docker 이미지 받기

Docker Hub에는 공식 Oracle 이미지가 없지만, Oracle의 컨테이너 레지스트리에서 직접 받을 수 있습니다. 

// docker search 명령어로 image 검색  
docker search [image명]
docker search oracle

2) Oracle Container Registry에 로그인

Oracle Container Registry(https://container-registry.oracle.com/)에 접속 후 로그인

Database → Enterprise를 찾아 Accept License Agreement 클릭

Accept License Agreement 클릭 결과

 

3) docker login을 사용해서 Oracle 레지스트리에 로그인

C:\>docker login container-registry.oracle.com
Username: 사용자 오라클 아이디
Password: 사용자 오라클 패스워드
Login Succeeded

 

4) docker image pull 및 실행(run)

docker run -d --name docker-oracle 
-p 1521:1521 -p 5500:5500 
-e ORACLE_SID=ORCL 
-e ORACLE_PDB=ORCLPDB1 
-e ORACLE_PWD=admin1234 
container-registry.oracle.com/database/enterprise:21.3.0.0

 

설명

  • --name docker-oracle → 컨테이너 이름 지정
  • -p 1521:1521 → Oracle Listener 포트 연결
  • -p 5500:5500 → Oracle Enterprise Manager 포트 연결
  • -e ORACLE_SID=ORCL → 기본 SID 설정
  • -e ORACLE_PDB=ORCLPDB1 → 기본 PDB 이름 설정
  • -e ORACLE_PWD=admin1234 → 시스템 관리자 계정 비밀번호 설정
  • container-registry.oracle.com/database/enterprise:21.3.0 → 사용할 이미지

실행결과

C:\>docker run -d --name docker-oracle -p 1521:1521 -p 5500:5500 -e ORACLE_SID=ORCL -e ORACLE_PDB=ORCLPDB1 -e ORACLE_PWD=admin1234 container-registry.oracle.com/database/enterprise:21.3.0.0
locally
21.3.0.0: Pulling from database/enterprise
7c8051acdded: Pull complete
e8e925221939: Pull complete
588ca0fc7bfb: Pull complete
ae20812c4f08: Pull complete
d9825b0e2b89: Pull complete
a6246436cc89: Pull complete
f85ecbfecac3: Pull complete
c5203ee57353: Pull complete
706e9c3af817: Pull complete
8901a4ae8c19: Pull complete                                                        
a33ea7dcd68f: Pull complete
998fd4ce9410: Pull complete
d08d9a55c68d: Pull complete
a9475d76f917: Pull complete
bd9ddc54bea9: Pull complete
12e12fcd89bc: Pull complete
227a8fd94a56: Pull complete
8b6232f72c10: Pull complete
9af4cdee8387: Pull complete
Digest: sha256:c5ad975902cfe523a4ac9f046ec87cd0fd41c24118651ca0e7194f736ae4e3c7
Status: Downloaded newer image for container-registry.oracle.com/database/enterprise:21.3.0.0
17703b1f72f276429919f282619a3650df0f31c354d6cd9ed70544b35bb1fc6a

 

5) 컨테이너 정상 실행 확인 및 로그 확인

컨테이너가 정상적으로 실행되고 있는지 확인

docker ps

 

컨테이너 로그 확인

docker logs -f [컨테이너명]
docker logs -f docker-oracle

2. Oracle 접속 방법

SQLPlus로 접속

docker exec -it [컨테이너명] sqlplus [계정명]/[패스워드]@//[호스트]:[포트]/[PDB] as sysdba
docker exec -it docker-oracle sqlplus sys/admin1234@//localhost:1521/ORCLPDB1 as sysdba

 

만약, 아래와 같이 아이디와 비밀번호가 유효하지 않다는 에러가 나타난다면

PS C:\Users\maest> docker exec -it docker-oracle sqlplus sys/admin1234@//localhost:1521/ORCLPDB1 as sysdba

SQL*Plus: Release 21.0.0.0.0 - Production on Sat Mar 8 01:39:41 2025
Version 21.3.0.0.0

Copyright (c) 1982, 2021, Oracle.  All rights reserved.

ERROR:
ORA-12514: TNS:listener does not currently know of service requested in connect
descriptor

Enter user-name: sys
Enter password:
ERROR:
ORA-28009: connection as SYS should be as SYSDBA or SYSOPER

Enter user-name: sysdba
Enter password:
ERROR:
ORA-01017: invalid username/password; logon denied

 

다음 명령어를 실행 후 다시 접속을 시도 해봅시다

docker exec [컨테이너명] ./setPassword.sh [패스워드]
docker exec docker-oracle ./setPassword.sh admin1234

 

Oracle SQL Developer로 접속

 

SQL Developer나 DBeaver 등을 이용해 다음과 같이 접속 가능합니다.

계정 만들기

1. SQLPlus로 sys계정으로 접속
C:\>docker exec -it docker-oracle sqlplus sys/admin1234@//localhost:1521/ORCLPDB1 as sysdba

2. 계정을 생성합니다
SQL> CREATE USER [계정명] IDENTIFIED BY [패스워드];
ex) CREATE USER lucky IDENTIFIED BY mypassword;

3. 권한 부여
SQL> GRANT [부여권한] TO [계정명]
ex) GRANT CONNECT, RESOURCE TO lucky;

4. 테이블 스페이스 할당량 세팅
SQL> ALTER USER [계정명] QUOTA [할당량] ON USERS;
ex) ALTER USER lucky QUOTA UNLIMITED ON USERS; -- 무제한
    ALTER USER lucky QUOTA 500M ON USERS;      -- 500MB로 제한
    ALTER USER lucky QUOTA 2G ON USERS;        -- 2GB로 제한

4. 정리

Docker Hub에는 공식 Oracle 이미지가 없지만, Oracle의 컨테이너 레지스트리에서 직접 받을 수 있습니다.

docker login을 사용해서 Oracle 레지스트리에 로그인 후 docker를 통해 image를 pull하고

SQLPlus를 통해 sysdba 계정으로 로그인 후 신규 계정을 생성하여 Oracle을 사용 할 수 있습니다.

1. 투 포인터(Two Pointers) 알고리즘이란?

투 포인터는 두 개의 포인터를 이용하여 배열이나 리스트를 탐색하는 알고리즘 기법이다.
주로 정렬된 배열에서 특정 조건을 만족하는 쌍을 찾거나, 연속된 부분 배열을 탐색하는 문제에서 사용된다.

이 방법을 사용하면 O(N^2) 복잡도의 문제를 O(N)으로 최적화할 수 있습니다.


2. 투 포인터 동작 원리

  • 시작점과 끝점에서 이동
  • 오름차순 정렬된 배열에서 특정 합(target)을 찾을 때 사용
  • 좌측 포인터(left)는 작은 값부터, 우측 포인터(right)는 큰 값부터 이동
  • 두 포인터를 조절하면서 특정 조건을 만족하는 값을 찾는다.

아래 예제를 통해 원리에 대한 설명 하겠습니다.

 

특정 합을 가지는 두 숫자 찾기 (Two Sum)

문제: 정렬된 배열에서 합이 9 되는 두 숫자의 인덱스를 찾는다.

 

입력 예시

int[] nums = {1, 2, 3, 5, 7, 10, 12};
int target = 9;

 

투 포인터 진행 과정

1. left(🔴)는 처음 인덱스에서 시작, right(🔵)는 끝에서 시작

2. sum을 비교하면서 작으면 left++, 크면 right--

3. sum == target 이면 정답 찾음

단계 left 포인터 (🔴) right 포인터 (🔵) 합(sum) 진행 방향
초기 🔴1 (idx:0) 🔵12 (idx:6) 1 + 12 = 13 sum > target → right 감소
2 🔴1 (idx:0) 🔵10 (idx:5) 1 + 10 = 11 sum > target → right 감소
3 🔴1 (idx:0) 🔵7 (idx:4) 1 + 7 = 8 sum < target → left 증가
4 🔴2 (idx:1) 🔵7 (idx:4) 2 + 7 = 9 sum == target → 정답 찾음

 

최종결과 : [1, 4] → (nums[1] = 2, nums[4] = 7)

 

Java 코드

  • 시간 복잡도: O(N)
  • 두 개의 포인터를 사용하여 선형 탐색 진행
import java.util.Arrays;

public class MainClass {
	public static int[] twoSum(int[] nums, int target) {
        int left = 0, right = nums.length - 1;

        while (left < right) {
            int sum = nums[left] + nums[right];

            if (sum == target) {
                return new int[]{left, right}; // 정답
            } else if (sum < target) {
                left++; // 합이 작으면 왼쪽 포인터 증가
            } else {
                right--; // 합이 크면 오른쪽 포인터 감소
            }
        }
        return new int[]{-1, -1}; // 정답이 없을 경우
    }
	
	public static void main(String[] args) {
		int[] nums = {1, 2, 3, 5, 7, 10, 12};
		int target = 9;
        int[] result = twoSum(nums, target);
        
        // 최종결과: [1, 4]
        System.out.println("최종결과: " + Arrays.toString(result));
	}
}

3. 투 포인터 응용 예시

슬라이딩 윈도우를 활용한 부분 배열 최소 길이 찾기

문제 : 연속된 부분 배열의 합이 target 이상이 되는 최소 길이를 구하는 문제

          (left 포인터를 이동하며 구간을 줄여가면서 최적 해 찾기)

 

입력 예시

int[] nums = {2, 3, 1, 2, 4, 3};
int target = 7;

 

포인터 이동 과정

단계 left(🔴) right(🔵) 부분합(sum) 최소 길이
초기 🔴2 (idx:0) 🔵2 (idx:0) sum = 2 -
2 🔴2 (idx:0) 🔵3 (idx:1) sum = 5 -
3 🔴2 (idx:0) 🔵1 (idx:2) sum = 6 -
4 🔴2 (idx:0) 🔵2 (idx:3) sum = 8 minLength = 4
5 🔴3 (idx:1) 🔵2 (idx:3) sum = 6 -
6 🔴3 (idx:1) 🔵4 (idx:4) sum = 10 minLength = 3
7 🔴1 (idx:2) 🔵4 (idx:4) sum = 7 minLength = 3
8 🔴2 (idx:3) 🔵4 (idx:4) sum = 6 -
9 🔴2 (idx:3) 🔵3 (idx:5) sum = 9 minLength = 2

 

최소 길이 부분 배열: [4, 3] (길이: 2)

Java코드

  • 시간 복잡도: O(N)
  • 슬라이딩 윈도우(Sliding Window) 방식으로 최적화
  • 연속된 부분 배열을 유지하면서 최소 길이 갱신
public class MainClass {
	public static int minSubArrayLen(int target, int[] nums) {
        int left = 0, sum = 0, minLength = Integer.MAX_VALUE;

        for (int right = 0; right < nums.length; right++) {
            sum += nums[right]; // 오른쪽 포인터 확장

            while (sum >= target) { // 조건을 만족하면 최소 길이 업데이트
                minLength = Math.min(minLength, right - left + 1);
                sum -= nums[left]; // 왼쪽 포인터 축소
                left++;
            }
        }

        return (minLength == Integer.MAX_VALUE) ? 0 : minLength;
    }

    public static void main(String[] args) {
        int[] nums = {2, 3, 1, 2, 4, 3};
        int target = 7;
        int result = minSubArrayLen(target, nums);

        System.out.println("최소 길이: " + result); // 최소 길이: 2 (부분 배열: [4,3])
    }
}

4. 투 포인터 알고리즘이 적합한 문제 유형

1) 정렬된 배열에서 특정 조건을 만족하는 두 수 찾기

  • Two Sum 문제 (합, 차이)
  • 배열에서 두 숫자의 차이가 K인 쌍 찾기
  • 배열에서 곱이 특정 값을 만족하는 두 수 찾기

2) 연속된 부분 배열 문제

  • 최소 길이의 부분 배열 찾기
  • 가장 큰 합을 가지는 연속된 부분 배열 찾기

3) 문자열 처리 문제

  • 가장 긴 중복 없는 부분 문자열 찾기
  • 애너그램(Anagram) 검사

4) 백준 및 프로그래머스 관련 문제

  • 백준 - 3273번: 두 수의 합, 2470번: 두 용액, 1806번: 부분합, 1644번: 소수의 연속합, 2003번: 수들의 합 2
  • 프로그래머스 - 레벨 2: 구명보트, 레벨 3: 보석 쇼핑, 레벨 2: 연속된 부분 수열의 합

5. 정리

투 포인터 알고리즘은 정렬된 배열에서 최적의 방법으로 특정 조건을 만족하는 요소를 찾는 강력한 알고리즘 입니다.

  • 브루트포스 대비 O(N²) → O(N)으로 성능 최적화 가능
  • 슬라이딩 윈도우와 결합하여 다양한 문제 해결 가능
  • 데이터 크기가 클수록 효과적인 알고리즘

위와같은 장점도 있지만, 투포인터 알고리즘은 중복된 값이 있는 경우 차가적인 고려가 필요합니다. 또한 배열이나 리스트에서는 효과적이지만 트리, 그래프, 해시맵과 같은 비선형 자료구조에서는 적용 하기 어렵다는 단점이 있습니다.

 

1. Chunked Transfer Encoding이란?

Chunked Transfer Encoding은 HTTP/1.1에서 도입된 데이터 전송 방식으로, 데이터를 고정된 크기가 아닌, 여러 개의 작은 청크(Chunk)로 나누어 전송하는 방식이다. 이 방식은 서버가 모든 데이터를 한 번에 생성하지 않아도, 데이터가 준비되는 대로 클라이언트에게 스트리밍 방식으로 전송할 수 있도록 해주고, Content-Length를 명시하지 않고도 데이터를 스트리밍 방식으로 전송할 수 있도록 해줍니다. 즉, 응답 본문의 크기를 미리 알 수 없을 때 유용하게 사용됩니다.


2. Chunked Transfer Encoding의 장점과 단점

장점

Chunked Transfer Encoding(청크 전송 인코딩)은 HTTP/1.1에서 응답 크기를 미리 알 수 없을 때, 데이터를 조각(chunk) 단위로 전송하는 방식입니다. 이 방식은 여러 상황에서 유용한 이점을 제공합니다.

1) 응답 크기를 미리 알 필요 없음

  - Content-Length를 사용할 경우, 서버는 응답의 크기를 미리 계산해야 함.

  - 하지만, Chunked Transfer Encoding은 데이터가 생성되는 대로 전송 가능하므로 응답 크기를 사전에 알 필요가 없음.

  - 예시 상황

  • 실시간 로그 스트리밍
  • 동적 콘텐츠 생성 (대량 데이터 처리)
  • 점진적 웹 페이지 렌더링 (Server-Sent Events)

2) 빠른 응답 가능 (Low Latency)

  - Chunked Transfer Encoding을 사용하면 서버가 모든 데이터를 준비하지 않아도 즉시 응답을 시작할 수 있음.

  - 데이터를 점진적으로 전송하여 클라이언트가 일부 데이터를 먼저 처리할 수 있도록 함.

  - 특히 느린 데이터베이스 쿼리 또는 API 요청이 포함된 응답에서 효과적.

3) 실시간 데이터 전송 및 스트리밍 지원

  - Chunked Transfer Encoding을 사용하면 스트리밍 데이터(예: 로그, 실시간 API, 비디오 등)를 지속적으로 전송 가능.

  - 예시 상황

  • 실시간 로그 전송
  • WebSocket 대체용 Server-Sent Events (SSE)
  • 대량 데이터 처리 API (예: AI 모델 예측 결과 점진적 제공)

4) 서버 리소스 절약

  - Content-Length 방식은 응답 데이터를 모두 준비한 후 전송하므로 서버의 메모리를 많이 사용할 수 있음.

  - 반면, Chunked Transfer Encoding은 데이터를 작은 조각으로 나누어 바로 전송하므로, 메모리 사용량을 줄일 수 있음.

  - 서버가 한 번에 모든 데이터를 메모리에 로드하지 않아도 되므로, RAM 사용량이 줄고, 효율적인 자원 관리가 가능.

5) 클라이언트가 부분적으로 데이터를 사용할 수 있음

  - 일반적인 HTTP 응답(Content-Length)은 데이터를 모두 수신한 후에야 사용할 수 있음.

  - 하지만 Chunked Transfer Encoding을 사용하면, 클라이언트가 데이터를 순차적으로 받아 바로 사용할 수 있음.

  - 예시 상황

  • 프론트엔드에서 데이터 점진적 렌더링 (예: 페이지 로딩 속도 향상)
  • 비동기 요청 처리 (대용량 데이터 API에서 JSON 데이터 일부만 먼저 처리)

6) HTTP Keep-Alive와 함께 사용 가능

  - Chunked Transfer Encoding은 HTTP/1.1의 Keep-Alive 연결과 함께 사용 가능.

  - 한 번의 연결을 유지하면서 여러 개의 청크를 지속적으로 전송 가능 → 연결을 여러 번 맺을 필요 없이 성능 향상.

  - 예시 상황

  • 실시간 금융 데이터 피드
  • IoT 센서 데이터 스트리밍
  • 대량 파일 업로드 후 처리 상태 전송

단점

Chunked Transfer Encoding(청크 전송 인코딩)은 동적 데이터를 스트리밍 방식으로 전송하는 데 유용하지만, 몇 가지 단점이 있습니다.

 

1) 클라이언트에서 청크 데이터 처리 필요

  - 일반적인 HTTP 응답과 달리, 클라이언트는 청크 크기를 해석하고 데이터를 조립해야 함.

  - 일부 단순한 클라이언트(옛날 웹 브라우저, 특정 IoT 장치 등)는 Transfer-Encoding: chunked를 제대로 지원하지

    않을 수 있음.

  - 예시 문제: 청크 응답을 처리할 수 없는 클라이언트가 데이터를 제대로 렌더링하지 못할 수 있음.

  - 해결 방법: 최신 브라우저 및 HTTP 라이브러리를 사용하고, API 응답을 JSON으로 변환하여 관리.

 

2) 추가적인 네트워크 및 성능 오버헤드

  - 각 청크마다 크기 정보(16진수)와 개행 문자(\r\n)가 포함되므로, 데이터 크기가 약간 증가할 수 있음.

  - HTTP 헤더와 비교하면 큰 차이는 없지만, 고빈도 요청이 발생하는 경우 네트워크 대역폭을 조금 더 소비할 수 있음.

  - 해결 방법:

  • 작은 청크 전송을 최소화하고, 가능한 경우 일정 크기의 청크로 묶어서 전송.
  • JSON, 압축(Gzip) 등을 활용하여 전송 데이터 크기를 최적화.

3) HTTP/2 및 HTTP/3에서는 불필요

  - HTTP/2 및 HTTP/3는 멀티플렉싱과 스트리밍이 기본적으로 지원되므로 Chunked Transfer Encoding이 필요 없음.

  - HTTP/1.1에서는 유용하지만, 최신 프로토콜에서는 효율성이 떨어짐.

  - 해결 방법: 가능하면 HTTP/2 이상을 사용하여 더 효율적인 데이터 전송 구현.

 

4) 중간 프록시/캐시 서버와의 비호환성

  - 일부 프록시 서버(Nginx, Cloudflare, Squid 등)는 Chunked Transfer Encoding을 지원하지 않거나 제한할 수 있음.

  - 캐시 서버는 전체 응답 크기를 알아야 하기 때문에 청크 데이터는 캐싱하기 어려움.

  - 해결 방법:

  • 프록시 또는 로드 밸런서를 사용한다면 Chunked Encoding 지원 여부 확인.
  • 캐싱이 필요한 경우 Content-Length 방식으로 전송.

5) 서버에서 청크 전송 도중 연결이 끊길 경우, 클라이언트에서 응답을 제대로 처리하지 못할 수 있음

  - 청크 전송 도중 네트워크 오류가 발생하면, 클라이언트는 불완전한 데이터를 수신할 수 있음.

  - 일반 HTTP 응답(Content-Length 포함)에서는 클라이언트가 전체 데이터를 받을 때까지 기다릴 수 있지만,

    청크 응답에서는 데이터 일부가 누락될 가능성이 있음.

  - 해결 방법:

  • 클라이언트 측에서 타임아웃 및 오류 핸들링 로직 추가.
  • 서버에서는 적절한 연결 유지 정책 및 재시도(retry) 로직 구현.

6) 데이터 압축(Gzip, Brotli)과 함께 사용하기 어려움

  - Chunked Transfer Encoding과 Content-Encoding: gzip을 함께 사용할 경우, 일부 서버 및 클라이언트에서

    정상적으로 해석되지 않는 문제가 발생할 수 있음.

  - 이유: 압축 데이터는 완전한 데이터 세트가 있어야 해석 가능하지만, 청크 방식은 데이터가 나눠져 있기 때문에

    압축된 청크가 올바르게 해석되지 않을 수 있음.

  - 해결 방법:

  • 압축이 필요한 경우 Content-Length를 사용하는 일반 HTTP 응답 방식으로 전송.
  • 서버에서 미리 압축된 데이터를 생성한 후 전송.

3. Chunked Transfer Encoding의 동작 방식

서버가 클라이언트에 응답을 줄 때 데이터를 작은 블록 단위로 보낸다면 다음과 같은 HTTP 응답을 보낼 수 있습니다.

여기서 각 청크(Chunk)는 데이터의 크기와 내용을 순차적으로 전송 합니다.

 

- Content-Length 헤더 없이 데이터를 여러 개의 "청크(chunk)"로 나누어 전송
- 각 청크 앞에는 해당 청크의 크기를 16진수로 표시
- 마지막 청크 크기는 0으로 전송하며, 이는 응답의 끝을 의미

 

HTTP 응답 (Chunked Encoding 적용)

형식

[청크 크기(16진수)]\r\n
[청크 데이터]\r\n
...
0\r\n
\r\n

 

각 청크는 크기(16진수) + 데이터로 이루어지고, 마지막 청크는 크기 `0`을 전송하여 전송 완료를 알림.

 

예제

Content-Length 사용 (정적 데이터)

HTTP/1.1 200 OK
Content-Length: 20
Content-Type: text/plain

Hello, this is text.

Content-Length는 HTTP 응답 본문의 크기(바이트 단위)를 나타냅니다.

 

Chunked Transfer Encoding 사용 (동적 데이터)

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: text/plain

5\r\n
Hello\r\n
7\r\n
, World!\r\n
0\r\n
\r\n

 

"Hello,World!"가 조각(Chunk)으로 나누어 전송되고, 해당 응답을 분석하면 아래와 같이 데이터를 응답하게 된다.

 

  • 5 → 첫 번째 청크 크기 (16진수, 5 = 10진수로 5 바이트)
  • Hello → 첫 번째 청크 데이터
  • 7 → 두 번째 청크 크기 (16진수, 7 = 10진수로 7 바이트)
  • , World! → 두 번째 청크 데이터
  • 0 → 마지막 청크 (전송 종료 의미)

 

정적 vs 동적 데이터 전송 비교

구분 정적 데이터 전송 동적 데이터 전송
데이터 특성 고정된 크기의 데이터 실시간 생성되는 데이터
Content-Length 사용 여부 보통 Content-Length 사용 (일부 경우 Chunked) Content-Length 없이 Chunked 사용
응답 흐름 한 번에 응답 전송 데이터가 생성될 때마다 전송
예제 HTML, CSS, JSON 파일 다운로드 실시간 로그, 스트리밍 API 응답
전송 방식 데이터 전체 준비 후 전송 데이터가 준비될 때마다 조각으로 전송

4. 정리

Chunked Transfer Encoding은 실시간 데이터 전송, 빠른 응답, 서버 리소스 절약, 로그 전송, 대량 데이터 처리 등의 장점이 있어 API, 스트리밍, 대규모 데이터 처리 등에 적합하다. 하지만 네트워크 오버헤드, 캐시 문제, HTTP/2 미지원 등의 한계가 있으므로 상황에 맞게 사용해야 한다고 생각 한다.

'기타 > CS' 카테고리의 다른 글

[HTTP] HyperText Transfer Protocol  (0) 2025.03.02

+ Recent posts