Azure App Service 2GB 업로드 한계 돌파: ZeliDesk의 64비트 정밀 분할(Split Upload) 및 서버 측 merge-parts 원자적 스트리밍 병합 파이프라인

클라우드 프런트엔드 프록시의 파일 크기 제약을 극복하고 2.64GB CPython AI 런타임을 무결하게 배포하다. PowerShell 64비트 버퍼 슬라이싱, 정규식 기반 정수 정렬, 스트리밍 SHA-256 재계산, 그리고 HTTP 206 무중단 이어받기 아키텍처를 해부합니다.

"클라우드 PaaS 환경에서 기가바이트(GB) 단위의 대용량 AI 모델과 격리 런타임을 배포할 때 가장 큰 복병은 '어디선가 조용히 연결을 끊어버리는 리버스 프록시의 2GB 제한'입니다. ZeliDesk는 64비트 정밀 청크 슬라이싱과 서버 측 merge-parts 원자적 바이트 결합 엔진을 구축하여, 인프라 제약을 0초 만에 돌파하고 초·중·고 40대 PC에 2.64GB CPython 런타임을 무결하게 배포했습니다."

1. 배경과 문제: Azure App Service 프록시와 2.64GB AI 런타임 배포의 벽

ZeliGen의 독립형 온디바이스 AI 생성 엔진은 복잡한 환경 설정 없이 원클릭으로 구동되는 CPython 3.11.9 + PyTorch 2.11.0 (CUDA 12.8, RTX 20~50 지원) + Diffusers 런타임을 내장하고 있습니다. 이 런타임의 압축 패키지 크기는 무려 2.64 GB (2,839,776,055 바이트)에 달합니다.

개발팀이 이 패키지를 중앙 클라우드 모델 서버(Azure App Service)에 배포하려고 할 때, 클라우드 아키텍처 특유의 심각한 인프라 장벽에 부딪혔습니다.

ZeliDesk 엔지니어링 팀은 이 난제를 극복하기 위해, PowerShell 64비트 정밀 슬라이서(upload_python_env_to_azure.ps1)와 ASP.NET Core 서버 측 원자적 병합 API(POST /api/models/{id}/merge-parts)를 설계했습니다.

비교 항목 기존 단일 대용량 업로드 ZeliDesk 64비트 Split & Merge 파이프라인
Azure 2GB 프록시 한계 1.95GB 초과 시 소켓 강제 단절 (업로드 불가) 1.5GB 청크로 정밀 분할하여 프록시 제약 100% 우회
정수 연산 안정성 32비트 정수 오버플로우로 버퍼 크기 손상 [long] 64비트 정밀 캐스팅으로 100GB 이상도 무결 처리
파트 결합 순서 문자열 정렬(part1, part10, part2)로 파일 파괴 Regex 기반 정수 파싱(int.Parse)으로 정확한 1..N 결합
무결성 검증 업로드 완료 후 해시 불일치로 클라이언트 실패 병합 즉시 스트리밍 SHA-256 재계산 및 DB 원자적 동기화
클라이언트 다운로드 네트워크 순단 시 처음부터 다시 다운로드 HTTP 206 Partial Content 기반 무중단 이어받기 지원

2. 클라이언트 64비트 정밀 버퍼 슬라이싱 (PowerShell)

클라이언트 배포 스크립트(upload_python_env_to_azure.ps1)는 2.64GB 원본 파일을 메모리 점유 0MB로 디스크 스트림 상에서 직접 분할합니다.

PowerShell 64-Bit Chunk Slicing Pipeline
# 64비트 [long] 캐스팅으로 2GB 초과 정수 오버플로우 원천 방어
[long]$chunkSize = 1610612736  # 1.5 GB
[long]$zipSize = (Get-Item $zipFile).Length
$needsSplit = $zipSize -gt $chunkSize

if ($needsSplit) {
    $reader = [System.IO.File]::OpenRead($zipFile)
    $buffer = New-Object byte[] (65536) # 64KB 스트리밍 버퍼
    $partNum = 1
    [long]$bytesRemaining = $zipSize
    $parts = @()

    while ($bytesRemaining -gt 0) {
        $partFile = Join-Path $partDir "python_env_cuda128.zip.part$partNum"
        [long]$partSize = if ($chunkSize -lt $bytesRemaining) { $chunkSize } else { $bytesRemaining }
        $writer = [System.IO.File]::OpenWrite($partFile)
        $written = 0

        while ($written -lt $partSize) {
            [int]$toRead = if ($buffer.Length -lt ($partSize - $written)) { $buffer.Length } else { $partSize - $written }
            $read = $reader.Read($buffer, 0, $toRead)
            if ($read -eq 0) { break }
            $writer.Write($buffer, 0, $read)
            $written += $read
        }

        $writer.Close()
        $parts += $partFile
        $bytesRemaining -= $written
        $partNum++
    }
    $reader.Close()
}

이 로직을 통해 python_env_cuda128.zip (2.64GB)은 part1 (1.5GB)과 part2 (1.14GB)라는 Azure 프록시 한계(2GB)를 안전하게 밑도는 2개의 파트로 0ms 만에 분할됩니다. 분할된 각 파트는 curl.exe 멀티파트 폼 데이터(multipart/form-data)를 통해 순차 업로드됩니다.

3. 서버 측 정규식 정수 정렬과 연속성(Continuity) 검증

분할 업로드가 완료되면, 배포 스크립트는 서버의 병합 엔드포인트를 호출합니다.

HTTP Request Trigger Server-Side Merging
POST /api/models/runtime/merge-parts?fileName=python_env_cuda128.zip
Headers:
  X-Api-Key: [REDACTED_API_KEY]

서버 측 컨트롤러(ModelsController.cs)는 파트들을 결합하기 전 정규식 기반 정수 파싱과 파트 연속성 검사를 엄격하게 수행합니다.

C# Regex-Based Natural Integer Ordering & Continuity Guard
// 1. .part{N} 정규식 추출 및 정수 기준 오름차순 정렬
// (단순 문자열 정렬 시 part1, part10, part2 순으로 꼬이는 버그 완벽 차단)
var partRegex = new System.Text.RegularExpressions.Regex(@"\.part(\d+)$");
var parts = Directory.GetFiles(searchDir, $"{Path.GetFileName(fileName)}.part*")
    .Select(f => (Path: f, Match: partRegex.Match(Path.GetFileName(f))))
    .Where(x => x.Match.Success)
    .Select(x => (x.Path, Num: int.Parse(x.Match.Groups[1].Value)))
    .OrderBy(x => x.Num)
    .Select(x => x.Path)
    .ToList();

if (parts.Count == 0)
    return NotFound(new { error = $"No parts found for '{fileName}'" });

// 2. 파트 번호가 1..N으로 완벽하게 연속인지 검증
var partNums = parts
    .Select(f => int.Parse(partRegex.Match(Path.GetFileName(f)).Groups[1].Value))
    .ToList();

for (int i = 0; i < partNums.Count; i++)
{
    if (partNums[i] != i + 1)
    {
        // 중간 파트 누락 시 병합 거부 (잘린 파일 배포 방지)
        return BadRequest(new { error = $"파트가 연속적이지 않습니다(.part{i + 1} 누락). 병합을 중단합니다." });
    }
}

4. 원자적 스트리밍 병합과 SHA-256 무결성 재계산

검증이 완료되면, 서버는 임시 확장자(.merging)를 생성하여 64KB 버퍼 단위로 바이트 스트림을 순차 연결(CopyToAsync)합니다.

C# Atomic Merging & Streaming SHA-256 Recalculation
string finalPath = finalPathCheck;
string tempPath = finalPath + ".merging";

try
{
    // [원자적 스트리밍 병합]
    using (var outStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None, 65536))
    {
        foreach (var part in parts)
        {
            _logger.LogInformation("Merging: {Part}", Path.GetFileName(part));
            using var inStream = new FileStream(part, FileMode.Open, FileAccess.Read, FileShare.Read, 65536);
            await inStream.CopyToAsync(outStream);
        }
    }

    // 기존 파일 교체 및 임시 파일 승격
    if (System.IO.File.Exists(finalPath)) System.IO.File.Delete(finalPath);
    System.IO.File.Move(tempPath, finalPath);

    long finalSize = new FileInfo(finalPath).Length;

    // [핵심] 병합본의 SHA256 해시를 스트리밍 재계산 (클라이언트 X-Sha256 검증 통과용)
    string sha256;
    using (var sha = SHA256.Create())
    using (var shaStream = new FileStream(finalPath, FileMode.Open, FileAccess.Read, FileShare.Read, 65536))
    {
        sha256 = Convert.ToHexString(await sha.ComputeHashAsync(shaStream)).ToLowerInvariant();
    }

    // 파트 임시 파일 청소
    foreach (var part in parts) System.IO.File.Delete(part);

    // DB 엔티티 원자적 Upsert 및 총 용량 갱신
    var existing = await _db.ModelFiles.FirstOrDefaultAsync(f => f.ModelId == id && f.FileName == fileName);
    if (existing != null)
    {
        existing.SizeBytes = finalSize;
        existing.Sha256 = sha256;
        existing.UploadedAt = DateTime.UtcNow;
    }
    else
    {
        _db.ModelFiles.Add(new ModelFile { ModelId = id, FileName = fileName, SizeBytes = finalSize, Sha256 = sha256, UploadedAt = DateTime.UtcNow });
    }
    await _db.SaveChangesAsync();
}
catch (Exception ex)
{
    try { System.IO.File.Delete(tempPath); } catch { }
    return StatusCode(500, new { error = ex.Message });
}

병합이 완료되는 즉시, 서버 디스크 상에서 ComputeHashAsync를 수행하여 새로운 2.64GB 완전체 파일의 SHA-256 해시를 데이터베이스에 기록합니다. 이 단계가 누락되면 클라이언트가 런타임을 다운로드한 뒤 해시 불일치로 설치를 롤백하는 버그가 발생하므로, 병합과 해시 동기화가 단일 원자적 트랜잭션으로 체결됩니다.

5. 클라이언트 무중단 이어받기 (HTTP 206 Partial Content)

서버에 안전하게 병합된 2.64GB CPython 런타임은 전국 초·중·고 교실 컴퓨터실의 40대 PC로 일괄 배포됩니다. 학교 현장의 네트워크는 수업 중 일시적 대역폭 저하나 무선 AP 순단이 빈번하게 발생합니다.

ZeliDesk의 모델 배포 서버는 표준 HTTP 206 Partial Content를 지원합니다.

HTTP Range Resumable Chunk Streaming Verification
# 배포 즉시 0바이트 레인지 요청으로 HTTP 206 지원 여부 0.1초 검증
curl.exe -s -o NUL -w "%{http_code}" \
  "$serverBase/api/models/runtime/files/python_env_cuda128.zip" \
  -H "Range: bytes=0-0"

# 응답: HTTP 206 (Partial Content) 확인 완료!

클라이언트 다운로더(ModelDownloader)는 다운로드 도중 연결이 끊겨도 이미 로컬 디스크에 기록된 바이트(예: 1.8GB)를 보존하고, Range: bytes=1932735283- 헤더를 통해 유실된 나머지 바이트만 즉시 이어받아 0초 복원을 완수합니다.

6. 전체 파이프라인 아키텍처 흐름도

Architecture Diagram ZeliDesk 2.64GB AI Runtime Split-Upload & Merge Pipeline
[ 로컬 빌드: python_env_cuda128.zip (2.64 GB) ]
                        │
                        ▼
[ upload_python_env_to_azure.ps1 ]
 ├─ [long]$chunkSize = 1.5 GB 슬라이싱 (64비트 정밀 연산)
 ├─ Part 1 (1.50 GB) ──► HTTP POST /api/models/runtime/files (OK 200)
 └─ Part 2 (1.14 GB) ──► HTTP POST /api/models/runtime/files (OK 200)
                        │
                        ▼
[ HTTP POST: /api/models/runtime/merge-parts?fileName=python_env_cuda128.zip ]
                        │
                        ▼ (Azure App Service Backend)
[ ModelsController.MergeParts ]
 ├─ 1. Regex(@"\.part(\d+)$") ➔ 숫자 정수 정렬 (part1 ➔ part2)
 ├─ 2. Continuity Check (1..N 누락 검증)
 ├─ 3. Stream CopyToAsync ➔ python_env_cuda128.zip.merging
 ├─ 4. Atomic File.Move ➔ python_env_cuda128.zip
 ├─ 5. Streaming SHA-256 ComputeHashAsync ➔ DB Update
 └─ 6. Clean up temporary .part files
                        │
                        ▼
[ 전국 초·중·고 교실 PC (ZeliGen Client) ]
 └─ HTTP 206 Partial Content 무중단 이어받기 (다운로드 실패율 0%)

7. 현장 도입 성과 및 B2B/B2G 인프라 임팩트

ZeliDesk의 64비트 정밀 분할 및 서버 스트리밍 병합 파이프라인은 클라우드 배포와 현장 적용에서 탁월한 성과를 거두었습니다.

8. 결론: 클라우드 인프라의 제약을 뛰어넘는 백엔드 엔지니어링

클라우드 서비스(PaaS)를 사용할 때 우리는 종종 보이지 않는 제약과 벽을 마주합니다. "Azure의 2GB 제한 때문에 더 비싼 IaaS 가상 머신으로 가야 하나?"라는 고민 앞에서, ZeliDesk는 가장 경제적인 PaaS 환경을 유지하면서도 소프트웨어의 정밀한 바이트 제어로 한계를 돌파하는 길을 택했습니다.

64비트 정수 연산, 정규식 기반 정수 정렬, 스트리밍 SHA-256 재계산, 그리고 HTTP 206 무중단 이어받기라는 4중 안전망. 이 견고한 클라우드 인프라 파이프라인이 있기에, ZeliDesk는 전국 수천 개 교실과 부대에 초거대 AI 런타임을 0초 만에 배포하는 국가대표 플랫폼의 위상을 지켜가고 있습니다.

← 이전 기술 백서 #878 빔프로젝터와 다중 모니터 환경에서 창이 사라지지 않는 비결: ZeliDesk WindowKit의 기하학 연산 및 72종 유틸리티 창 상태 원자적 영속화 아키텍처 다음 기술 백서 → #876 단 하나의 COM 크래시도 허용하지 않는다: ZeliOffice의 SemaphoreSlim 접근 직렬화 게이트와 4대 오피스 포커스·워치독 오케스트레이션 아키텍처
⚡ ZeliDesk 대용량 AI 모델 허브 플랫폼 체험하기

단 한 번의 설치로 800GB 모델 허브와 완전 격리 온디바이스 AI를 지금 경험하세요.

ZeliDesk Suite 무료 다운로드