한 글자 차이로 배열이 nil이 되는 Rails 버그와 압축 무한 루프

26년 06월 02일

부분 3, 4를 선택했는데 1, 2가 나온다

이날은 인프라가 조용히 잘못 동작하는 문제를 잡은 날이었다.

한 글자 차이로 배열이 사라지는 Rails 버그, 컨텍스트가 꽉 찰 때마다 무한 루프에 빠지는 압축 문제. 전부 “보이지 않는 곳에서 조용히 실패하는” 버그였다.

교육 플랫폼에서 부분을 여러 개 선택해서 일괄 변경하는 기능이 있었다.

3번이랑 4번을 선택했는데, 모달에 1번이랑 2번이 나왔다.

프론트엔드를 의심했다.

근데 프론트엔드는 정상이었다.

선택한 ID를 정확히 보내고 있었다.

파라미터도 제대로 인코딩됐다.

3이랑 4를 보냈다.

문제는 백엔드에 있었다.

Rails의 Strong Parameters에서 배열을 스칼라로 permit하고 있었다.

한 글자의 차이

Rails에서 파라미터를 허용할 때, 스칼라랑 배열을 다르게 써야 한다.

params.permit(:파트 식별자 목록)      # 스칼라만 허용 params.permit(파트 식별자 목록: [])   # 배열 허용

첫 번째는 스칼라만 받는다.

배열이 들어오면 nil로 만든다.

조용히. 에러 없이.

그러니까 프론트엔드가 3이랑 4를 배열로 보내면, 백엔드에서 nil이 된다.

nil이 되면 필터가 안 먹는다.

전체 부분이 반환된다.

페이지당 2개씩이라 1, 2가 나온 거다.

해결은 한 글자만 바꾸는 거다.

:파트 식별자 목록파트 식별자 목록: []로. 배열을 허용하게 만드는 거다.

조용히 실패하는 게 가장 위험하다

이 버그가 위험한 이유는 조용히 실패하기 때문이다.

에러가 안 난다.

배열이 들어오면 nil이 되고 nil이면 필터를 건너뛴다.

잘못된 결과가 나오는데 에러 로그는 없다.

에러가 나면 잡기 쉽다.

근데 조용히 잘못된 결과를 내면, 사용자가 발견할 때까지 모른다.

“왜 다른 부분이 나오지”라고 신고가 들어와야 비로소 알 수 있다.

컨텍스트 압축 무한 루프

두 번째는 에이전트 도구에서 컨텍스트 압축이 무한 루프에 빠지는 문제였다.

에이전트가 작업하다가 컨텍스트가 꽉 차면, 더 큰 컨텍스트 모델로 전환한다.

그리고 압축을 돌린다.

압축이 끝나면 원래 모델로 돌아간다.

근데 원래 모델이 여전히 컨텍스트를 못 담으면, 또 꽉 찬다.

또 큰 모델로 전환한다.

또 압축한다.

또 돌아간다.

또 꽉 찬다.

무한 루프다.

컨텍스트 게이트

해결은 돌아갈 때 컨텍스트를 확인하는 거다.

원래 모델이 현재 컨텍스트를 담을 수 있으면 돌아간다.

못 담으면 큰 모델에 머문다.

“무조건 돌아간다”를 “담을 수 있을 때만 돌아간다”로 바꾼 거다.

이렇게 하면 루프가 끊긴다.

원래 모델이 못 담으면 계속 큰 모델에 있는다.

세 개의 상태 플래그

그리고 세 개의 상태 플래그를 만들었다.

첫 번째는 반환 보류 플래그다.

원래 모델이 못 담을 때 설정한다.

진짜 사용자 프롬프트가 오면 해제한다.

두 번째는 시스템 프롬프트 보호 플래그다.

플러그인이 만든 연속 프롬프트가 반환 보류를 해제하지 못하게 막는다.

시스템 프롬프트가 루프를 다시 트리거하는 걸 방지한다.

세 번째는 자체 압축 카운터다.

큰 모델에서 자체 압축을 최대 2번까지만 돌린다.

2번 넘으면 더 안 돌린다.

이 세 가지가 합쳐서 무한 루프를 막는다.

단일 방어로는 부족하다.

여러 층으로 막아야 한다.

Jira 첨부 이미지 가져오기

마지막으로 Jira 첨부 이미지를 가져오는 문제도 해결했다.

Jira의 첨부 이미지는 인증이 걸려 있다.

CDN URL은 서명이 필요하고 서명이 14분 만에 만료된다.

그래서 프로그래밍으로 가져올 수 없다.

해결은 브라우저 자동화로 우회하는 거다.

인증된 브라우저 세션에서 직접 다운로드한다.

브라우저는 이미 인증되어 있으니까, 서명을 신경 쓸 필요가 없다.

이걸 스킬로 정리해서, 다음에 Jira 첨부가 필요하면 같은 방법으로 가져올 수 있게 했다.

마무리

내가 이날 배운 핵심은 다음과 같다.

이날의 버그는 전부 “조용히 잘못 동작하는” 버그였다.

Rails의 Strong Parameters는 배열을 nil로 만든다.

에러 없이. 컨텍스트 압축은 무한 루프에 빠진다.

에러 메시지 없이. Jira 첨부는 서명이 만료되어서 가져올 수 없다.

에러는 나지만 원인이 불분명하게. 조용히 실패하는 버그가 가장 위험하다.

에러가 안 나니까 발견이 늦다.

발견이 늦으면 영향 범위가 커진다.

이런 버그를 잡으려면 “왜 이 결과가 나오는가”를 끝까지 파고들어야 한다.

3이랑 4를 보냈는데 1이랑 2가 나온다.

프론트엔드가 정상이면 백엔드를 본다.

백엔드에서 파라미터가 nil이면, 왜 nil인지 파고든다.

Strong Parameters의 동작을 확인하고 한 글자 차이를 발견한다.

“조용히 실패”하는 코드를 쓰지 않는 것도 중요하다.

배열이 nil이 되면 에러를 내는 게 낫다.

무한 루프에 빠기 전에 카운터로 끊는 게 낫다.

실패를 명시적으로 드러내면 버그를 빨리 잡을 수 있다.