Node.js 및 .Net 성능


183

Node.js가 빠르며 많은 양의 부하를 수용 할 수있는 것에 대해 많이 읽었습니다. 누구 든지이 프레임 워크와 다른 프레임 워크, 특히 .Net에 대한 실제 증거가 있습니까? 내가 읽은 대부분의 기사는 일화이거나 .Net과 비교할 수 없습니다.

감사


1
우리가 어떤 종류의 시나리오를 이야기하고 있는지 더 정확하게 설명해 주시겠습니까?
Marcus Granström

1
IIS에서 실행되는 비슷한 웹 응용 프로그램에 대한 .Net 및 Node.js의 성능 비교에 관심이 있습니다.
David Merrilees

1
나는 성능이 높은 웹 사이트를 만드는 사람을 상상할 수 없습니다. .Net 외부의 요구 사항. 가장 기본적인 문제는 성능이 높기 때문에 라이센스 측면에서 비용면에서 효과적이지 않다는 것입니다. 사이트는 일반적으로 확장이 필요합니다. 그리고 나는 .Net 증오가 아닙니다. .Net은 청구서를 지불합니다.
Shane Courtrille

4
Node / express / mongo와 새로운 .net webapi / mongo를 사용하여 작은 REST API의 내부 테스트를 수행해야했으며 클라이언트가 원하는 것에 따라 성능 차이가 있었지만 하루가 끝날 무렵에는 차. 자신의 시나리오에 따라 자체 테스트를 개발해야합니다. 두 언어로 서로 다른 API를 작성하는 데 3 일이 걸렸고 테스트를 올바르게 설정하는 데 며칠이 걸렸습니다. 원격으로 심각한 일을 할 계획이라면 요구 사항에 따라 테스트를 설정하고 부하에 더 적합한 것을 스스로 결정하는 것이 좋습니다.
AlexGad

5
@ShaneCourtrille .Net (프레임 워크)과 Windows (운영 체제)를 혼동하고 있습니다. 그것들은 매우 다르며 .Net에 대한 라이센스 요구 사항이 없습니다 (Linux에서 Mono로 아주 잘 작동합니다).
rainabba

답변:


366

FAST 하고 많은 LOAD를 처리하는 것은 서로 다른 두 가지입니다. 초당 하나의 요청을 처리 할 때 실제로 빠른 서버는 초당 500 개의 요청을 보내는 경우 ( LOAD 아래 ) 완전히 손상 될 수 있습니다 .

또한 정적 페이지와 캐시 된 페이지와 동적 페이지를 고려해야합니다. 정적 페이지가 걱정된다면 IIS가 커널 모드 캐싱을 사용하기 때문에 IIS가 노드를 이길 수 있습니다. 이는 정적 페이지를 요청하는 요청이 커널을 벗어나지 않을 수도 있음을 의미합니다.

ASP.NET과 노드 사이의 비교를 찾고 있다고 생각합니다. 이 전투에서 모든 것이 컴파일 / 통역 된 후에는 성능이 거의 비슷할 것입니다. .NET이 FASTER 이거나 노드가 FASTER 일 수도 있지만 신경 쓰지 않을 정도로 가깝습니다. .NET에 베팅했지만 확실하지 않습니다.

노드가 실제로 매력적인 곳은 LOAD 를 처리하는 것 입니다. 이것은 기술이 실제로 다른 곳입니다. ASP.NET은 스레드 풀의 각 요청에 대한 스레드를 전용으로 지정하고 ASP.NET이 모두 사용 가능 해지면 사용 가능한 모든 스레드 요청이 큐잉되기 시작합니다. @shankar의 예제와 같은 "Hello World"앱을 제공하는 경우 스레드가 차단되지 않고 많은 요청을 처리 할 수 ​​있기 때문에 중요하지 않을 수 있습니다. 스레드가 부족합니다. ASP.NET 모델의 문제점은 스레드를 차단하는 I / O 요청 (DB 호출, 서비스에 대한 http 요청, 디스크에서 파일 읽기)을 시작할 때 발생합니다. 이러한 차단 요청은 스레드 풀의 소중한 스레드가 아무 작업도 수행하지 않음을 의미합니다. 차단이 많을수록ASP.NET 앱을 로드 할 수있게됩니다.

이러한 블로킹을 방지하려면 응답을 기다리는 동안 스레드를 유지할 필요가없는 I / O 완료 포트를 사용하십시오. ASP.NET은 이것을 지원하지만 불행히도 .NET DO N'T의 많은 공통 프레임 워크 / 라이브러리를 지원합니다. 예를 들어 ADO.NET은 I / O 완료 포트를 지원하지만 Entity Framework는 해당 포트를 사용하지 않습니다. 따라서 순수하게 비동기적이고 많은 부하를 처리하는 ASP.NET 응용 프로그램을 만들 수 있지만 대부분의 사람들은 동기식 응용 프로그램을 만드는 것만 큼 쉽지 않으므로 좋아하는 부분을 사용하지 못할 수도 있습니다. 당신이하는 경우 프레임 워크 (예 : linq to entity).

문제는 ASP.NET (및 .NET Framework)이 비동기 I / O에 대해 의견이 맞지 않도록 만들어 졌다는 것입니다. .NET은 동기식 코드 나 비동기식 코드를 작성하더라도 신경 쓰지 않으므로이 결정을 내리는 것은 개발자의 책임입니다. 비동기 작업을 통한 스레딩 및 프로그래밍이 "고난"한 것으로 생각되어 .NET이 모든 사람을 행복하게 만들려고했기 때문입니다 (누브 및 전문가). .NET이 비동기를 수행하기 위해 3-4 가지 패턴으로 끝나기 때문에 훨씬 어려워졌습니다. .NET 4.5는 돌아가서 .NET 프레임 워크를 개조하여 비동기 IO에 대한 의견이있는 모델을 만들려고하지만 관심있는 프레임 워크가 실제로이를 지원할 때까지는 시간이 걸릴 수 있습니다.

반면 노드 디자이너는 모든 I / O가 비동기 적이어야한다고 의견을 내 렸습니다. 이 결정으로 인해 노드 설계자들은 또한 스레드 전환을 최소화하기 위해 각 노드 인스턴스가 단일 스레드가되고 하나의 스레드가 대기중인 코드 만 실행하도록 결정할 수있었습니다. 새로운 요청 일 수도 있고, DB 요청의 콜백 일 수도 있고, http 나머지 요청의 콜백 일 수도 있습니다. 노드는 스레드 컨텍스트 스위치를 제거하여 CPU 효율성을 최대화하려고합니다. 노드는 ALL I / O가 비동기식이라는이 의견을 선택했기 때문에 모든 프레임 워크 / 애드온이이 선택을 지원함을 의미합니다. 노드가 100 % 비동기 인 앱을 작성하는 것이 더 쉽습니다 (노드가 비동기 인 앱을 작성해야하기 때문에).

다시 말하지만, 어떤 식 으로든 증명할 수있는 어려운 숫자는 없지만 노드가 일반적인 웹 응용 프로그램의 LOAD 경쟁에서 이길 것이라고 생각합니다. 고도로 최적화 된 (100 % 비동기) .NET 앱은 동등한 node.js 앱에 돈을 줄 수 있지만 모든 .NET 및 모든 노드 앱의 평균을 가져간 경우 평균 노드에서 더 많은 것을 처리 할 수 ​​있습니다 하중.

희망이 도움이됩니다.


39
ASP.NET은 오랫동안 비동기 요청 처리기를 지원했으며 MVC4에서는 사용이 매우 간단 해졌습니다.
fabspro

12
"이러한 차단 요청은 스레드 풀의 귀중한 스레드가 아무 것도하지 않고 있음을 의미합니다. 차단이 많을수록 ASP.NET 앱이 더 적은 LOAD를 제공 할 수 있습니다." 프런트 엔드 (수신 요청) 또는 백엔드 (실제 작업 스레드)에 문제가되는 이유는 무엇입니까? 클라이언트 요청이 응답을 기다리고 있습니다. 이 토론에서 사람들이 간과하는 핵심은 "처리량"이라고 생각합니다. 서버가 보유한 동시 연결 수와 각 요청에 얼마나 빨리 응답 할 수 있습니까?
sjdirect 23.30에

19
// 내 의견을 편집하지 않겠습니다. 여기에 제가 말하고자하는 것이 있습니다 .// @sjdirect-처리량은 응답 시간과 다릅니다. 응답 시간을 신경 쓰는 것이 좋지만 대기열 시간 + 응답 시간 또는 응답 시간 중 하나를 선택할 수 있습니다. 요청 처리는 두 시나리오 모두에서 오래 걸리지 만 (동기식으로 실행해도 DB 요청이 더 빨리 실행되지는 않지만) 요청 스레드가 차단되면 요청에 큐 시간도 추가됩니다. 이전 요청이 완료 될 때까지 요청 처리를 시작할 수 없기 때문입니다.
Matt Dotson

6
정말 유익했습니다. 감사합니다! 그러나 Entity Framework 6 (현재 RC1)은 이제 .NET 4.5의 비동기 패턴을 지원합니다. msdn.microsoft.com/ko-kr/data/jj819165
국회

4
이것은 매우 추측입니다! 데이터를 갖는 것이 좋을 것입니다. 일반적으로 성능 주제를 진행하는 방법을 결정합니다.
kingPuppy

50

nodejs와 IIS 사이에서 기본적인 성능 테스트를 수행했습니다. "hello, world!"를 제거 할 때 IIS는 nodejs보다 약 2.5 배 빠릅니다. 아래 코드.

내 하드웨어 : Dell Latitude E6510, Core i5 (이중 코어), 8GB RAM, Windows 7 Enterprise 64 비트 OS

노드 서버

runs at http://localhost:9090/
/// <reference path="node-vsdoc.js" />
var http = require("http");
http.createServer(function (request, response) {
response.writeHead(200, { "Content-Type": "text/html" });
response.write("<p>hello, world!</p>");
response.end();
}).listen(9090);

default.htm

hosted by iis at http://localhost/test/
<p>hello, world!</p>

작업 병렬 라이브러리를 사용하는 자체 벤치 마크 프로그램 :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace HttpBench
{
class Program
{
    private int TotalCount = 100000;
    private int ConcurrentThreads = 1000;
    private int failedCount;
    private int totalBytes;
    private int totalTime;
    private int completedCount;
    private static object lockObj = new object();

    /// <summary>
    /// main entry point
    /// </summary>
    static void Main(string[] args)
    {
        Program p = new Program();
        p.Run(args);
    }

    /// <summary>
    /// actual execution
    /// </summary>
    private void Run(string[] args)
    {
        // check command line
        if (args.Length == 0)
        {
            this.PrintUsage();
            return;
        }
        if (args[0] == "/?" || args[0] == "/h")
        {
            this.PrintUsage();
            return;
        }

        // use parallel library, download data
        ParallelOptions options = new ParallelOptions();
        options.MaxDegreeOfParallelism = this.ConcurrentThreads;
        int start = Environment.TickCount;
        Parallel.For(0, this.TotalCount, options, i =>
            {
                this.DownloadUrl(i, args[0]);
            }
        );
        int end = Environment.TickCount;

        // print results
        this.Print("Total requests sent: {0}", true, this.TotalCount);
        this.Print("Concurrent threads: {0}", true, this.ConcurrentThreads);
        this.Print("Total completed requests: {0}", true, this.completedCount);
        this.Print("Failed requests: {0}", true, this.failedCount);
        this.Print("Sum total of thread times (seconds): {0}", true, this.totalTime / 1000);
        this.Print("Total time taken by this program (seconds): {0}", true, (end - start) / 1000);
        this.Print("Total bytes: {0}", true, this.totalBytes);
    }

    /// <summary>
    /// download data from the given url
    /// </summary>
    private void DownloadUrl(int index, string url)
    {
        using (WebClient client = new WebClient())
        {
            try
            {
                int start = Environment.TickCount;
                byte[] data = client.DownloadData(url);
                int end = Environment.TickCount;
                lock (lockObj)
                {
                    this.totalTime = this.totalTime + (end - start);
                    if (data != null)
                    {
                        this.totalBytes = this.totalBytes + data.Length;
                    }
                }
            }
            catch
            {
                lock (lockObj) { this.failedCount++; }
            }
            lock (lockObj)
            {
                this.completedCount++;
                if (this.completedCount % 10000 == 0)
                {
                    this.Print("Completed {0} requests.", true, this.completedCount);
                }
            }
        }
    }

    /// <summary>
    /// print usage of this program
    /// </summary>
    private void PrintUsage()
    {
        this.Print("usage: httpbench [options] <url>");
    }

    /// <summary>
    /// print exception message to console
    /// </summary>
    private void PrintError(string msg, Exception ex = null, params object[] args)
    {
        StringBuilder sb = new System.Text.StringBuilder();
        sb.Append("Error: ");
        sb.AppendFormat(msg, args);
        if (ex != null)
        {
            sb.Append("Exception: ");
            sb.Append(ex.Message);
        }
        this.Print(sb.ToString());
    }

    /// <summary>
    /// print to console
    /// </summary>
    private void Print(string msg, bool isLine = true, params object[] args)
    {
        if (isLine)
        {
            Console.WriteLine(msg, args);
        }
        else
        {
            Console.Write(msg, args);
        }
    }

}
}

결과 :

IIS: httpbench.exe http://localhost/test

Completed 10000 requests.
Completed 20000 requests.
Completed 30000 requests.
Completed 40000 requests.
Completed 50000 requests.
Completed 60000 requests.
Completed 70000 requests.
Completed 80000 requests.
Completed 90000 requests.
Completed 100000 requests.
Total requests sent: 100000
Concurrent threads: 1000
Total completed requests: 100000
Failed requests: 0
Sum total of thread times (seconds): 97
Total time taken by this program (seconds): 16
Total bytes: 2000000

nodejs: httpbench.exe http://localhost:9090/

Completed 10000 requests.
Completed 20000 requests.
Completed 30000 requests.
Completed 40000 requests.
Completed 50000 requests.
Completed 60000 requests.
Completed 70000 requests.
Completed 80000 requests.
Completed 90000 requests.
Completed 100000 requests.
Total requests sent: 100000
Concurrent threads: 1000
Total completed requests: 100000
Failed requests: 0
Sum total of thread times (seconds): 234
Total time taken by this program (seconds): 27
Total bytes: 2000000

결론 : IIS는 nodejs보다 약 2.5 배 빠릅니다 (Windows). 이것은 매우 기초적인 테스트이며 결코 결정적인 것은 아닙니다. 그러나 이것이 좋은 출발점이라고 생각합니다. Node.js는 다른 웹 서버, 다른 플랫폼에서 더 빠를 수 있지만 Windows IIS에서는 승자가됩니다. ASP.NET MVC를 nodejs로 변환하려는 개발자는 진행하기 전에 일시 중지하고 두 번 생각해야합니다.

업데이트 됨 (2012 년 5 월 17 일) Tomcat (Windows의 경우)은 정적 HTML을 제거 할 때 IIS보다 약 3 배 빠른 IIS 손을이기는 것으로 보입니다.

수코양이

index.html at http://localhost:8080/test/
<p>hello, world!</p>

바람둥이 결과

httpbench.exe http://localhost:8080/test/
Completed 10000 requests.
Completed 20000 requests.
Completed 30000 requests.
Completed 40000 requests.
Completed 50000 requests.
Completed 60000 requests.
Completed 70000 requests.
Completed 80000 requests.
Completed 90000 requests.
Completed 100000 requests.
Total requests sent: 100000
Concurrent threads: 1000
Total completed requests: 100000
Failed requests: 0
Sum total of thread times (seconds): 31
Total time taken by this program (seconds): 5
Total bytes: 2000000

결론 : 벤치 마크 프로그램을 여러 번 실행했습니다. Tomcat은 WINDOWS에서 정적 HTML을 처리하는 데 가장 빠른 서버 인 것으로 보입니다.

업데이트 됨 (2012 년 5 월 18 일) 이전에는 10,000 개의 동시 요청과 함께 총 10 만 건의 요청이있었습니다. 총 요청 1,000,000 건과 동시 요청 100,000 건으로 늘 렸습니다. IIS가 비명을 지르며 Nodejs가 최악의 결과를 냈습니다. 아래 결과를 표로 작성했습니다.

WINDOWS에서 정적 HTML을 제공하는 NodeJS와 IIS 및 Tomcat.


56
사과와 고양이를 비교하고 있습니다. Node.js와 ASP.NET MVC를 비교하십시오. 최대 IIS는 정적 파일을 제공하는 데 더 빠르지 만 심각하게 의심합니다.
alessioalex

12
@alessioalex :이 비교가 유효하지 않은 이유를 이해하지 못합니다. 정적 HTML의 응답 시간을 비교하고 있습니다. IIS는 default.htm에서 정적 HTML을 제거하는 반면 nodejs 서버는 동일한 문자열을 제거하고 IIS가 나옵니다. ASP.NET MVC 응용 프로그램을 비교하려면 더 많은 노력과 시간이 필요하며 나중에 할 계획입니다.
Shankar

28
좋아, IIS는 노드보다 Windows에서 정적 파일을 제공하는 것이 더 낫습니다. IIS는 정적 파일 만 제공하며 (Apache 또는 NGINX와 같은) Node는 그 이상을 수행합니다. ASP.NET MVC를 노드와 비교해야합니다 (데이터베이스 조회, 외부 서비스에서 데이터 검색 등). Node over ASP.NET MVC를 사용하면 성능이 크게 향상됩니다.
alessioalex

27
이 작업을 수행하려면 최소한 노드의 특성을 이해하십시오. 하나의 노드 프로세스는 단일 코어 만 사용할 수 있습니다. 따라서 비교하는 것은 하나의 코어에서 실행되는 노드 프로세스와 여러 코어를 사용하는 IIS 및 Tomcat 프로세스입니다. 제대로 비교하려면 노드 클러스터를 실행해야합니다. 사용하기 쉬운 클러스터 솔루션 은 nodejs.org/api/cluster.html 을 참조하십시오 . 그러나 경험에서 알 수 있듯이 노드와 비동기 c #의 차이점은 수행중인 작업에 따라 10-15 %입니다.
AlexGad

14
또한 노드와 IIS 및 Tomcat으로 정적 파일을 테스트하는 것은 의미가 없습니다. 우선, 노드는 정적 파일에는 적합하지 않지만 실제로는 적합하지 않습니다 (적절한 작업에 올바른 도구 사용). 누군가 정적 파일의 속도가 걱정된다면 CDN을 사용해야합니다.
AlexGad

26

NIO 서버 (Node.js 등)는 BIO 서버보다 빠른 경향이 있습니다. (IIS 등). 내 주장을 뒷받침하기 위해 TechEmpower는 웹 프레임 워크 벤치 마크 전문 회사입니다 . 그것들은 매우 개방적이며 모든 프레임 워크를 테스트하는 표준 방법을 가지고 있습니다.

라운드 9 테스트는 현재 최신입니다 (2014 년 5 월). 많은 IIS 버전이 테스트되었지만 aspnet-stripped는 가장 빠른 IIS 변형 인 것 같습니다.

초당 응답 의 결과는 다음과 같습니다 (높을수록 좋습니다).

  • JSON 직렬화
    • nodejs : 228,887
    • aspnet- 스트립 : 105,272
  • 단일 쿼리
    • nodejs-mysql : 88,597
    • ASPNET-STRIP-RAW : 47,066
  • 여러 개의 쿼리
    • nodejs-mysql : 8,878
    • ASPNET-STRIP-RAW : 3,915
  • 일반 텍스트
    • nodejs : 289,578
    • aspnet- 스트립 : 109,136

모든 경우에 Node.js는 IIS보다 2 배 이상 빠릅니다.


1
다중 쿼리 테스트를 제외하고 ASPNET에는 두 개의 항목 (aspnet-stripped-raw 및 aspnet-mysql-raw)이 있으며이 둘은 최상위 njs 항목 인 nodejs-mysql을 능가합니다.
GalacticCowboy

4
다중 쿼리 테스트는 서버 속도를 정확하게 테스트하지 않습니다. 주로 MySQL 드라이버 속도를 테스트하고 있습니다. NodeJS는 주로 MongoDB, CouchDB와 같은 NO-SQL 데이터베이스를 사용합니다. MySQL 드라이버가 최적화되지 않았을 수 있습니다. JSON 직렬화 및 일반 텍스트 테스트는 순수한 서버 속도를 제공하는 경향이 있습니다. 더 신뢰할 수 있습니다.
ttekin

IIS 노드를 사용하면 어떻게됩니까? 내 성능이 저하되거나 동일합니다.
Umashankar

3
벤치 마크 페이지에 대한 링크를 주셔서 감사합니다. 그러나 대답은 업데이트가 필요할 수 있습니다. .NET Core 2.1의 출현으로 상황이 약간 변경되었을 수 있습니다. 예를 들어 2018 JSON 직렬화 벤치 마크에는 ASP.NET Core가 971,122 요청 / 초, Node.js가 561,593 요청 / 초로 나열되어 있으므로 현재 ASP.NET Core는 Node.js보다 거의 두 배 빠릅니다.
stakx-더 이상

13

나는 여기서 시나리오가 매우 중요하다는 Marcus Granstrom에 동의해야합니다.

솔직히 말해서 건축 결정을 내리는 것 같습니다. 내 조언은 관심 영역을 격리하고 고려중인 스택 사이에서 "제빵"하는 것입니다.

하루가 끝나면 결정에 대한 책임이 있으며 "Stackoverflow의 일부 사람이 나에게 괜찮다고 말한 기사를 보여주었습니다"라고 변명하지는 않습니다.


1
MVC.net 웹 사이트의 대안으로 고려할 가치가있는 사람들 (상사를 포함)을 설득 할 무언가를 찾고 있습니다. 지금까지 내가 찾은 것은 더 많은 부하를 지원하고 더 잘 수행 할 수 있다는 일화적인 언급입니다. 실제로 이것을 증명 한 사람이 있습니까?
David Merrilees

17
그러나 MVC 웹 사이트에 어떤 문제가 있습니까? 왜 대안을 찾고 있습니까? 이것이 가장 중요한 Q입니다. 문제가 동시로드가 심한 경우 속도가 느리면 async.net을 사용하고 있는지 확인해야합니다. 여전히 느리다면 코드를 분석하고 병목 현상의 위치를 ​​파악해야합니다. 내 경험상 REAL WORLD 시나리오에서 노드와 비동기 네트 사이에는 큰 차이가 없습니다. 플랫폼을 변경할 수는 있지만 다른 코드 병목 / 두통에 대해 한 세트의 코드 병목 / 두통을 간단히 변경할 수 있습니다.
AlexGad

1

내가 보는 주요 차이점은 node .js가 동적 프로그래밍 언어 (유형 검사)이므로 유형이 런타임에서 파생되어야한다는 것입니다. C # .NET과 같은 강력한 형식의 언어는 이론적으로 노드 .js (및 PHP 등)와의 싸움에서 이론적으로 훨씬 더 많은 잠재력을 가지고 있습니다. 그런데 .NET은 노드 .js보다 C / C ++와 더 나은 기본 상호 운용성을 가져야합니다.


4
JS의 "약한"타이핑 속도가 느려진다는 당신의 제안은 잘못과 관련이 없으며 애플과 스톤을 비교하는 것입니다 (오렌지조차도 제안하는 것보다 더 유사합니다).
rainabba

7
@rainabba 어떤 종류의 계산 (예 : x의 피보나치)을 비교할 때 그는 완전히 정확합니다.
Stan

5
@steve 실제로 Z가 주어지면 JS는 언어이고 .Net은 프레임 워크이므로 여전히 말할 수 없습니다. 그것들은 완전히 다릅니다. .Net 런타임은 특정 프로세서 아키텍처를 위해 컴파일되므로 단일 하드웨어에 대해 특정 코드 청크의 성능을 크게 변경할 수 없습니다. V8에서 알 수 있듯이 JS는 해석되고 실행되며 매우 다양한 속도를 낼 수 있으며 언젠가 JS로 작성된 피보나치 코드가 CLR을 통해 실행되는 코드만큼 빨리 실행되지 않는다고 생각할 이유가 없습니다. 빨리). 사과와 돌; 내가 말했듯이.
rainabba

1
당신이 바로,하지만 내 눈에, 나는 다른 나라, 중국에서, 난 그냥 SQL로 EF 또는 Linq에 알려진 인터뷰 한 많은 많은없는 프로그래머를 모르는 수 있으며, 이러한 프레임 워크는 크게 .NET의 성능이 저하
dexiang

1
JS도 마찬가지입니다. JS가 피보나치를 따라 잡는 동안 .NET이 대기중인 곳에 남아있을 것이라고 정말로 생각하십니까?
quanben
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.