Загружаем каталог…
Загружаем каталог…
NewVent 3편. 1·2편은 모델 벤치마크 얘기였고, 이번은 그 뒤에 벌어진 일이다. 구현·컨테이너화·배포 준비·프론트 연동을 하면서 만난 것들. 1. 지금까지 만든 것 벤치마크가 끝나고 실제 코드로 옮긴 것들. 상태 BedrockClient Converse API, gemma-3-27b. stopReason == MAX_TOKENS 로 절단 판정 컨테이너화 멀티스테이지 빌드. 659MB → 422MB 인프라 EC2(t3.small) + Elastic IP + DuckDNS + SSM 접속 IAM 모델 단위 최소권한. gemma·임베딩만 열고 Claude 계열은 차단 채팅 수정 라우터 1회 + 블록별 재시도. 못 알아들으면 되묻기 RAG pgvector, 1024차원, HNSW 코사인 인덱스 개인정보 필터 감지되면 확인 전까지 LLM 호출 보류 인증 경로·쿠키를 사용자/관리자로 분리 IAM 을 모델 단위로 좁힌 건 벤치마크에서 배운 것이다. 2편에서 Haiku 를 쓰다가 비용의 대부분을 거기서 썼다. 그래서 팀원에게 나눠줄 키는 모델 3개만 부를 수 있게 만들었다. 정책 시뮬레이션으로 확인했다. google.gemma-3-27b-it allowed amazon.titan-embed-text-v2:0 allowed embed-multilingual-v3 allowed us.anthropic.claude-haiku-4-5-20251001-v1:0 implicitDeny us.anthropic.claude-sonnet-5 implicitDeny 실수로든 호기심으로든 비싼 모델을 부를 수 없다. 개인 계정에 월 $10 예산을 걸어둔 상태에서는 이게 예산 알림보다 확실하다. 2. JWT 오류 앱이 이 메시지로 죽었다. WeakKeyException: The specified key byte array is 104 bits which is not secure enough for any JWT HMAC-SHA algorithm. .env 의 JWT_SECRET 은 47자였다. 376비트다. 충분하다. 그런데 앱은 104비트를 봤다. 104비트는 13바이트다. 13자짜리 문자열이 뭘까 세어봤다. ${JWT_SECRET} → 13자 치환되지 않은 플레이스홀더가 그대로 키가 됐다. application.yaml 에 secret: ${JWT_SECRET} 이 기본값 없이 적혀 있었고, 환경변수가 없으면 이 문자열이 값으로 들어간다. 왜 환경변수가 없었나. .env 는 Docker Compose 가 읽는 파일이고 JVM 은 읽지 않는다. IDE 로 띄우면 아무것도 전달되지 않는다. DB 설정은 ${DB_PASSWORD:123} 처럼 기본값이 있어서 조용히 넘어갔고, JWT_SECRET 만 기본값이 없어서 리터럴이 새어 나왔다. 무서운 건 자리표시 문자열이 32바이트를 넘었다면 아무 오류 없이 떴을 거라는 점이다. 누구나 아는 값으로 JWT 를 서명한 채로. 팀에서 고친 방식이 좋았다. 설정을 읽는 순간 검사한다. if (secret == null || secret.isBlank() || secret.contains("${")) { throw new IllegalStateException("JWT_SECRET 이 없습니다. (openssl rand -base64 48)"); } contains("${") 한 줄이 이 클래스의 사고를 다 막는다. 그리고 값은 메시지에 싣지 않고 길이만 알려준다. 3. AWS Bedrock API 오류 RAG 담당 팀원이 Cohere 임베딩을 부르는데 AccessDeniedException 이 났다. IAM 정책에 모델 ARN 을 넣었는데도 그랬다. 정책 시뮬레이션은 통과라고 했다. bedrock:InvokeModel cohere.embed-multilingual-v3 → allowed 정책을 아무리 고쳐도 안 풀리는 게 당연했다. 정책은 처음부터 맞았다. 모델 가용성을 조회하니 답이 나왔다. { "authorizationStatus": "AUTHORIZED", // IAM 은 통과 "regionAvailability": "AVAILABLE", "entitlementAvailability": "AVAILABLE", "agreementAvailability": { "status": "NOT_AVAILABLE" } // 여기 } Bedrock 은 서드파티 모델을 쓰기 전에 제공사 약관 동의 를 요구한다. IAM 과 완전히 다른 관문인데, 막힐 때 나오는 건 똑같이 AccessDenied 다. 그래서 전부 IAM 문제로 보인다. Cohere 모델은 전부 NOT_AVAILABLE 이었고, 이미 쓰던 gemma·Titan 은 AVAILABLE 이었다. Amazon·Google 모델은 자동으로 열려 있었고 Cohere 만 따로 동의가 필요했던 것이다. 약관은 계정 단위, 리전별 이다. 한 번 동의하면 그 계정의 모든 IAM 사용자와 EC2 역할이 쓴다. 팀원이 각자 할 일은 없다. 대신 리전을 옮기면 다시 해야 한다. 4. AWS 자격증명 오류 배포 없이 백엔드·프론트를 붙여보려고 로컬에서 끝까지 돌렸다. 로그인 → 이벤트 생성 → LLM 생성 → 폴링 → 미리보기. 전부 200 이었다. E2E 통과라고 적었다. 며칠 뒤 컨테이너에 AWS 자격증명을 안 넣고 같은 흐름을 돌렸는데 또 성공했다. 자격증명이 없는데 LLM 생성이 성공할 수는 없다. 단서는 응답에 있었다. { "phase": "DONE", "versionId": 75, "attempt": null } attempt 가 null 이었다. 재시도 카운터인데 값이 없다는 건 모델을 한 번도 안 불렀다 는 뜻이다. 생성 API 에 templateCode 를 주면 템플릿 경로로 가고, 그 경로는 템플릿 HTML 을 그대로 쓴다. 모델을 부르지 않는다. 모델을 타는 건 templateCode 를 생략한 백지 경로뿐이다. 즉 내 E2E 테스트는 파이프라인은 검증했지만 모델 호출 구간을 한 번도 지나지 않았다. 그것도 모르고 "끝까지 돌았다"고 보고했다. 백지 경로로 다시 하니 정상적으로 실패했다. phase: FAILED message: "페이지 생성 서버에 연결하지 못했습니다. 잠시 후 다시 시도해 주세요." 그런데 이 문구도 문제였다. "잠시 후 다시 시도해 주세요" 는 일시적 장애처럼 읽힌다. 실제 원인은 설정 누락인데 관리자는 네트워크를 의심하고 재시도한다. 재시도는 호출 기록에 실패 행을 남기고, 그 행은 하루 호출 상한을 깎는다. 호출은 못 했는데 예산은 줄어든다. 그래서 기동 시점에 자격증명을 확인하는 검사를 넣었다. 없으면 앱이 아예 뜨지 않고 원인을 한국어로 알려준다. 5. AWS_ACCESS_KEY 오륮 컨테이너 설정에 필수 변수 검사를 넣었다. 값이 없으면 멈추게. AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID:?키가 필요합니다}" 의도는 좋았는데 부작용이 있었다. docker compose down 도 막혔다. Compose 는 모든 서브커맨드가 먼저 설정 모델을 만들고 , 치환은 그 파싱 단계에서 일어난다. down 이 키를 필요로 하는 게 아니라, Compose 가 설정을 못 만들어서 무엇을 내려야 할지 모르는 상태가 된다. config · ps · logs · kill 전부 같이 막혔다. 여기서 " down 을 조심해야 한다"와 "필수 변수 검사"를 한 문단에 섞어 써서, 두 문제를 하나로 만들어버렸다. 실제로는 완전히 별개 였다. 검사를 지우고 mock 만으로 재현해보니 여전히 이랬다. docker compose down Container newvent-postgres Removed Network backend_default Resource is still in use ← 실패 남은 컨테이너: newvent-app ← 고아 원인은 앱을 별도 오버라이드 파일에 정의한 구조였다. Compose 에게는 넘긴 -f 파일들이 곧 프로젝트의 정의 다. 기본 파일만 주면 앱을 자기 것으로 보지 않고 고아로 분류한다. 그리고 함부로 지우지 않는다 — 의도적으로 스택을 쪼개 쓰는 경우가 있으니까. 경고조차 뜨지 않는다. 유일한 단서가 Resource is still in use 라는 간접적인 실패 메시지다. "DB 는 내렸는데 8080 이 왜 살아 있지"로 헤매게 된다. --remove-orphans 를 붙이면 해결된다. 그리고 이건 필수 변수 검사를 없애도 그대로 남는다. 두 문제를 분리하고 나서야 그게 보였다. 앞으로 남은 것들 프론트 연동에서 SSE 전제를 폴링으로 바꾸는 작업 되묻기 상태를 실패와 구분해서 화면에 보여주기 nginx + certbot, 그리고 RDS 전환 검토 관리자 목록 API 와 생성 API 의 템플릿 코드 어휘 통일 등
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
NewVent 구현단계 트러블슈팅. NewVent 3편. 1·2편은 모델 벤치마크 얘기였고, 이번은 그 뒤에 벌어진 일이다. 구현·컨테이너화·배포 준비·프론트 연동을 하면서 만난 것들. 1. 지금까지 만든 것 벤치마크가 끝나고 실제 코드로 옮긴 것들. 상태 BedrockClient Converse API, gemma-3-27b. stopReason == MAX_TOKENS 로 절단 판정 컨테이너화 멀티스테이지 빌드. 659MB → 422MB 인프라 EC2(t3.small) + Elastic IP + DuckDNS + SSM 접속 IAM 모델 단위 최소권한. gemma·임베딩만 열고 Claude 계열은 차단 채팅 수정 라우터 1회 + 블록별 재시도. 못 알아들으면 되묻기 RAG pgvector,…
Открыть источник