프록시, 리버스 프록시
드라마 "참교육" 보다가 리버스 프록시라는 개념이 나왔다.
- 프록시(Forward Proxy) - 클라이언트 대신 서버에 요청을 전달하는 것
- '클라이언트 → 프록시 → 서버' 구조로 되어있다.
- CORS 에러 해결할 때 사용하는 프록시가 이것임
- 서버는 클라이언트의 주소를 모르고 프록시가 요청을 대신 보내는 역할
- 리버스 프록시(Reverse Proxy) - 원본 서버 대신에 요청을 처리하는 것
- 서버에 요청이 들어오면 서버가 아닌 리버스 프록시가 해당 요청을 처리한다.
- 트래픽 분산 효과 / 서버 안정성 향상 / 로드밸런싱 / DDos 방어
- 사용자는 실제 서버의 위치를 모른다.
∴ 프록시는 사용자를 숨기고, 리버스 프록시는 서버를 숨긴다.
ORM
쿼리 작성없이 DB 조작을 가능하게 하는 것 → 직관적으로 관리할 수 있음
Index in Table
테이블의 특정 컬럼을 빠르게 찾기 위한 색인 구조이다.
다음을 위해 사용한다:
- WHERE 조건으로 자주 조회하는 데이터를 빠르게 찾기 위해
- JOIN 시 연결 키를 빠르게 찾기 위해
- ORDER BY, GROUP BY 같은 정렬/집계 작업을 더 효율적으로 하기 위해
이를 사용하면:
- 조회 속도가 빨라지고
- 대용량 테이블에서 효과가 크며
- 특정 조건 검색, 정렬, 조인 성능이 향상된다.
하지만 단점이 있는데:
- INSERT, UPDATE, DELETE가 느려질 수 있다.
- Index 도 저장 공간을 사용한다.
- 너무 많이 만들면 전체 성능이 떨어질 수 있다.
다음 상황에서 주로 사용한다:
- 자주 검색되는 컬럼
- JOIN 키
- Unique 해야 하는 컬럼
- 데이터 수가 많고 조회 비중이 큰 테이블
Index가 많아지면 왜 느려지는 걸까?
테이블 데이터만 바꾸는 게 아니라, 관련된 인덱스 구조도 같이 갱신해야 하기 때문이다.
- INSERT - 새 행을 넣을 때, 그 행의 값이 들어가는 모든 인덱스에도 새 항목을 추가해야 한다.
- UPDATE - 인덱스에 포함된 컬럼이 바뀌면, 기존 인덱스 항목을 지우고 새 값으로 갱신해야 한다.
- DELETE - 행을 지울 때, 인덱스에서도 해당 항목을 제거해야 한다.
즉, 인덱스가 없으면 주로 테이블 한 곳만 건드리면 되는데 인덱스로 인해서 테이블 + 인덱스를 동시에 수정해야 하기 때문이다.
또한, 인덱스는 보통 B-Tree 같은 구조라서:
- 중간에 새 값을 넣으려면 위치를 찾아야 하고
- 필요하면 페이지 분할 같은 재구조화가 발생하며
- 디스크 I/O와 메모리 작업이 늘어날 수 있다.
- 그래서 쓰기 작업이 많을 수록 인덱스 비용이 체감된다.
∴ 조회는 빨라지지만 업데이트 시 인덱스도 함께 관리해야 하기 때문에 성능이 느려질 수 있다.
테이블 파티셔닝
Analytics 서비스 만들다가 생긴 궁금증에서 출발: → 이벤트 데이터를 하나의 테이블로만 관리하면 데이터가 늘어날 수록 조회 성능이 느려질텐데 어떻게 관리하는 걸까?
1억 행의 events → 이 상태에서 데이터를 가져오게 된다면 엄청난 양의 데이터를 뒤져야 한다.
테이블 파티셔닝은 하나의 큰 테이블을 특정 기준에 따라 여러 개의 물리적 파티션으로 나누어 저장하는 방식이다.
events ← 부모 테이블
├── events_2026_08 ← 파티션
├── events_2026_09 ← 파티션
└── events_2026_10 ← 파티션
여러 테이블이 실제로 존재하지만, 어플리케이션에서는 하나의 테이블처럼 사용하고 DB는 조건에 맞는 파티션만 조회한다.
select * from events;
insert into events (...) values (...);
이를 사용하면:
- 대용량 데이터 조회 성능 개선
- 오래된 데이터의 삭제·보관 관리 용이
- 인덱스와 테이블 유지보수 범위 축소
- 날짜·지역·테넌트 단위 데이터 관리
멀티 테넌시
하나의 애플리케이션이 여러 고객이나 조직(테넌트)을 함께 서비스하는 구조를 말한다.
예를 들어, 사용자가 어떤 조직에 속할 수 있는 기능이 있고 해당 조직만 접근할 수 있는 페이지 및 기능이 있다면 멀티 테넌시이다. → 슬랙의 채널과 노션의 조직 페이지
Cloudflare Assets
웹사이트의 정적 파일을 올려두고 빠르게 보여주는 기능이다. (CDN을 이용하는 정적 파일 배포) 빌드 결과물의 HTML, CSS, JavaScript, 이미지, 폰트가 대상이며, Cloudflare가 전 세계 가까운 서버에서 캐시해 전달한다. 즉, API 서버가 이미지·JS 파일까지 직접 처리하지 않게 해 주는 “사이트 파일 배포·전달 담당”이라고 볼 수 있다.
반면, 사용자 업로드 파일(이미지, 비디오)과 같은 것은 보통 Assets 보다는 R2가 적합하다. R2와 Assets 모두 CDN를 활용한다는 것은 동일하지만, 두 서비스는 타겟이 다르다.
- R2: 이미지, 비디오 같은 미디어 자산
- Assets: 빌드 산출물 (이미지, 폰트, JS, HTML, CSS)
Monorepo
packages의 역할
둘 이상에서 쓰거나 독립적으로 관리할 가치가 있는 코드를 모아두는 공간이다. 예를 들어 백엔드와 프론트엔드 사이의 API 계약 관련 코드를 packages로 관리하면 진실 원천이 하나가 되기 때문에 수정과 관리에 용이하다.
다만, 항상 공용으로 사용하는 코드만 packages로 두는 것은 아니고, 설령 하나의 앱만 사용하더라도 “서비스 규칙을 담는 곳”으로 사용할 수도 있다. 즉, packages가 **"모듈"**로서의 역할도 하는 것이다. (책임을 분리하고, 서로 함부로 의존하지 못하게 하려고 분리)
catalog의 역할
모노레포 안에서 여러 패키지가 같은 의존성 버전을 공유하게 하는 기능이다.
루트 package.json:
{
"workspaces": {
"packages": ["apps/*", "packages/*"],
"catalog": {
"typescript": "^5.8.3",
"zod": "^3.25.0"
}
}
}
하위 패키지:
{
"dependencies": {
"zod": "catalog:"
},
"devDependencies": {
"typescript": "catalog:"
}
}
이렇게 하면 여러 패키지의 zod, typescript 의 버전을 각각 적지 않고 루트에서 한 번만 관리할 수 있다. (catalog를 사용하지 않으면 공통으로 사용함에도 불구하고 매번 패키지마다 버전을 관리해줘야 하는 불편함을 겪게 된다.)
그룹별로도 나눌 수 있는데, 그룹이란 일종의 역할로 보면 된다. (역할 예시: 테스트, UI, 인증 등등)
{
"catalogs": {
"testing": {
"vitest": "^3.0.0"
}
}
}
{
"devDependencies": {
"vitest": "catalog:testing"
}
}
이때, 그룹별로 나눠 관리한다면 catalogs 를 사용해야 한다. 위 예시는 catalog 목록 중에서 vitest는 testing 그룹으로 분리함을 명시한다.