# 백그라운드 작업

**URL:** https://heroiclabs.com/docs/kr/nakama/guides/server-framework/background-jobs/
**Summary:** 이 가이드에서는 백그라운드 작업에 대한 모범 사례를 설명합니다.

---


# 백그라운드 작업

일반적인 엔지니어링 실무에서 복잡하거나 긴 작업은 예약된 작업으로 백그라운드에서 실행합니다. 이런 방식은 게임 서버 컨텍스트에서 데이터 유지 관리를 수행하거나 특정 시간에 전체 플레이어 기반으로 플레이어 일일 보상을 나눠주는 것과 같은 작업을 실행하는 데 주로 사용됩니다. 이러한 작업은 플레이어 기반이 작을 때는 대체로 빠르고 문제가 없습니다. 그러나 플레이어 기반이 증가하기 시작하면 이러한 작업을 수행하는 데 필요한 시간도 늘어납니다. 이로 인해 하루 중 특정 시간에 서버에 높은 CPU 및 데이터베이스 로드가 급증할 수 있으며, 이로 인해 해당 시간 동안 성능과 플레이어 경험이 떨어질 수 있습니다.

이에 대한 해결책은 대신 JIT(Just-In-Time) 접근 방식을 사용하는 것입니다. 해당 날짜에 게임에 로그인했는지 여부에 관계없이 모든 플레이어가 하루에 100금화를 받는 예를 살펴보겠습니다.

## 백그라운드 작업 접근 방식

백그라운드 작업 접근 방식을 사용하면 플레이어 기반에 가장 영향이 적은 시간(예: 자정)에 작업을 예약하고 모든 플레이어에 대해 반복하고 100금화를 제공하는 작업을 실행합니다.

여기서 합리적인 필터링을 수행하여 특정 기간 내에 활동한 플레이어에 대해서만 반복할 수 있지만, 지금은 최근에 플레이했는지 여부에 관계없이 모든 플레이어에게 금화를 제공하려고 합니다.

최상의 시나리오에서 이 방법은 시간 복잡도의 O(n) 순서가 될 것입니다. 이에 따라 플레이어 기반이 늘어나면 이 작업을 수행하는 데 필요한 시간도 늘어납니다.

상상할 수 있듯이 시간이 지남에 따라 이 작업은 서버의 CPU와 데이터베이스에 점점 더 많은 부담을 줄 것이며 결국 이 시간 동안 플레이어에게는 네트워크 성능이 좋지 않게 됩니다.

## JIT 접근 방식

매일 자정에 대량의 금화를 배포하는 대신 Just-In-Time 접근 방식은 플레이어가 다음에 게임에 로그인할 때마다 적절한 금화를 배포합니다.

이를 구현하려면 플레이어가 로그인할 때 다음 단계를 수행합니다:

* 사용자 메타데이터의 속성 또는 저장소 엔진의 값을 통해 플레이어가 마지막으로 로그인한 시간을 확인합니다.
* 마지막 로그인이 없으면 플레이어에게 100금화를 줍니다.
* 마지막 로그인이 있다면 플레이어가 마지막으로 로그인한 이후 경과한 시간을 기준으로 계산된 금화를 플레이어에게 제공합니다.
* 마지막 로그인 타임스탬프를 현재 시간으로 저장

어떤 사용자가 로그인하고 하루 중 몇 시에 로그인하는지에 관계없이 이 작업은 항상 시간 복잡도의 O(1) 순서입니다.

이 접근 방식의 이점은 세 가지입니다:

1. 이 작업에 대한 CPU/데이터베이스 수요는 작은 청크로 필요할 때 하루 종일 분산됩니다.
2. 대규모 플레이어 기반을 대상으로 작업을 수행하는 경우와 달리 플레이어는 네트워크 성능 저하의 급증을 경험하지 않습니다.
3. 게임에 다시 로그인하지 않는 플레이어는 CPU/데이터베이스 처리 시간에 비용을 들이지 않습니다.

## JIT 접근 방식을 구현하는 방법

매일 자정에 각 플레이어에게 100 금화를 나눠주는 예를 다시 사용하여 위에서 설명한 JIT 접근 방식으로 구현해 보겠습니다.

사용자가 게임에 로그인할 때마다 이를 트리거되도록 하기 위해 사용자가 장치 ID를 사용하여 인증할 때마다 [사후 후크](../../../server-framework/introduction/hooks/#after-hooks)를 설정합니다. 이것을 `InitModule` 함수 안에 등록합니다.

```typescript
let InitModule: nkruntime.InitModule = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
  initializer.registerAfterAuthenticateDevice(afterAuthenticateDevice)
}
```

그런 다음 실행할 함수를 정의합니다.

```typescript
let afterAuthenticateDevice : AfterHookFunction<Session, AuthenticateDeviceRequest> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, session: nkruntime.Session, request: nkruntime.AuthenticateDeviceRequest) {
  // Define a constant for how many milliseconds are in a day
  const msInADay = 1000 * 60 * 60 * 24;

  // Get previous midnight
  const previousMidnight = new Date().setHours(0, 0, 0, 0);

  // Get the user's account so that we can check their metadata for a lastLoginTimestamp
  const account = nk.accountGetId(ctx.userId);
  const lastLoginTimestamp : number = account.user.metadata.lastLoginTimestamp;

  let rewardCoins = 0;

  if (!lastLoginTimestamp) {
    // If the user has never logged in, give them 100 coins
    rewardCoins = 100;
    logger.debug("Player has never logged in before, giving them 100 coins.");
  } else if(lastLoginTimestamp < previousMidnight) {
    // If the user's last login was before midnight, we're going to give them at least 100 coins
    rewardCoins = 100;

    // Calculate how many full days have passed since midnight and the time they last logged in
    const timeBetweenMidnightAndLastLogin = previousMidnight - account.user.metadata.lastLoginTimestamp;
    const totalFullDaysSinceLastLogin = Math.floor(timeBetweenMidnightAndLastLogin / msInADay);

    // Increase the reward by 100 for each full day since last login
    rewardCoins += totalFullDaysSinceLastLogin * 100;

    logger.debug(`Player has logged in for the first time today and it has been a further ${totalFullDaysSinceLastLogin} days; giving them ${rewardCoins} coins.`);
  } else {
    logger.debug("Player has already logged in today, no coins are being rewarded.");
  }

  // Update the user's lastLoginTimestamp
  const metadata = account.user.metadata;
  metadata.lastLoginTimestamp = Date.now();
  nk.accountUpdateId(ctx.userId, null, null, null, null, null, null, metadata);

  // Give the player their coins
  nk.walletUpdate(ctx.userId, { coins: rewardCoins });
};
```

위의 예에서 볼 수 있듯이 사용자가 로그인하면 `afterAuthenticateDevice` 함수가 후크로 실행됩니다. 이 함수는 사용자가 마지막으로 로그인한 후 얼마나 지났는지 확인하고(한 번이라도 로그인한 적이 있는 경우) 당일의 첫 번째 로그인에 대해 100개의 코인을 보상하고 마지막 로그인 이후 추가 1일당 100개의 추가 코인을 보상합니다.

이 예제에서는 사용자가 인증한 후 실행되는 후크를 사용하지만 다른 것도 가능합니다. 사용자 세션의 다양한 지점에서 원하는 로직을 수행할 수 있습니다. 사용자가 대체 인증 방법을 연결하기 전이나 친구를 추가한 후를 예로 들 수 있습니다. 사용자가 대신 사용자 지정 [RPC 함수](../../../server-framework/introduction/#rpc-functions)를 호출하도록 만들 수도 있습니다. 사용 가능한 후크의 전체 목록은 문서의 [메시지 이름](../../../server-framework/introduction/hooks/#message-names) 섹션을 참조하세요. 