원하는 라인 수(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.rsuse 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
먼저 게이트웨이에서 반환할 대용량 텍스트 구조를 정의하는 Pydantic 스키마를 작성한다.
gateway/app/schemas.pyfrom 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.pyfrom 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으로 폴백하여 응답합니다.
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.pyimport 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로 획기적으로 줄어든 것을 직접 확인하셨습니다. 이것이 바로 대용량 텍스트 파이프라인에서 압축 전송 최적화가 필수적인 이유입니다.