분산 배포

여러 노드에서 하나의 문서 제공

여러 노드가 동일한 문서를 제공할 때, 문서를 렌더링하는 노드가 반드시 브라우저의 페이지 요청에 응답하는 노드인 것은 아닙니다. 이번 릴리스에서는 그 작업을 두 개의 명확한 절반으로 나눕니다 — 쓰기 측은 문서의 아티팩트를 공유 저장소에 게시하고, 읽기 측은 이를 어느 노드에서든 제공하는 역할을 합니다.

현재 애플리케이션에서 UseDoconutWebFarm(...)을 호출하고 있다면, 이 페이지가 그 대체품입니다. 이번 릴리스에는 UseDoconutWebFarm이나 WebFarmOptions가 존재하지 않습니다.

토폴로지는 변경되지 않습니다. 오늘 사용하는 동일한 UNC 경로를 공유 루트로 지정하면 모든 노드가 현재와 같은 방식으로 계속 작동합니다. 변하는 점은 게시가 렌더링의 부수 효과가 아니라 호출하는 작업이 된다는 것입니다.

웹 팜 미들웨어를 대체한 것

이전에는 하나의 미들웨어가 모든 일을 처리했습니다: 공유 폴더에서 사전 렌더링된 이미지를 제공하고, 다른 노드가 아직 이미지를 쓰고 있는 동안 폴링했습니다. 기다림은盲目的인 예산이었으며 — “페이지가 나타날 때까지 최대 3분 차단”이라는 식이었습니다.

이제 동일한 작업이 두 개의 호출로 나뉘어 눈에 보이고 논리적으로 이해할 수 있습니다:

text
node A            OpenDocument request
                      -> PublishAsync(...)   writes pages, document info,
                                             search index and metadata
                                             into  RootPath/{token}/
                      -> returns the token

node B            ?token=...&page=7
                      -> UseDoconutCloud<FileShareCloudHandler>
                      -> reads RootPath/{token}/ and serves the page

양쪽 모두 필요한 공유 구성

게시자는 FileShareConfig를 사용해 쓰고, 읽기 핸들러는 이를 사용해 제공합니다. RootPath나 UsePerTokenSubfolders가 서로 다르면 다른 노드는 404를 반환합니다. 구성을 정확히 한 곳에서 만들고 양쪽에서 호출하도록 하세요 — 참조 애플리케이션은 이 이유로 단일 SharedStore.Config(...) 헬퍼를 유지합니다:

csharp
internal static class SharedStore
{
    public static FileShareConfig Config(string rootPath, bool asyncPublish) => new()
    {
        Location              = CloudLocation.FileShare,
        RootPath              = rootPath,               // e.g. \\fileserver\doconut
        UsePerTokenSubfolders = true,
        ReadReadyPollMs       = asyncPublish ? 250 : 0,
    };
}

FileShareConfig는 Doconut.Clouds에 위치하며, AzureConfig, AmazonConfig, GoogleCloudConfig, DropBoxConfig, RedisConfig, CDNConfig, FTPConfig와 함께 제공됩니다. 이전 라이브러리에서는 이들이 Doconut.Configs.Cloud에 있었으며, 클래스 이름은 변경되지 않았습니다.

속성목적
LocationCloudLocation.FileShare는 UNC/로컬 공유용입니다.
RootPath모든 노드가 접근할 수 있는 공유 폴더
UsePerTokenSubfolders각 문서의 아티팩트를 자체 {token} 폴더에 보관합니다.
ReadReadyPollMs읽기 측이 아직 쓰이고 있는 페이지를 기다리는 시간; 0은 폴링을 비활성화합니다.

쓰기 측 — 문서 게시

DistributedDocumentPublisher를 주입하고 로컬 세션을 여는 대신 게시합니다. 한 번의 호출로 페이지, 문서 정보, 검색 인덱스, 메타데이터 등 전체 아티팩트 세트를 렌더링하고 기록합니다:

csharp
var shareConfig = SharedStore.Config(rootPath, asyncPublish: false);

var documentOptions = new DocOptions { TimeOut = 60 };
var config          = new PdfConfig { AllowSearch = true };

var publishOptions = new DistributedDocumentPublishOptions
{
    CleanUpStagingDirectory = true,
    FirstPagePriority       = true,
};

DistributedDocumentPublishResult result = await _publisher.PublishAsync(
    pathToFile, shareConfig, config, documentOptions, publishOptions, ct);

return Content(result.Token);

DistributedDocumentPublishResult는 Token, ArtifactPath, TotalPages, Format을 포함합니다. 토큰은 일반 뷰어 토큰과 동일하게 브라우저에 전달됩니다.

이 단일 호출은 이전의 두 단계 — 로컬 세션으로 문서를 열고 ExportToPng(sharedPath)로 이미지를 공유에 덤프하는 작업 — 를 대체합니다. 여기서는 ExportToPng와 ExportToCloud에 해당하는 기능이 없습니다.

읽기 측 — 공유에서 제공

FileShare 핸들러를 클라이언트가 이미 호출하는 동일한 브랜치에 마운트합니다:

csharp
app.UseDoconutResources();

app.MapWhen(
    ctx => ctx.Request.Path.Value?.EndsWith("DocImage.axd", StringComparison.OrdinalIgnoreCase) == true,
    branch => branch.UseDoconutCloud<FileShareCloudHandler>(options =>
    {
        options.Location          = CloudLocation.FileShare;
        options.CloudUploadConfig = SharedStore.Config(rootPath, asyncPublish: false);
    }, "/"));

UseDoconutCloud는 이번 릴리스에서 제네릭으로 제공됩니다: 공급자는 미들웨어 내부에서 Location 값을 전환하는 것이 아니라 타입 인수로 전달됩니다. 핸들러 이름은 <Provider>CloudHandler 형식이며 — FileShareCloudHandler, AzureCloudHandler, AmazonS3CloudHandler, GoogleCloudHandler, DropBoxCloudHandler, RedisCloudHandler, FTPCloudHandler, CDNCloudHandler가 있습니다.

CDN 핸들러는 읽기 전용이며 — 이미 CDN에 존재하는 내용을 제공할 뿐이며 업로드를 수행하지 않습니다.

첫 페이지 우선순위와 HTTP 202

FirstPagePriority = true를 설정하면 1페이지를 즉시 제공할 수 있습니다. 이후 페이지들은 HTTP 202 Accepted를 반환하며, 작성될 때까지 요청을 차단하지 않습니다.

이렇게 하면 대기 시간이 클라이언트 쪽으로 이동하고, 그곳에서 제한되고 가시적으로 관리됩니다. 위젯에서 재시도를 활성화합니다:

javascript
const objViewer = $('#div_ctlDoc').docViewer({
    BasePath:   '/',
    ResPath:    'doconut-res/images',
    retryOn409: true      // 202(및 레거시 409) 준비 응답을 재시도
});

재시도를 하지 않는 클라이언트는 아직 렌더링 중인 페이지에 대해 깨진 타일을 표시합니다. 재시도 예산 및 기본값(retryInitialDelayMs, retryBackoffFactor, retryMaxDelayMs, retryMaxAttempts, retryMaxTotalMs)은 ViewerConfig에서 문서화되어 있습니다.

이 조합 — FirstPagePriority와 읽기 측 폴링 — 은 이전의 PageWaitTimeSeconds와 StartWaitFromPage 설정을 대체합니다. 이제는 더 이상 존재하지 않습니다.

선택 사항: 백그라운드에서 퍼블리시

기본적으로 PublishAsync는 반환하기 전에 렌더링을 완료합니다. 토큰이 발급되는 즉시 반환하고, 호스팅된 워커가 동일한 폴더에 렌더링하도록 하려면 옵트인 큐를 등록합니다:

csharp
builder.Services.AddDoconut(options => { /* … */ });
builder.Services.AddDoconutDistributedAsyncPublish();

그런 다음 인라인 퍼블리시 대신 큐에 넣고 퍼블리시 옵션에 Async = true를 설정합니다. 큐는 제한이 있으며, 포화될 경우 EnqueueAsync는 DistributedPublishQueueFullException을 발생시킵니다. 이를 503 상태와 Retry-After 헤더로 응답하여 클라이언트가 대기하도록 합니다:

csharp
try
{
    var token = await _publishQueue.EnqueueAsync(
        pathToFile, shareConfig, config, documentOptions, publishOptions, ct);
    return Content(token);
}
catch (DistributedPublishQueueFullException)
{
    Response.Headers.RetryAfter = "5";
    Response.StatusCode = 503;
    return Content("The publish queue is full. Please retry.");
}

이 등록이 없으면 아무것도 등록되지 않으며 퍼블리시가 동기식으로 유지됩니다 — 기본 경로는 바이트 단위로 완전히 동일합니다.

백그라운드 퍼블리시가 활성화된 경우, 읽기 측에 ReadReadyPollMs를 0이 아닌 값으로 지정하여 워커가 아직 쓰지 않은 페이지를 잠시 기다리게 합니다.

노드 간 보안

세션에 바인딩된 토큰은 서비스 노드가 최초 요청을 본 경우에만 유효합니다. 팜 환경에서는 그렇지 않을 수 있어, 세션당 검사가 정당한 요청을 거부하게 됩니다. 그래서 분산 레퍼런스 애플리케이션은 UnsafeMode = true를 설정합니다.

UnsafeMode = true는 보안 모델이 아니라, 더 이상 적용되지 않는 모델을 제거한 것입니다. 이를 서명된 접근 티켓으로 교체하면 각 노드가 독립적으로 검증할 수 있습니다:

csharp
builder.Services.AddDoconutDistributedDocumentSecurity(options => { /* … */ });

단일 노드 배포에서는 UnsafeMode = true를 절대 사용하지 말고, 하나의 노드에서 세션 검사가 실패할 경우의 임시 방편으로도 사용하지 마세요 — 이 경우 올바른 해결책은 AddSession() / UseSession()입니다. 자세한 내용은 세션 및 보안 을 참고하십시오.

게시된 문서 닫기

CloseDocument(token)은 주입된 Viewer에서 로컬 세션을 해제합니다. 공유에서 문서의 게시된 아티팩트를 제거하려면, 퍼블리셔가 다음과 같이 노출합니다:

text
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)

문서가 게시될 때 사용한 동일한 FileShareConfig를 전달합니다.

설정별 매핑

이전현재
UseDoconutWebFarm(new WebFarmOptions { … })PublishAsync(...)는 쓰기 측에서, UseDoconutCloud<FileShareCloudHandler>(...)는 읽기 측에서
WebFarmOptions.PathFileShareConfig.RootPath
PageWaitTimeSeconds / StartWaitFromPageDistributedDocumentPublishOptions.FirstPagePriority와 FileShareConfig.ReadReadyPollMs
viewer.OpenDocument(...) then viewer.ExportToPng(share)PublishAsync(...) 호출 하나
viewer.ExportToCloud(cloudConfig)PublishAsync(...)를 제공자의 구성과 함께
UseDoconutCloud(new CloudOptions { Location = … })UseDoconutCloud<THandler>(options => …, pathPrefix)
DocOptions.IsWebfarm / WebfarmPath여전히 존재하지만 IsWebFarm / WebFarmPath 로 표기됩니다 — 게시 경로에서는 필요하지 않습니다
Doconut.Configs.CloudDoconut.Clouds

체크리스트

  1. 하나의 헬퍼에서 FileShareConfig를 구축하고 양쪽에서 호출합니다.
  2. RootPath를 이미 사용 중인 공유에 지정합니다.
  3. PublishAsync(...)로 게시하고, 모든 ExportToPng / ExportToCloud 호출을 삭제합니다.
  4. 클라이언트가 호출하는 브랜치에서 UseDoconutCloud<FileShareCloudHandler>(...)를 사용해 제공합니다.
  5. FirstPagePriority와 retryOn409를 함께 켜거나, 둘 다 끕니다.
  6. 세션당 검사를 서명된 액세스 티켓으로 교체합니다 — UnsafeMode = true만 단독으로 배포하지 마세요.
  7. 한 노드에서 문서를 열고 다른 노드에서 늦은 페이지를 요청하여 확인합니다.

이 페이지가 도움이 되었나요?