한국어

개발자 도구 · Base64 인코더 및 디코더

JSON API의 Base64: 바이너리 필드가 인코딩되는 이유와 비용

· 그것이 중요한 이유

베이스64 인코딩

전송을 위해 인코딩된 바이너리 데이터를 나타내는 긴 Base64 문자열을 포함하는 JSON 필드
원본 ToolAcre 벡터 일러스트레이션

JSON에는 바이트 유형이 없으므로 이진 데이터는 일반적으로 Base64로 인코딩되어 문자열로 인코딩됩니다. 이 게시물에서는 해당 규칙이 존재하는 이유, 크기 및 CPU 비용, 별도의 바이너리 엔드포인트가 더 나은 호출인 경우에 대해 설명합니다.

응답을 지배하는 PDF 필드 — 하나의 Base64 blob이 다른 모든 것보다 중요한 구체적인 API 페이로드

API 응답에는 페이로드 크기를 지배하는 단일 필드가 있는 대형 개체가 포함되어 있습니다. 응답은 JSON이므로 모든 값은 문자열 또는 숫자입니다. 사용자 ID, 타임스탬프, 상태 코드 등 대부분의 필드는 작습니다. 한 필드에는 imageData 또는 fileContents가 포함되어 있으며 40-킬로바이트 Base64 문자열입니다. 전체 응답은 50킬로바이트입니다. 해당 단일 필드는 전송의 80%를 차지하는데, 이는 서버가 원래 바이트로 보냈고 클라이언트가 결국 다시 바이트를 필요로 하기 때문에 낭비처럼 보입니다.

Base64는 이 문제를 해결했습니다. JSON에는 기본 바이트 유형이 없으므로 이진 데이터를 문자열로 래핑해야 합니다. Base64는 임의의 바이트를 JSON에서 안전한 ASCII 문자로 변환합니다. 클라이언트와 서버 모두 송신 시 인코딩하고 수신 시 디코딩해야 하므로 CPU 오버헤드가 추가됩니다. 결과 페이로드는 원시 바이트보다 약 1/3 더 큽니다. 이 게시물에서는 해당 규칙이 존재하는 이유, 실제로 비용이 얼마나 드는지, 별도의 바이너리 엔드포인트로 JSON 제약 조건을 위반하는 경우 가치가 있는지 설명합니다.

JSON이 원시 바이트를 전달할 수 없는 이유 - 문자열은 유효한 유니코드 텍스트여야 하므로 임의의 바이트에는 텍스트 래퍼가 필요합니다.

JSON은 모든 값이 유효한 유니코드 텍스트여야 하는 텍스트 형식입니다. 사양에서는 문자열, 숫자, 부울 및 null을 정의합니다. 바이트 배열이나 버퍼 유형이 없습니다. API가 이미지, 암호화 서명 또는 파일 업로드와 같은 이진 데이터를 반환해야 하는 경우 원시 바이트를 JSON 개체에 직접 넣을 수 없습니다. 바이트에는 JSON 파서가 구조적 표시자로 해석하는 문자가 포함될 수 있습니다. 바이너리 blob 중간에 있는 null 바이트는 문자열을 조기에 종료하거나 파서를 중단할 수 있습니다.

일반적인 해결 방법은 바이너리 데이터를 Base64로 인코딩하여 JSON 파서가 일반 텍스트로 처리하는 ASCII 문자 문자열을 생성하는 것입니다. 그런 다음 수신 클라이언트는 Base64를 다시 바이트로 디코딩하여 사용합니다. 이 인코딩 단계는 대부분의 개발자에게 숨겨져 있는 API 수준에서 발생하지만 API가 많은 바이너리 필드를 반환하면 누적되는 실제 비용입니다. 요청-응답 주기 전반에 걸쳐 JSON의 Base64 비용이 복합적으로 발생합니다. 크기 패널티는 첫 번째입니다. Base64 출력은 인코딩 오버헤드로 인해 입력보다 약 33% 더 큽니다.

비용: 1/3 더 많은 바이트, 디코드 시간 및 메모리 복사본 — 각 비용은 일반적인 클라이언트에 나타납니다.

30 메가바이트 비디오 파일은 Base64로 인코딩되면 40 메가바이트가 됩니다. 30 메가바이트 대신 40을 다운로드하면 모바일 장치의 대역폭과 배터리가 소모되고 연결이 느린 사용자에게는 시간이 걸립니다. 두 번째 비용은 CPU 시간입니다. 서버는 바이너리 데이터를 JSON로 문자열화하기 전에 Base64로 인코딩해야 합니다. 클라이언트는 JSON를 구문 분석한 다음 각 Base64 필드를 다시 바이트로 디코딩해야 합니다. 여러 바이너리 필드가 있는 응답이나 수천 개의 응답을 처리하는 클라이언트의 경우 해당 CPU 시간이 누적됩니다.

휴대폰과 같은 제한된 장치에서 Base64 디코딩에 사용되는 JavaScript 문자열 작업 및 TextDecoder는 배터리를 소모하고 애플리케이션 속도를 저하시킵니다. 세 번째 비용은 메모리입니다. JSON 파서는 Base64 필드에 대한 문자열 객체를 생성한 다음 디코딩하여 Uint8Array로 또 다른 복사본을 생성합니다. 큰 필드는 애플리케이션이 사용하기 전에 메모리에서 두 번 인스턴스화됩니다. 실제 사례를 통해 비용이 명확해졌습니다. API 엔드포인트가 100킬로바이트 아바타 이미지를 포함한 사용자 프로필 데이터를 반환한다고 가정합니다. 서버는 디스크에서 이미지를 바이트로 읽고 Base64로 인코딩한 후 JSON 응답에 포함합니다.

실제 사례: API 응답에서 Base64 필드 검사 — 서버가 실제로 보낸 내용을 확인하기 위해 브라우저에서 디코딩

JSON 응답은 이제 약 135킬로바이트입니다(33% 오버헤드 + 기타 필드). 클라이언트는 100 대신 135 킬로바이트를 다운로드합니다. 브라우저에서 JSON 구문 분석기는 Base64 데이터에 대한 JavaScript 문자열 객체를 생성합니다.

애플리케이션에 이미지가 필요할 때 Base64 디코더를 호출하여 원본 100 킬로바이트의 Uint8Array를 생성합니다. 몇 밀리초 동안 디코딩하는 동안 두 개체는 모두 메모리에 존재합니다. 페이지에 아바타가 포함된 10개의 프로필이 표시되면 비용이 곱해집니다. 대안은 API가 각 아바타 리소스에 대한 별도의 URL과 함께 JSON 응답을 반환하여 브라우저가 기본 캐싱, 점진적 렌더링 및 메모리 관리를 통해 이미지 다운로드를 처리하도록 하는 것입니다.

대안: 멀티파트, 별도의 다운로드 URL 및 원시 바이너리 엔드포인트 — 각각의 장단점

JSON에 바이너리를 포함하는 것과 별도로 가져오는 것 사이의 균형은 API 목적과 사용 패턴에 따라 다릅니다. 수백 개의 작은 썸네일 이미지를 반환하는 검색 결과 페이지의 경우 각각을 별도의 요청으로 가져오면 HTTP 연결 풀링 및 캐싱이 무효화됩니다. JSON 응답에서 Base64로 인라인하는 것이 더 빠를 수 있습니다. 하나 또는 두 개의 고해상도 이미지를 요청하는 상세 프로필 페이지의 경우 별도의 다운로드가 분명히 더 좋습니다. API 문서에는 Base64 필드의 최대 크기와 클라이언트가 별도의 엔드포인트를 예상해야 하는 시기가 명시되어야 합니다.

필드가 정기적으로 1~2KB를 초과하는 경우 인라인 Base64 전략은 API 설계를 재고해야 한다는 신호입니다. JSON에는 Base64에 대한 대안이 있지만 각각 장단점이 있습니다. 멀티파트 MIME 응답은 바이너리와 텍스트를 분리하므로 바이너리 섹션은 원시 바이트로 전송되고 텍스트 섹션만 ​​JSON입니다. 이를 위해서는 클라이언트가 JSON.parse를 호출하는 대신 다중 부분 메시지를 구문 분석해야 하므로 복잡성이 추가됩니다. JSON 응답의 별도 다운로드 URL은 클라이언트가 바이너리 리소스를 별도로 가져오도록 지시합니다.

API 문서에 언급할 가치가 있는 규칙 — 표준과 base64url 알파벳, 패딩 및 최대 크기

이는 바이너리 리소스가 크거나 메타데이터보다 자주 액세스되지 않는 경우에 잘 작동합니다. 바이트만 반환하고 JSON을 완전히 포기하는 원시 바이너리 엔드포인트는 가장 간단한 접근 방식이지만 JSON이 제공하는 구조를 제거합니다. 일부 API는 압축된 이진 데이터를 반환하고 이를 Base64로 인코딩하여 크기 패널티를 줄이지만 압축 해제 오버헤드를 추가합니다. 선택은 예상되는 용도에 따라 다릅니다. 작은 필드는 인라인으로 괜찮고, 큰 필드는 별도의 리소스에 속하며, 구조화된 데이터는 Base64 비용으로도 JSON에 보관할 가치가 있습니다.

규칙은 상호 운용성에 중요합니다. Base64로 인코딩된 바이너리 데이터를 사용하는 API는 데이터를 명확하게 문서화하고 알파벳이 표준인지 URL에 안전한지 명시해야 합니다. 표준 Base64는 JSON 문자열에서는 안전하지만 URL에서는 안전하지 않은 + 및 /,을 사용합니다. URL 안전 Base64는 이를 - 및 _로 대체합니다. 이는 data: URI에 적합하지만 JSON에서 불필요하게 이스케이프되었습니다. 패딩이 포함되는지 생략되는지 여부를 문서에서 지정해야 합니다. 둘 다 유효한 Base64이지만 패딩을 예상하고 패딩되지 않은 데이터를 받는 클라이언트는 자동으로 실패하거나 가비지를 생성하기 때문입니다.

여기서 다루지 않는 내용 — protobuf, CBOR 및 기타 바이너리 직렬화 형식

매우 크거나 자주 업데이트되는 필드의 경우 클라이언트가 불필요한 데이터를 킬로바이트 단위로 가져오려고 시도하지 않도록 별도의 바이너리 끝점을 문서화하는 것이 필수적입니다. Base64 필드를 사용하여 API를 디버깅하는 것은 올바른 도구를 사용하면 간단합니다. Base64 인코더 및 디코더를 사용하면 필드를 저장하거나 어디로든 보내지 않고도 브라우저에서 로컬로 모든 필드를 디코딩할 수 있습니다. JSON 응답에서 Base64 필드를 복사하여 디코더에 붙여넣고 디코드를 누르세요. 텍스트와 같은 데이터(예: Base64 내부의 JSON)의 경우 디코딩된 출력이 즉시 나타납니다.

이미지와 같은 이진 데이터의 경우 16진수 보기에 바이트가 표시됩니다. 이렇게 하면 서버가 예상한 대로 전송했는지, 클라이언트 디코더가 올바르게 작동하는지 확인하는 데 도움이 됩니다. 필드가 예기치 않은 데이터로 디코딩되는 경우 서버 인코딩이나 필드를 복사하는 방법에 문제가 있는 것입니다. 부분 blob으로 디코딩되는 경우 필드가 잘렸거나 Base64 길이가 잘못되었을 수 있습니다. 로컬 디코딩은 필드를 파일에 쓰고 외부 도구를 여는 것에 비해 디버깅 속도를 높입니다.

요점: JSON의 Base64는 타협이므로 문서화하십시오. Base64 인코더 및 디코더가 인코딩된 필드를 로컬에서 검사하고 확인하는 데 어떻게 도움이 되는지

API에서 Base64에 대한 실용적인 접근 방식은 회피보다는 인식입니다. Base64는 JSON에서 바이너리 데이터를 전달하는 표준 방법이며 작동합니다. 각 Base64 필드의 크기는 1/3 더 비싸고 요청-응답 주기당 CPU 시간은 몇 밀리초 더 소요된다는 점을 이해하세요. 인증 토큰(JWT 자체가 Base64로 인코딩된 경우)과 같은 소규모 중요한 메타데이터의 경우 비용은 무시할 수 있습니다. 대용량 첨부 파일의 경우 바이너리가 동일한 응답으로 이동해야 하는지 아니면 별도의 리소스로 이동해야 하는지 질문하세요.

API 사양에 인코딩 체계와 최대 크기를 문서화하세요. 응답을 검사할 때 Base64 인코더 및 디코더를 사용하여 필드가 올바르게 디코딩되었는지 확인하고 서버가 실제로 보낸 내용을 이해합니다. 이러한 규율은 절충안을 가시적으로 유지하고 우발적인 결정이 아닌 의도적인 결정을 유지합니다.