폴링 기반 웹앱의 제한 사항에 대한 분석

완료됨

폴링 기반 웹 애플리케이션.

애플리케이션의 현재 아키텍처는 타이머를 기반으로 서버에서 모든 주가 정보를 가져와 주식 정보를 보고합니다. 이 설계는 종종 폴링 기반 설계라고 합니다.

서버

주가 정보는 Azure Cosmos DB 데이터베이스에 저장됩니다. HTTP 요청에 의해 트리거되면 getStocks 함수는 데이터베이스의 모든 행을 반환합니다.

import { app, input } from "@azure/functions";

const cosmosInput = input.cosmosDB({
    databaseName: 'stocksdb',
    containerName: 'stocks',
    connection: 'COSMOSDB_CONNECTION_STRING',
    sqlQuery: 'SELECT * from c',
});

app.http('getStocks', {
    methods: ['GET'],
    authLevel: 'anonymous',
    extraInputs: [cosmosInput],
    handler: (request, context) => {
        const stocks = context.extraInputs.get(cosmosInput);
        
        return {
            jsonBody: stocks,
        };
    },
});
  • 데이터 가져오기: 코드의 첫 번째 섹션인 cosmosInput은 Cosmos DB의 stocksdb 데이터베이스에 있는 쿼리 SELECT * from c를 사용하여 stocks 테이블의 모든 항목을 가져옵니다.
  • 데이터 반환: 코드의 두 번째 섹션인 app.http는 해당 데이터를 context.extraInputs의 입력으로 함수에 수신한 다음 이를 클라이언트에 응답 본문으로 반환합니다.

클라이언트

샘플 클라이언트는 Vue.js를 사용하여 UI를 구성하고 Fetch 클라이언트를 사용하여 API에 대한 요청을 처리합니다.

HTML 페이지는 타이머를 사용하여 요청을 5초마다 서버에 보내 주식을 요청합니다. 응답은 사용자에게 표시되는 주식 배열을 반환합니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css" integrity="sha256-8B1OaG0zT7uYA572S2xOxWACq9NXYPQ+U5kHPV1bJN4=" crossorigin="anonymous" />
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
    <link rel="stylesheet" href="style.css">
    <title>Stocks | Enable automatic updates in a web application using Azure Functions and SignalR</title>
</head>
<body>
    
    <!-- BEGIN: Replace markup in this section -->
    <div id="app" class="container">
        <h1 class="title">Stocks</h1>
        <div id="stocks">
            <div v-for="stock in stocks" class="stock">
                <div class="lead">{{ stock.symbol }}: ${{ stock.price }}</div>
                <div class="change">Change:
                    <span :class="{ 'is-up': stock.changeDirection === '+', 'is-down': stock.changeDirection === '-' }">
                        {{ stock.changeDirection }}{{ stock.change }}
                    </span></div>
            </div>
        </div>
    </div>
    <!-- END  -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js" integrity="sha256-chlNFSVx3TdcQ2Xlw7SvnbLAavAQLO0Y/LBiWX04viY=" crossorigin="anonymous"></script>
    <script src="bundle.js" type="text/javascript"></script>
</body>
</html>
import './style.css';

function getApiUrl() {

    const backend = process.env.BACKEND_URL;
    
    const url = (backend) ? `${backend}` : ``;
    return url;
}

const app = new Vue({
    el: '#app',
    interval: null,
    data() { 
        return {
            stocks: []
        }
    },
    methods: {
        async update() {
            try {
                
                const url = `${getApiUrl()}/api/getStocks`;
                console.log('Fetching stocks from ', url);

                const response = await fetch(url);
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }

                app.stocks = await response.json();
            } catch (ex) {
                console.error(ex);
            }
        },
        startPoll() {
            this.interval = setInterval(this.update, 5000);
        }
    },
    created() {
        this.update();
        this.startPoll();
    }
});

startPoll 메서드가 폴링을 시작하면 update 메서드가 5초마다 호출됩니다. update 메서드 내에서 GET 요청이 /api/getStocks API 엔드포인트로 전송되고 결과가 UI를 업데이트하는 app.stocks로 설정됩니다.

서버 및 클라이언트 코드는 비교적 간단합니다. 모든 데이터를 가져오고 모든 데이터를 표시합니다. 분석에서 알 수 있듯이 이러한 단순성은 몇 가지 제한 사항을 가져옵니다.

프로토타입 솔루션 분석

Tailwind Traders 엔지니어는 이 타이머 기반 폴링 방식의 몇 가지 단점을 확인했습니다.

  • 불필요한 API 요청: 타이머 기반 폴링 프로토타입에서 클라이언트 애플리케이션은 기본 데이터에 대한 변경이 있는지 여부에 관계없이 서버에 연결합니다.

  • 불필요한 페이지 새로 고침: 서버에서 데이터가 반환되면 데이터가 변경되지 않은 경우에도 전체 주식 목록이 웹 페이지에 업데이트됩니다. 이 폴링 메커니즘은 비효율적인 솔루션입니다.

  • 폴링 간격: 시나리오에 가장 적합한 폴링 간격을 선택하는 것도 어렵습니다. 폴링을 사용하면 백 엔드에 대한 각 호출 비용과 새 데이터에 대한 앱의 응답 속도 사이에서 선택해야 합니다. 새로운 데이터를 사용할 수 있게 되는 시간과 앱이 이를 검색하는 시간 사이에 지연이 발생하는 경우가 많습니다. 다음 그림에서는 이 문제를 보여 줍니다.

    5분마다 새 데이터를 확인하는 타임라인 및 폴링 트리거를 보여 주는 일러스트레이션입니다. 새 데이터는 7분 후에 사용할 수 있게 됩니다. 앱은 10분 후에 다음 폴이 발생할 때까지 새 데이터를 인식하지 못합니다.

    최악의 경우 새 데이터를 감지할 때까지의 잠재적 지연이 폴링 간격과 같을 수 있습니다. 그렇다면 더 짧은 간격을 사용하지 않는 이유는 무엇일까요?

  • 데이터 양: 애플리케이션이 크기 조정됨에 따라 클라이언트와 서버 간에 교환되는 데이터의 양이 문제가 됩니다. 각 HTTP 요청 헤더에는 세션의 쿠키와 함께 수백 바이트의 데이터가 포함됩니다. 특히 부하가 많은 경우 이러한 모든 오버헤드는 낭비되는 리소스를 만들고 서버에 불필요하게 무거운 부담이 됩니다.

이제 프로토타입에 더 익숙해졌으므로 컴퓨터에서 애플리케이션을 실행해 볼 차례입니다.

CORS 지원

Functions 앱의 local.settings.json 파일에서 Host 섹션에는 다음 설정이 포함되어 있습니다.

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "<STORAGE_CONNECTION_STRING>",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
    "COSMOSDB_CONNECTION_STRING": "<COSMOSDB_CONNECTION_STRING>"
  },
  "Host" : {
    "LocalHttpPort": 7071,
    "CORS": "http://localhost:3000",
    "CORSCredentials": true
  }
}

이 구성을 통해 localhost:3000에서 실행되는 웹 애플리케이션이 localhost:7071에서 실행되는 함수 앱을 요청할 수 있습니다. CORSCredentials 속성은 요청에서 자격 증명 쿠키를 허용하도록 함수 앱에 지시합니다.

CORS(원본 간 리소스 공유)는 한 도메인에서 실행되는 웹 애플리케이션이 다른 도메인의 리소스에 액세스할 수 있게 해주는 HTTP 기능입니다. 웹 브라우저는 웹 페이지가 다른 도메인의 API를 호출할 수 없도록 하는 동일 원본 정책이라는 보안 제한을 구현합니다. CORS는 특정 도메인(원본 도메인)에서 다른 도메인의 API를 호출할 수 있는 안전한 방법을 제공합니다.

로컬로 실행할 때 CORS는 게시되지 않는 샘플의 local.settings.json 파일에 구성됩니다. 클라이언트 앱(단위 7)을 배포할 때 클라이언트 앱에서 액세스할 수 있도록 함수 앱의 CORS 설정도 업데이트해야 합니다.