[AJECT 기술 사례] Next.js 15·PostgreSQL·Nginx로 구축한 기업 홈페이지와 실전 배포 아키텍처

AJECT ENGINEERING · 구축 사례
기업 홈페이지를 하나의 운영 시스템으로 설계하다
Next.js 15 · TypeScript · PostgreSQL 16 · Node.js 24 · Nginx · systemd
홈페이지에서 고객이 문의 버튼을 눌렀을 때, 메일 서버가 느리거나 잠시 응답하지 않으면 어떻게 될까요? 화면이 완성돼 있어도 문의가 유실되거나 접수 결과가 불분명하면 실제 업무에 사용하기 어렵습니다. AJECT 홈페이지 구축은 이 질문에서 출발했습니다. 화면과 API, 데이터 저장, 알림 작업, HTTPS와 Linux 프로세스 관리를 하나의 서비스로 연결하고, 같은 서버에서 운영하던 PLUTUS 서비스와 함께 배포하는 것이 핵심 과제였습니다.
이 글의 범위 — 적용 스택과 배포 결과는 기존 구축 기록을 기준으로 정리했습니다. 트랜잭션·재시도·멱등성 등 추가 설계는 개선 권고로 명시했습니다. 현재 서버 소스나 운영 설정을 새로 감사한 결과는 아니며, 처리량·응답시간·가용성 수치를 측정한 성능 보고서도 아닙니다.
1. 기술 스펙: 각 구성 요소가 맡는 책임
| 영역 | 기록된 스펙 | 설계상 역할 |
|---|---|---|
| 웹 애플리케이션 | Next.js 15.5.27 / TypeScript | 화면과 API를 같은 프로젝트에서 관리 |
| 실행 환경 | Node.js 24 | 기존 Node.js 18을 변경하지 않고 전용 런타임 사용 |
| 데이터베이스 | PostgreSQL 16 | 문의·동의·관리 이력·알림 작업을 영속 저장 |
| 외부 진입점 | Nginx / HTTPS 443 | 도메인별 요청 분기와 TLS 처리 |
| 인증서 | Let’s Encrypt / DNS-01 | DNS TXT 레코드로 도메인 소유권 검증 |
| 프로세스와 예약 실행 | systemd service / timer | 웹 프로세스 관리와 메일 워커 실행 |
| 메일 | Nodemailer / Gmail SMTP | 고객 접수 알림과 관리자 알림 전달 |
버전은 구축 당시 기록이며 최신 버전 추천을 의미하지 않습니다. 라이브러리 선택만큼 중요한 것은 책임의 경계입니다. Nginx는 외부 연결을 받고, Next.js는 요청을 처리하며, PostgreSQL은 업무 기록을 보존하고, 워커는 메일 전달을 담당합니다. Next.js의 Route Handler는 HTTP 요청을 처리하는 API를 구성할 수 있지만, TypeScript의 정적 타입만으로 외부 입력이 검증되지는 않습니다. 문의 API에서는 문자열 길이, 이메일 형식, 필수 동의 값 등을 실행 시점에 검사해야 합니다.
2. 구조도: 하나의 HTTPS 진입점과 분리된 백엔드

AJECT 웹 애플리케이션은 127.0.0.1:3200에서 실행되고 Nginx가 외부 HTTPS 요청을 전달합니다. 방문자는 443번 포트로 접근하지만, 애플리케이션 포트를 외부에 직접 공개할 필요는 없습니다. Nginx는 도메인별 가상 호스트 설정으로 AJECT와 기존 PLUTUS 요청을 각각의 백엔드로 보냅니다.
같은 서버에서 여러 도메인이 443번 포트를 사용할 수 있는 이유는 도메인 정보를 기준으로 요청을 분기하기 때문입니다. HTTPS 연결에서는 SNI가 인증서 선택에 사용되고, HTTP 요청의 호스트 정보가 가상 호스트 라우팅에 관여합니다. 다만 프로세스와 도메인을 분리해도 CPU·메모리·디스크·서버 장애는 공유합니다. 이 구성은 런타임 변경의 영향을 줄이는 설계이며, 서버 수준의 고가용성을 제공하는 구성은 아닙니다.
3. 문의 접수와 메일 발송을 분리하면 무엇이 달라질까?
요청 안에서 메일을 직접 보내면 응답시간은 입력 검증, DB 저장, SMTP 연결과 전송 시간을 모두 포함합니다. SMTP가 지연되면 사용자는 문의가 접수됐는지 몰라 다시 제출할 수 있습니다. AJECT는 문의 데이터와 알림 작업을 DB에 기록하고, 별도 워커가 알림을 처리하는 구조를 사용했습니다. 웹 요청에서 SMTP 대기 시간을 분리하는 것이 목적입니다.

접수 성공과 메일 발송 성공은 서로 다른 상태입니다. 문의 저장이 완료돼도 워커가 아직 실행되지 않았다면 메일은 대기 상태일 수 있습니다. SMTP 서버가 메시지를 받아들였더라도 고객의 받은편지함 도착까지 보장되지는 않습니다. UI와 관리자 화면에서도 “접수 완료”, “알림 대기”, “전송 실패”를 구분하는 것이 업무 혼선을 줄입니다. 구체적인 상태값과 표시 기능의 적용 여부는 후속 점검 대상입니다.
4. 여섯 테이블로 나눈 데이터 모델
원문에 기록된 테이블은 다음과 같습니다. 아래 내용은 각 테이블의 책임을 설명하며, 실제 컬럼·외래키·인덱스 정의를 공개한 DB 스키마 명세는 아닙니다.
| 테이블 | 책임 | 운영에서 확인할 사항 |
|---|---|---|
inquiries |
문의 본문과 접수 정보 | 식별자, 조회 조건, 보관 기간 |
inquiry_notes |
관리자 내부 메모 | 작성자 추적, 고객 공개 데이터와 분리 |
inquiry_status_history |
처리 상태 변경 이력 | 변경 시각·주체·이전 상태 기록 |
privacy_consents |
개인정보 동의 기록 | 동의 문구 버전과 시각 연결 |
notification_outbox |
알림 발송 작업 | 대기·성공·실패 조회와 재처리 기준 |
inquiry_rate_limits |
문의 요청 제한용 기록 | 제한 기준과 만료 데이터 정리 |
문의 본문과 메모, 상태 이력을 분리하면 “현재 어디까지 처리됐는가”와 “누가 언제 변경했는가”를 따로 조회할 수 있습니다. 알림 작업을 업무 데이터와 분리하면 메일 장애를 문의 유실과 혼동하지 않고 조사할 수 있습니다. 다만 테이블이 존재한다는 사실만으로 권한 검증이나 요청 제한의 정확한 동작까지 검증된 것은 아닙니다.
5. Outbox의 신뢰성: 락만으로 모든 문제가 해결되지는 않는다
원문에 따르면 메일 워커는 PostgreSQL advisory lock을 사용해 중복 실행 충돌을 제어합니다. Advisory lock은 애플리케이션이 정한 키에 대해 협력적으로 잠금을 사용하는 기능입니다. 동일 규약을 따르는 워커끼리 실행을 조율할 수 있지만, 메일이 정확히 한 번 전달된다는 보장은 아닙니다.
예를 들어 SMTP 전송 직후, 성공 상태를 DB에 기록하기 전에 워커가 종료되면 다음 실행에서 같은 작업을 다시 보낼 가능성이 있습니다. 세션 수준 advisory lock을 사용한다면 같은 DB 연결에서 획득·해제하는지도 확인해야 합니다. 트랜잭션 수준 락은 트랜잭션 종료와 함께 해제되므로 작업 범위와 락 수명의 관계가 중요합니다.
개선 권고: 문의와 outbox 항목을 하나의 DB 트랜잭션으로 저장해 둘 중 하나만 남는 상황을 줄이고, 문의 식별자와 알림 유형을 조합한 작업 키로 중복 작업 생성을 제한하는 방식이 좋습니다. 실패 작업에는 시도 횟수, 다음 시도 시각, 마지막 오류를 기록하고 지수 백오프와 최대 재시도 횟수를 정해야 합니다. 이는 적용 완료된 기능 소개가 아니라 후속 구현·검증 항목입니다.
권장 설계의 의사코드 — 실제 운영 코드 아님
BEGIN
문의 저장
동일 문의에 대한 알림 작업 저장
COMMIT
접수 결과 응답
워커 실행
실행 잠금 획득
처리 대상 작업 조회
SMTP 발송 시도
성공 결과 또는 실패·재시도 정보 저장
잠금 해제
처리량이 많아지면 단일 워커 실행 잠금에서 작업별 점유 방식으로 확장할 수 있습니다. 하지만 작은 서비스에서는 별도 메시지 브로커를 추가하기 전에 DB outbox의 적체량과 실패 패턴을 관찰하는 편이 운영 복잡도를 낮춥니다. 확장 시점은 기술 유행보다 측정된 부하와 업무 지연으로 판단해야 합니다.
6. 기존 서비스를 유지하는 배포 절차
이번 구축에서는 시스템 기본 Node.js를 교체하지 않고 Node.js 24를 별도로 설치해 AJECT 전용 systemd 유닛에서 사용했습니다. 기존 서비스가 사용하는 런타임 경로를 유지하고, 신규 Nginx 가상 호스트 설정을 추가한 뒤 설정 검사와 reload를 수행한 것이 원문에 기록된 배포 방식입니다.
Nginx의 설정 검사는 문법과 참조 파일 등을 확인하며, 백엔드 기능 전체가 정상이라는 뜻은 아닙니다. Reload는 새 설정의 워커를 시작하고 기존 워커를 점진적으로 종료하는 방식이므로 연결 영향을 줄이는 데 유용합니다. 다만 애플리케이션 재시작이나 DB 마이그레이션까지 포함한 완전한 무중단 배포를 이 절차만으로 보장할 수는 없습니다.
# Nginx 운영 명령 예시
sudo nginx -t
# 설정 검사가 성공한 뒤 실행
sudo systemctl reload nginx
| 원문에 기록된 검증 | 결과 | 확인 범위 |
|---|---|---|
| Next.js 로컬 접근 | HTTP 200 | 해당 경로 응답 확인 |
| AJECT HTTPS 접근 | HTTP 200 | 도메인·TLS·프록시 경로 확인 |
| www 대표 주소 전환 | HTTP 301 | 대표 도메인 리디렉션 확인 |
| 기존 PLUTUS HTTPS 접근 | HTTP 200 | 기존 사이트 응답 확인 |
| 외부 네트워크 접속 | 접속 확인 기록 | 외부에서 홈페이지 도달 확인 |
위 결과는 기존 배포 시점의 기록이며 이번 글 보강 중 서버를 재측정한 값은 아닙니다. HTTP 200은 중요한 시작점이지만 관리자 인증, 문의 저장, 메일 전달, 부하 상황의 안정성까지 증명하지는 않습니다. 다음 단계는 실제 문의 한 건의 접수부터 관리자 조회와 알림 결과까지 연결해서 확인하는 것입니다.
7. 80번 포트가 막혀 있을 때 HTTPS를 적용하는 방법
HTTP-01 인증은 80번 포트로 검증 파일에 접근할 수 있어야 합니다. 이번 환경에서는 DNS-01을 사용해 도메인의 TXT 레코드로 소유권을 검증했습니다. 이 방식은 웹 서버의 80번 포트를 인증에 사용하지 않으므로 해당 포트가 열리지 않는 환경에서도 인증서를 발급할 수 있습니다.
인증서 발급과 자동 갱신은 별개의 운영 과제입니다. TXT 레코드를 수동으로 등록했다면 갱신 때도 작업이 필요할 수 있습니다. 자동화에는 DNS 제공자의 API 연동, 최소 권한의 자격 증명, 갱신 후 Nginx 반영, 만료 감시가 필요합니다. DNS-01이 80번 포트의 HTTP 방문자를 자동으로 HTTPS로 전환해 주는 것은 아니라는 점도 구분해야 합니다.
8. 다음 운영 단계: 무엇을 측정하고 검증할 것인가
후속 과제는 관리자 로그인과 문의 처리 전체 흐름의 최종 검증, 백업·복원 시험, 인증서 자동 갱신입니다. 소스와 환경 설정을 분리한 것은 기본 조치이며, 환경 파일의 접근 권한, 관리자 세션과 권한 검사, 로그의 개인정보 노출 여부도 함께 확인해야 합니다.
운영 지표는 문의 API 오류율과 지연, outbox 대기 작업 수와 가장 오래된 작업의 나이, 메일 실패 횟수, 디스크 사용량, 인증서 잔여 기간으로 시작할 수 있습니다. 문의 API가 빠르게 응답해도 알림 큐가 계속 쌓이면 실제 업무는 지연되고 있다는 신호입니다. 이 글에서는 실측값을 제시하지 않았으며, 목표 응답시간이나 동시 처리량은 부하 시험을 거쳐 정해야 합니다.
롤백도 애플리케이션 파일만 되돌리는 문제로 끝나지 않습니다. 이전 릴리스와 설정을 보관하고, DB 변경이 이전 버전과 호환되는지 확인해야 합니다. 백업 파일의 존재보다 실제 복원 가능 여부가 중요합니다. 같은 서버의 장애가 AJECT와 PLUTUS에 동시에 영향을 줄 수 있으므로, 서비스 성장에 맞춰 별도 호스트나 관리형 DB로의 분리도 검토할 수 있습니다.
9. AJECT가 이 사례에서 보여주려는 기술
기업 홈페이지의 기술적 가치는 사용하는 프레임워크 이름만으로 설명되지 않습니다. 사용자 요청을 업무 기록으로 남기고, 외부 메일 서비스의 지연을 접수 처리에서 분리하며, 기존 서비스의 실행 환경을 보호하고, 검증 결과의 의미와 한계를 설명할 수 있어야 합니다. AJECT는 이 구축 경험을 AI·데이터·소프트웨어 서비스의 설계와 운영으로 확장해 나갑니다.
화면에서 시작해 데이터와 운영까지 연결하는 소프트웨어.
AJECT 공식 홈페이지에서 서비스와 개발 문의 확인하기 →
참고한 공식 기술 문서
- Next.js — Route Handlers
- PostgreSQL 16 — Advisory Locks
- Nginx — 설정 검사와 reload 명령
- Let’s Encrypt — HTTP-01과 DNS-01
편집 기준: 2026년 10월 11일. 구조도는 본 구축 기록을 설명하기 위해 제작한 개념도이며, 제안 기능은 현재 구현 상태와 구분해 표기했습니다.