대용량 텍스트 검색 및 전송 최적화 엔진 (2) 대용량 페이로드 최적화 (上)

Pt J·2026년 6월 16일
post-thumbnail

대용량 텍스트 검색 및 전송 최적화 엔진 (2) 대용량 페이로드 최적화 (上)

더미 데이터 생성

Rust

원하는 라인 수(lines)를 입력받아
그 크기만큼의 문장 벡터(Vec<String>)를 생성해 반환하는
Rust FFI 함수를 작성할 것이다.

 ~/workspace/fast-text-search$ touch engine/src/generator.rs

engine/src/generator.rs

/// 지정된 줄 수만큼 더미 텍스트 데이터를 고속으로 생성합니다.
///
/// # Arguments
/// * `lines` - 생성할 줄 수.
pub fn generate_data(lines: usize) -> Vec<String> {
    let mut data = Vec::with_capacity(lines);
    for i in 0..lines {
        data.push(format!(
            "이것은 Rust 엔진에서 생성된 {i}번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다."
        ));
    }
    data
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_generate_data_count() {
        let lines = 100;
        let data = generate_data(lines);
        assert_eq!(data.len(), lines);
    }

    #[test]
    fn test_generate_data_content() {
        let data = generate_data(1);
        assert_eq!(data[0], "이것은 Rust 엔진에서 생성된 0번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.");
    }

	#[test]
    fn test_generate_data_boundary_zero() {
        let data = generate_data(0);
        assert_eq!(data.len(), 0);
    }
}

작성한 코드를 로직에 추가한다.

engine/src/lib.rs

use pyo3::prelude::*;

mod generator;

/// 엔진 모듈이 정상 로드되는지 확인하는 헬스 함수.
#[pyfunction]
fn engine_health() ->  PyResult<&'static str> {
    Ok("ok")
}

/// 지정된 줄 수만큼 더미 텍스트 데이터를 고속으로 생성하여 파이썬에 반환
#[pyfunction]
fn generate_dummy_data(lines: usize) -> PyResult<Vec<String>> {
    let result = generator::generate_data(lines);
    Ok(result)
}

/// `fast_text_engine` Python 확장 모듈.
#[pymodule]
fn fast_text_engine(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(engine_health, m)?)?;
    m.add_function(wrap_pyfunction!(generate_dummy_data, m)?)?;
    Ok(())
}

다음과 같이 단위 테스트를 진행할 수 있다.

~/workspace/fast-text-search/engine$ cargo test
   Compiling engine v0.1.0 (/Users/edenjint3927/workspace/fast-text-search/engine)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.12s
     Running unittests src/lib.rs (target/debug/deps/engine-edefff1e00f5569b)

running 3 tests
test generator::tests::test_generate_data_boundary_zero ... ok
test generator::tests::test_generate_data_content ... ok
test generator::tests::test_generate_data_count ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

단위 테스트를 문제 없이 통과하면 Python에서 사용할 수 있는 형태로 빌드한다.

~/workspace/fast-text-search/engine$ VIRTUAL_ENV=../gateway/.venv uv run --active maturin develop

Python

먼저 게이트웨이에서 반환할 대용량 텍스트 구조를 정의하는 Pydantic 스키마를 작성한다.

gateway/app/schemas.py

from pydantic import BaseModel

class HealthResponse(BaseModel):
    """API 게이트웨이 및 Rust 엔진의 헬스 상태 응답 스키마."""
    status: str
    engine: str

class TextDataResponse(BaseModel):
    """대용량 텍스트 생성 결과를 반환하는 응답 스키마."""
    lines: int
    data: list[str]

더미 텍스트 생성을 위한 API를 작성한다.

import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware

import fast_text_engine
from .config import settings
from .schemas import HealthResponse, TextDataResponse

# (중략)

@app.get("/api/data")
async def get_text_data(lines: int = 10000) -> TextDataResponse:
    """Rust FFI 엔진을 호출하여 대용량 더미 텍스트 데이터를 생성하고 반환합니다.

    Args:
        lines (int): 생성할 텍스트의 라인 수 (기본값: 10000).
   
    Returns:
        TextDataResponse: 총 라인 수와 텍스트 리스트를 포함한 응답 객체.
    """
    try:
        text_list = fast_text_engine.generate_dummy_data(lines)
        logger.info(f"대용량 텍스트 데이터 생성 완료: {lines} 라인")
    except Exception as e:
        logger.error(f"대용량 텍스트 데이터 생성 실패: {e}")
        raise HTTPException(status_code=500, detail="Rust FFI 엔진에서 데이터를 생성하는 중 에러가 발생했습니다.")

    return TextDataResponse(
        lines=len(text_list),
        data=text_list
    )

Python에 대해서도 테스트 코드를 작성해 보자.
지금까지 작성한 API에 대한 엔드포인트 테스트를 작성한다.

~/workspace/fast-text-search/gateway$ mkdir tests && touch tests/test_main.py

gateway/tests/test_main.py

from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_health_check() -> None:
    """/health API가 200 OK와 함께 올바른 스키마를 반환하는지 검증합니다."""
    response = client.get("/health")
    assert response.status_code == 200

    data = response.json()
    assert data["status"] == "ok"
    assert data["engine"] == "ok"

def test_get_text_data_default() -> None:
    """/api/data API 호출 시 파라미터가 없으면 기본값인 10000줄을 생성해 주는지 검증합니다."""
    response = client.get("/api/data")
    assert response.status_code == 200

    data = response.json()
    assert "lines" in data
    assert "data" in data
    assert data["lines"] == 10000
    assert len(data["data"]) == 10000

def test_get_text_data_custom() -> None:
    """/api/data API에 원하는 라인 수를 쿼리 매개변수로 지정했을 때 해당 크기만큼 생성되는지 검증합니다."""
    test_lines = 500
    response = client.get(f"/api/data?lines={test_lines}")
    assert response.status_code == 200

    data = response.json()
    assert data["lines"] == test_lines
    assert len(data["data"]) == test_lines

def test_get_text_data_invalid_type() -> None:
    """/api/data API에 잘못된 타입의 파라미터를 넘겼을 때, Pydantic 유효성 검사에 의해 422 에러가 나는지 검증합니다."""
    response = client.get("/api/data?lines=abc")
    assert response.status_code == 422

pytest 를 개발 의존성에 추가한 후 테스트를 수행한다.

~/workspace/fast-text-search/gateway$ uv add --dev pytest

pyproject.toml 파일에 pytest 와 관련된 경로 설정을 해 주어야
원활한 테스트가 가능하다.

gateway/pyproject.toml

[project]
name = "gateway"
version = "0.1.0"
description = "대용량 텍스트 검색 및 최적화 시스템의 FastAPI 게이트웨이"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "fastapi>=0.137.0",
    "pydantic-settings>=2.14.1",
    "uvicorn[standard]>=0.49.0",
]

[dependency-groups]
dev = [
    "httpx>=0.28.1",
    "mypy>=2.1.0",
    "ruff>=0.15.17",
    "maturin>=1.14.0",
    "pytest>=9.1.0",
]

[tool.pytest.ini_options]
pythonpath = ["."]
~/workspace/fast-text-search/gateway$ uv run pytest
=========================================================== test session starts ============================================================
platform darwin -- Python 3.12.13, pytest-9.1.0, pluggy-1.6.0
rootdir: /Users/edenjint3927/workspace/fast-text-search/gateway
configfile: pyproject.toml
plugins: anyio-4.13.0
collected 4 items                                                                                                                          

tests/test_main.py ....                                                                                                              [100%]

============================================================ 4 passed in 0.13s =============================================================

엔드포인트 테스트를 문제 없이 통과하면 게이트웨이 서버를 실행하여 확인한다.

[터미널 A | 게이트웨이 실행]

~/workspace/fast-text-search/gateway$ uv run uvicorn app.main:app --reload

[터미널 B | 응답 확인]

~$ curl -i "http://localhost:8000/api/data?lines=100"
HTTP/1.1 200 OK
date: Tue, 16 Jun 2026 01:10:43 GMT
server: uvicorn
content-length: 17912
content-type: application/json

{"lines":100,"data":["이것은 Rust 엔진에서 생성된 0번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 1번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 2번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 3번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 4번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 5번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 6번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 7번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 8번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 9번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 10번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 11번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 12번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 13번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 14번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 15번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 16번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 17번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 18번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 19번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 20번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 21번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 22번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 23번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 24번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 25번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 26번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 27번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 28번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 29번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 30번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 31번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 32번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 33번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 34번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 35번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 36번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 37번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 38번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 39번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 40번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 41번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 42번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 43번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 44번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 45번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 46번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 47번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 48번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 49번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 50번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 51번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 52번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 53번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 54번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 55번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 56번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 57번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 58번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 59번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 60번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 61번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 62번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 63번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 64번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 65번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 66번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 67번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 68번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 69번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 70번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 71번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 72번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 73번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 74번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 75번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 76번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 77번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 78번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 79번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 80번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 81번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 82번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 83번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 84번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 85번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 86번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 87번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 88번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 89번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 90번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 91번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 92번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 93번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 94번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 95번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 96번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 97번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 98번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다.","이것은 Rust 엔진에서 생성된 99번째 줄 더미 데이터입니다. 대용량 페이로드 최적화 테스트를 위해 반복되는 텍스트 세그먼트입니다."]}%

응답 압축 미들웨어 도입

🤖 AI AGENT | Brotli / GZip 응답 압축 미들웨어 도입

대용량 텍스트(예: 10,000줄 이상의 JSON 데이터)를 클라이언트에 전송할 때 발생하는 가장 큰 병목은 네트워크 대역폭입니다. 텍스트 데이터는 압축률이 매우 높기 때문에(보통 70~80% 이상 압축 가능), 전송 전에 서버에서 압축을 수행하면 응답 속도를 비약적으로 단축할 수 있습니다.

이번 단계에서는 실무 최적화 표준인 Brotli와 GZip 압축 미들웨어를 API 게이트웨이에 추가해 보겠습니다.

압축 처리 전략 (실무 패턴)

  • Brotli: 최신 브라우저가 모두 지원하며, GZip 대비 압축률이 15~20% 더 뛰어납니다. (대용량 텍스트 최적화 핵심)
  • GZip: Brotli를 지원하지 않는 아주 오래된 레거시 브라우저를 위한 폴백(Fallback) 용도로 사용합니다.
  • 동작 원리: 클라이언트가 요청 헤더에 Accept-Encoding: br, gzip 을 실어 보내면, 게이트웨이가 Brotli로 압축하여 응답하고(Content-Encoding: br), 지원하지 않는 환경일 경우 GZip으로 폴백하여 응답합니다.

Python

FastAPI는 기본적으로 GZip 미들웨어만 내장하고 있으므로,
Brotli를 처리해 줄 고성능 ASGI 미들웨어를 의존성에 추가해야 한다.

~/workspace/fast-text-search/gateway$ uv add brotli-asgi

main.py 에서 FastAPI 인스턴스에 미들웨어를 등록한다.

🤖 AI AGENT | 주의할 것

FastAPI/Starlette에서 미들웨어는 코드가 실행(등록)되는 역순으로 요청을 처리합니다. 따라서 브라우저가 보낸 Accept-Encoding 헤더에 맞춰 Brotli가 먼저 매칭되게 하려면 BrotliMiddleware가 GZipMiddleware보다 나중에 등록(코드 상 하단) 되어야 정상적으로 Brotli 우선 압축이 작동합니다.

gateway/app/main.py

import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from brotli_asgi import BrotliMiddleware

import fast_text_engine
from .config import settings
from .schemas import HealthResponse, TextDataResponse

# 로깅 설정
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
logger = logging.getLogger("gateway")

@asynccontextmanager
async def lifespan(app: FastAPI):
    """FastAPI 애플리케이션의 수명 주기를 관리하는 컨텍스트 매니저."""
    logger.info("Fast Text Search Gateway 서버 기동...")
    logger.info(f"설정 로드 완료 - Host: {settings.GATEWAY_HOST}, Port: {settings.GATEWAY_PORT}")

    yield

    logger.info("Fast Text Search Gateway 서버 종료...")

app = FastAPI(
    title="Fast Text Search Gateway",
    description="대용량 텍스트 최적화 전송 및 Elasticsearch 검색을 처리하는 API 게이트웨이",
    version="0.1.0",
    lifespan=lifespan
)

app.add_middleware(GZipMiddleware, minimum_size=1000)
app.add_middleware(BrotliMiddleware, quality=4, minimum_size=1000)

# (후략)

요청에 따른 응답 헤더 차이를 비교해 보자.

[압축을 하지 않을 경우]

~$ curl -I -X GET "http://localhost:8000/api/data?lines=10000"

HTTP/1.1 200 OK
date: Tue, 16 Jun 2026 01:37:24 GMT
server: uvicorn
content-length: 1808914
content-type: application/json
vary: Accept-Encoding

[Brotli로 압축할 경우]

~$ curl -H "Accept-Encoding: br" -I -X GET "http://localhost:8000/api/data?lines=10000"

HTTP/1.1 200 OK
date: Tue, 16 Jun 2026 01:36:16 GMT
server: uvicorn
content-length: 12464
content-type: application/json
vary: Accept-Encoding, Accept-Encoding
content-encoding: br

[GZip으로 압축할 경우]

~$ curl -H "Accept-Encoding: gzip" -I -X GET "http://localhost:8000/api/data?lines=10000"

HTTP/1.1 200 OK
date: Tue, 16 Jun 2026 01:37:01 GMT
server: uvicorn
content-length: 30402
content-type: application/json
vary: Accept-Encoding
content-encoding: gzip
🤖 AI AGENT | 압축 성능 비교 분석

터미널 로그에 기록된 content-length 수치들을 보면 압축 최적화의 놀라운 성과를 눈으로 직접 확인하실 수 있습니다:

  • 원본 텍스트 전송 (무압축): 1,808,914 바이트 (약 1.8 MB)
  • GZip 압축 전송: 30,402 바이트 (약 30 KB, 약 98.3% 압축!)
  • Brotli 압축 전송: 12,464 바이트 (약 12 KB, 약 99.3% 압축!)

네트워크 대역폭 사용량이 1.8MB에서 12KB로 획기적으로 줄어든 것을 직접 확인하셨습니다. 이것이 바로 대용량 텍스트 파이프라인에서 압축 전송 최적화가 필수적인 이유입니다.

profile
Peter J Online Space - since July 2020 | 아무데서나 채용해줬으면 좋겠다 (지금은 학생 때 하던 거 아무거나 공부하고 있고요, 취업시켜 주시면 그 분야로 공부할게요)

0개의 댓글