Go bufio.Scanner가 긴 줄에서 멈출 때: token too long과 Buffer 한도 처리
긴 줄에서 Scanner.Scan이 멈추는 이유를 재현한다. Err 검사, Buffer 설정과 별도 바이트 길이 제한을 9개 사례로 검증하고 Reader 전환·재시도의 경계를 정리한다.
Go에서 파일이나 표준 입력을 bufio.Scanner로 읽다가 긴 줄 이후 처리가 멈춘다면, 반복문 뒤의 Scanner.Err()부터 확인한다. 기본 토큰 버퍼 한도를 넘으면 Scan()이 false를 반환하므로 오류를 검사하지 않는 코드는 정상 EOF처럼 보일 수 있다. 허용할 크기를 정해 스캔 전에 버퍼를 설정하고, 실제 업무상 줄 길이 제한은 토큰을 받은 뒤 별도로 검사한다.
길게 이어진 종이 리본과 크기가 다른 투입구로 긴 줄과 읽기 한도를 표현한 개념 일러스트. 실제 Go 메모리 구조도는 아니다.
작성·문서 확인: 2026-11-02. 실행 검증: Go 1.24.7, Linux amd64, 표준 라이브러리 bufio. 아래 결과는 문자열 기반 재현이며 사용자 로그·서비스나 최신 Go 버전의 실행 결과가 아니다.
반복문 종료를 정상 완료로 오해하는 경우
for scanner.Scan() { ... }만 쓰면 반복이 왜 끝났는지 알 수 없다. Go 공식 Scan·Err 문서는 EOF와 오류 모두에서 반복이 끝날 수 있으며, Err()가 EOF를 제외한 오류를 돌려준다고 설명한다.
재현 입력은 ASCII x 70KiB 뒤에 LF 하나를 붙인 문자열이다. 이 환경에서는 기본 스캐너가 토큰을 반환하기 전에 bufio.Scanner: token too long으로 끝난다. 줄마다 JSON·긴 경로·합성 요청 텍스트를 넣는 형식이라면 파일 전체 크기와 별개로 한 줄이 커질 수 있다.
기본 MaxScanTokenSize는 64 × 1024다. 다만 이를 “내용이 정확히 65,536바이트인 줄까지 허용한다”는 계약으로 읽어서는 안 된다. 줄 구분자 등에 필요한 공간도 고려해야 한다. 정확한 경계에 기대기보다 파서의 여유 공간과 애플리케이션의 허용 크기를 구분한다. 공식 상수·Buffer 문서
읽기 버퍼와 업무상 제한을 분리한다
예제에서는 두 정책을 독립적으로 둔다.
| 항목 | 예제 값 | 의미 |
| 초기 버퍼 | 4KiB | 처음 사용할 스캐너 버퍼 |
| 스캐너 확장 한도 | 128KiB | 이 예제에서 토큰을 읽을 수 있게 둔 여유 |
| 업무상 줄 길이 | 64KiB 또는 비활성화 | 줄 구분자를 제거한 토큰의 바이트 수 검사 |
Buffer는 첫 Scan() 전에 호출한다. 예제의 128KiB는 권장 기본값이 아니라 비교를 위한 값이다. 데이터 계약·동시 처리 수·메모리 예산을 보고 정해야 한다.
또한 Buffer(buf, max)의 max를 곧바로 엄격한 입력 제한으로 취급하지 않는다. 아래 마지막 사례는 초기 버퍼를 128KiB로 주고 두 번째 인자는 32KiB로 설정했는데도 96KiB 줄을 읽는다. 작은 두 번째 인자만으로 큰 초기 버퍼를 제한할 수 없다는 재현이다.
실행 코드: 긴 줄 오류와 별도 길이 검사
아래를 scanner_demo.go로 저장하고 go run scanner_demo.go로 실행한다. 외부 모듈은 필요 없다. maxLine == 0은 비교를 위해 업무상 제한만 끈다는 뜻이며, 스캐너 자체의 한도를 없애지 않는다.
예제는 줄 내용을 저장하지 않고 길이만 모은다. 오류가 나면 결과 슬라이스를 반환하지 않지만, 실제 처리 중 이미 수행한 DB 쓰기나 출력까지 되돌려 주는 것은 아니다. 전부 성공해야 하는 작업은 별도 임시 저장·트랜잭션 정책이 필요하다.
실제 출력과 검증한 아홉 가지 사례
| 사례 | 확인 결과 |
| 기본 설정, 짧은 두 줄 | 길이 1·2 |
| 기본 설정, 70KiB 줄 | ErrTooLong 감지 |
| 128KiB 설정, 70KiB 줄 | 71,680바이트 읽기 |
| 같은 입력, 업무상 64KiB 제한 | 별도 길이 오류 |
| 업무상 제한과 같은 64KiB 내용 + LF | 성공 |
| 빈 줄 두 개 | 길이 0·0 |
| CRLF 두 줄 | 구분자 제외 길이 1·1 |
| 마지막 LF가 없는 입력 | 마지막 줄까지 길이 1·2 |
| 초기 버퍼 128KiB, 두 번째 인자 32KiB | 96KiB 줄 읽기 |
아홉 사례 모두 실제 실행했다. 마지막 사례는 스캐너 한도 설정과 업무상 길이 검사가 다른 역할임을 보여 준다. 길이는 문자 수가 아니라 바이트 수이며, 이 코드는 JSON 문법이나 UTF-8 유효성을 검증하지 않는다.
다시 읽기와 Reader 전환 전에 확인할 것
Go 공식 Scanner 소스 설명은 스캔이 멈췄을 때 원본 리더가 마지막 토큰보다 더 진행했을 수 있다고 명시한다. 따라서 실패한 같은 스트림에 새 스캐너를 붙여 이어 읽는 방식을 복구 절차로 가정하지 않는다. 재시도하려면 보관한 원본이나 다시 열 수 있는 파일에서 시작하고, 이미 처리한 줄의 중복도 관리해야 한다.
- 한도를 정할 수 있는 줄 단위 입력에는 설정한 Scanner와 명시적 오류 처리가 간단하다.
- 큰 줄을 조각 단위로 처리하거나 오류 이후 읽기 위치를 직접 관리해야 한다면
bufio.Reader를 검토한다.ReadSlice는 버퍼가 차면 조각과ErrBufferFull을 처리해야 하며,ReadString으로 바꿨다고 입력 자원 제한이 저절로 생기지는 않는다. Reader 공식 문서
s.Bytes()를 나중에 쓸 목적으로 보관한다면 다음 스캔에서 덮어써질 수 있으므로 복사한다. 이 예제는 즉시 길이만 읽어 슬라이스를 보관하지 않는다.
- 스캐너 버퍼 제한은 전체 입력량·전체 줄 수·처리 시간 제한이 아니다. HTTP 요청이나 장시간 스트림에 적용한다면 이 제한들도 별도로 둔다.
이 글에서는 기본·확장 버퍼와 줄 길이 검사만 실행 검증했다. 실제 네트워크 I/O 오류, 처리량·메모리 벤치마크, 서비스 부하 시험은 수행하지 않았다.
English version: Go bufio.Scanner: Fix token too long and Enforce Line-Length Limits