Dino Esposito
Wintellect

2003년 12월

적용 대상
Microsoft ASP.NET
Microsoft ADO.NET

요약: 데이터 기반 응용 프로그램의 데이터 검색을 가속화하기 위한 기술을 소개하며 쿼리, 페이징, 정렬 및 캐싱을 위한 성공적인 전략에 대해 논의합니다(7페이지/인쇄 페이지 기준).

목차

데이터 액세스 및 실행 시간
데이터 판독기 개체
SqlDataReader 클래스의 내부
명령 동작
캐싱 전략
페이징 및 정렬 전략
요약

데이터 액세스 및 실행 시간

분산된 데이터 기반 웹 응용 프로그램의 실제 성능은 단일 메트릭을 사용하여 정확히 측정하기 어렵습니다. 실행 시간, 응답 시간, 처리량 등의 다양한 메트릭을 조합하여 측정했을 때 믿을 수 있는 결과를 얻을 수 있는 것입니다. 이러한 요인들은 독립적이지만 밀접한 관계가 있는 매개 변수가 됩니다. 이러한 매개 변수 중 하나를 극단적으로 최적화하면 다음 매개 변수 값이 저하될 수밖에 없으며, 이와 같은 양적인 측면의 불균형은 실제 성능 문제로 이어질 가능성이 높습니다. 성능이 뛰어난 응용 프로그램을 작성하기 위해서는 다양한 메트릭 간에 최적의 균형을 이루어야 합니다.

즉, 최적화를 통해 적절한 성능 향상을 이루려면 규칙에 얽매이지 않아야 합니다. 일반적으로 적용되는 몇 가지 지침이 있지만 안정적인 해결 방법으로 받아들이기는 부족하며 대략적인 사전 지침에 불과합니다.

성능 매개 변수 중에서 처리량이 가장 중요하다고 보편적으로 인식되고 있습니다. 처리량은 단위 시간당 처리되는 요청의 수(일반적으로 초당 요청 수)로 정의됩니다. 처리량이 실질적인 핵심 요인이라는 데 동의한다면 다음에는 처리량을 향상시키기 위해 어떠한 작업을 수행해야 하는지 파악해야 합니다.

처리량은 서버에서 작동되는 클라이언트 스레드 수에 따라 다릅니다. 사용 가능한 프로세서의 수와 서버 응용 프로그램의 프로세서 사용 수준 또한 최종 처리량에 영향을 줍니다. 또한 처리량은 네트워크 지연에 의해 크게 영향을 받습니다. 코드를 조작해서 약간의 성능 향상을 꾀할 수도 있지만 연결이 느린 상태라면 여전히 문제가 됩니다. 이러한 문제는 비교적 해결하기 쉬울 수 있지만 여전히 문젯거리로 남아 있습니다.

또 다른 요인은 의미를 명확히 짐작할 수 있는 매개 변수인 실행 시간입니다. 실행 시간은 요청을 처리하고 클라이언트에 대한 모든 바이트를 생성하는 데 소요되는 시간입니다. 분산된 데이터 기반 응용 프로그램(실제로 거의 모든 응용 프로그램이 이런 유형일 것임)의 경우 실행 시간이 데이터 액세스 및 캐싱 전략, 상태 관리, 그리고 계층에 대해 효과적인 인프라를 사용할 수 있는지 여부 등과 직접적으로 연결되어 있습니다.

이 문서는 포괄적인 개념을 다루고 있지만 성능의 특정 측면에 주안점을 둡니다. 이러한 성능 측면은 단지 최적화를 위한 기본 매개 변수(실행 시간 및 응답 시간) 중 하나에 불과한 것이 아니라 나머지 다른 요인에 상당한 영향을 미치는 중요한 요인입니다. 이 문서에서는 데이터 검색을 가속화하는 기술 몇 가지를 살펴보고 정렬, 페이징 및 캐싱 같은 몇 가지 다른 시나리오에 대해 논의합니다. 이 문서가 명쾌한 해법을 제시하지는 않지만 해결 방법을 찾아내는 데 많은 도움을 줄 수 있을 것입니다.

데이터 판독기 개체

Microsoft .NET 웹 응용 프로그램에서 데이터 액세스는 Microsoft ADO.NET 클래스를 사용하여 진행됩니다. Microsoft에서는 특정 시나리오를 염두에 두고 ADO.NET을 설계했습니다. ADO는 2계층 클라이언트/서버 응용 프로그램이 사용되던 시기에 작성되었지만 ADO.NET은 확장성이 뛰어난 다중 계층 웹 응용 프로그램에서 볼 수 있는 것처럼 연결 해제 상태의 데이터 액세스를 반영하도록 설계되었습니다. 이러한 상황에서 데이터는 계층 간을 이동하며 비즈니스 개체를 통과하여 프레젠테이션에서 저장소로, 저장소에서 프레젠테이션으로 이동됩니다. 이러한 개체의 수는 원하는 확장성과 처리량에 맞게 좀 더 세부적으로 조정할 수 있습니다.

이러한 이유로, 지금까지 ADO.NET 개체를 다룰 때 DataSet가 가장 많이 거론되었습니다. 그렇다면 DataSet란 정확히 무엇일까요? 이것은 전체적인 함수 집합이 메모리 내부 데이터베이스의 데이터와 거의 일치하는 메모리 내부 데이터 캐시입니다. 이 개체는 이진 또는 SOAP 스트림 뿐만 아니라 XML 스키마 쌍으로 serialize할 수 있습니다. 필요에 따라 DataSet를 상태 비저장 또는 상태 저장 형식(DiffGram)으로 저장할 수 있습니다. 이 개체는 serialize하고 나면 계층 간에 쉽게 전송할 수 있습니다. 이러한 특성을 일괄 업데이트와 함께 사용하면 일반적이고 새로우며 확장성이 뛰어난 응용 프로그램 기능을 구현할 수 있습니다.

그러나 DataSet는 연결이 끊어진 데이터의 컨테이너입니다. 따라서 데이터를 가져오는 데 데이터 어댑터를 사용합니다. 데이터 어댑터는 기본적인 SQL 명령 개체를 추상화한 결과입니다. 명령과 달리 데이터 어댑터는 자동 데이터 캐싱 기능을 추가적으로 제공합니다. 즉, 데이터 어댑터는 먼저 데이터를 가져온 다음 DataSet에 캐시하고 연결을 닫은 후 반환합니다. 데이터로 수행하려는 작업 유형에 따라 이 방법이 적절할 수도 있고 그렇지 않을 수도 있습니다. 데이터를 캐시하지 않고 사용만 하며, 앞으로만 이동 가능한 읽기 전용 방식으로 작업하는 경우에는 데이터 판독기를 사용하는 것이 좋습니다. 이러한 사실은 Microsoft SQL Server™ 데이터 판독기 개체인 SqlDataReader 클래스에서 더욱 명확하게 확인할 수 있습니다.

SqlDataReader 클래스의 내부

SqlDataReader 클래스는 SQL Server의 원시 TDS(Tabular Data Stream) 스트림을 사용하여 커서와 같은 방식으로 데이터베이스 행을 읽습니다. 캐시에 저장할 데이터와는 달리, 사용할 데이터(사용자 인터페이스를 채우고, 계산을 수행하고, 도우미 값(helper value)을 검색하기 위한 최신 데이터)를 찾을 때는 데이터 판독기 측면에서 두 가지 사항을 고려해야 합니다. 첫 번째 고려 사항은 클래스의 설계 및 구현에 영향을 미치는 데이터 판독기의 높은 성능과 관련되어 있습니다. 두 번째 고려 사항은 보다 본질적인 측면입니다. 모든 것을 고려할 때 ADO.NET에서는 실제로 데이터 판독기를 통해서만 데이터를 가져올 수 있습니다. 데이터는 명령이나 데이터 어댑터를 사용하여 가져올 수 있습니다. SqlCommand 클래스는 스칼라 또는 판독기를 통해 데이터를 가져옵니다. SqlDataAdapter 클래스는 DataSet을 통해 데이터를 가져옵니다. 그러나 데이터 어댑터는 DataSet를 채우기 위해 데이터 판독기를 사용합니다. 그런 후 어댑터는 모든 행을 따라 스크롤한 다음 지정된 DataSet 인스턴스에 값을 압축해 넣습니다.

SqlDataReader를 만들려면 생성자를 사용하지 말고 SqlCommand 개체의 ExecuteReader 메서드를 호출하십시오. 데이터 판독기를 사용하는 동안 원본 연결도 사용되며 연결 닫기를 제외하고는 다른 작업을 수행할 수 없습니다. 동시 프로세스에 의해 수행된 결과 집합의 변경 내용을 데이터 판독기의 사용자가 즉시 볼 수 있다는 사실에 유의하십시오.

데이터 판독기에는 열 개수 및 현재 결과 집합에 행이 있는지 여부 등과 같은 데이터 행의 특성에 대한 정보를 반환하기 위한 속성이 있습니다. 속성 HasRows는 Microsoft .NET Framework 버전 1.1에 추가되었으며 SqlDataReader에 행이 있는지 또는 비어 있는지 여부를 나타냅니다. 데이터 판독기는 값을 읽기 위한 다양한 메서드를 제공합니다. 데이터 판독기를 사용하여 여러 가지 방법으로 값을 가져올 수 있습니다.

Object o = reader.Item[fieldName];
Object o = reader.Item[fieldIndex];
Object o = reader.GetValue(fieldIndex);
Object o = reader.GetSqlValue(fieldIndex);

이러한 메서드가 항상 동일하게 작동할까요? 실제로 각 메서드는 약간 다르게 작동하며 이로 인해 응용 프로그램 성능에 영향을 줄 수 있습니다.

가장 중요한 첫 번째 차이점은 reader["LastName"]보다 reader[3]과 같은 호출을 처리하는 것이 더 까다로우므로 개발자의 관점에서 볼 때 인덱스를 받아들이는 메서드가 덜 실용적일 수는 있지만 열 이름을 필요로 하는 메서드보다 작동이 더 빠르다는 것입니다. 열 이름은 0부터 시작하는 열 인덱스를 기준으로 내부적으로 항상 확인됩니다. 데이터 판독기의 GetOrdinal 메서드는 이러한 변환 서비스를 제공합니다.

GetValue 메서드 또한 Item 접근자보다 작동이 약간 더 빠릅니다. Item 속성은 간단히 GetValue를 호출하며 이 과정에서 열 이름을 인덱스로 변환합니다. GetSqlValueGetValue의 유일한 차이점은 각 메서드가 반환하는 형식입니다. GetSqlValue 메서드는 원시 SQL Server 형식을 모방한 .NET 형식을 반환하며 GetValue 메서드는 필드의 내용을 기본 .NET 형식의 인스턴스로 반환합니다. 데이터 판독기는 GetString, GetDateTime, GetSqlStringGetSqlDateTime 같은 몇 가지 형식별 메서드를 제공합니다. GetXXX 메서드는 해당하는 GetSqlXXX를 호출한 다음 SQL 래퍼 형식에서 실제 값을 추출합니다. 이 SQL 형식은 다음 의사 코드에 나와 있는 것처럼 .NET 개체와 관련된 래퍼일 뿐입니다.

string GetString(int i) {
   SqlString str = GetSqlString(i);
   return str.Value;
}

GetSqlXXX 메서드는 해당하는 GetXXX 메서드보다 작동이 약간 더 빠릅니다. 이와 같이 성능이 보다 나은 이유는 .NET Framework SqlDbType 열거의 데이터 형식이 SQL Server에서 만들어지는 내부 데이터 형식 표현과 거의 일치하기 때문입니다. 결과적으로 데이터를 변환하지 않고도 응용 프로그램에 반환할 수 있습니다.

GetSqlXXX 메서드를 통해 성능이 크게 향상되는 것은 아니지만 이 메서드를 사용하는 다른 이유가 있습니다. SQL 형식을 사용하면 null 값을 보다 쉽고 편리하게 조작할 수 있습니다. 예를 들어, 검색된 열 중 하나에 포함된 null 값을 .NET 형식으로 저장하려고 하면 예외가 발생합니다. 그러나 null 값을 SQL 형식으로 저장할 때는 어떠한 문제도 발생하지 않습니다. 나중에 각 SQL 형식에 대해 IsNull 메서드를 호출하여 값이 null인지 확인할 수 있습니다. 데이터 판독기 클래스에 대해 IsDBNull 메서드를 호출하면 GetXXX 메서드가 사용될 때 데이터가 null 값을 저장하는지 여부를 확인할 수 있지만 예외를 발생시키지 않으려면 데이터를 검색한 직후에 이 사항을 검사해야 합니다.

SQL 형식을 사용하는 또 다른 이유는 변환 중에 데이터의 정밀도를 유지할 수 있기 때문입니다. 예를 들어 십진 형식인 데이터는 SQL Server에서는 38자리이지만 .NET 형식 시스템에서는 28자리입니다.

SqlDataReader 클래스의 내부 연결을 명확히 이해하기 위해서는 이 클래스가 IEnumerable 인터페이스를 구현한다는 사실을 고려해야 합니다. 이 클래스를 사용하여 데이터 판독기를 서버의 데이터 바인딩된 컨트롤에 데이터 바인딩할 수 있습니다. 데이터 판독기를 DataGrid에 바인딩할 경우 메서드 DataBind가 반환된 직후에 판독기 및 원본 연결을 닫아야 합니다. 마지막으로, 판독기를 닫아도 원본 연결은 자동으로 닫히지 않는다는 점에 유의하십시오. 따라서 연결 개체에 대해 반드시 Close 메서드를 호출해야 합니다. 판독기를 닫을 때 연결도 함께 닫히는 경우는 특수한 명령 동작을 사용하는 경우 뿐입니다.

명령 동작

ExecuteReader 메서드를 호출하여 SqlCommand 개체에서 판독기를 가져옵니다. ExecuteReader를 호출할 때는 명령 동작으로 알려진 특정 작동 모드가 필요할 수 있습니다. ExecuteReader에는 CommandBehavior 형식의 인수를 받아들이는 오버로드가 발생합니다.

cmd.ExecuteReader(CommandBehavior.CloseConnection);

CommandBehavior는 열거이며, 해당 값은 아래에 나열되어 있습니다.

표 1. 데이터 판독기에 대한 명령 동작

동작 설명
CloseConnection 데이터 판독기가 닫힐 때 연결을 자동으로 닫습니다.
Default 이 옵션을 설정할 경우 매개 변수를 지정하지 않고 ExecuteReader를 호출할 때와 같은 결과가 발생합니다.
KeyInfo 이 쿼리는 열 메타데이터 및 기본 키 정보만 반환합니다.
SchemaOnly 이 쿼리는 열 메타데이터만 반환합니다.
SequentialAccess 이 명령을 지정하면 판독기는 데이터를 순차적 스트림으로 로드합니다.
SingleResult 첫 번째 결과 집합만 반환됩니다.
SingleRow 이 쿼리는 단일 행을 반환해야 합니다.

명령 동작을 적절히 사용하면 데이터 판독기의 성능이 향상될 수 있습니다.

SequentialAccess 모드는 반환된 결과 집합의 모든 열에 적용됩니다. 즉, 결과 집합에 열이 표시되는 순서로만 열에 액세스할 수 있습니다. 예를 들어, 열 #1을 읽고 난 후에만 열 #2를 읽을 수 있습니다. 좀 더 정확히 말하자면 특정 위치를 지나쳐서 열을 읽거나 해당 위치로 이동하면 더 이상 이전 열을 읽을 수도 없고 이전 위치로 이동할 수도 없습니다. GetBytes 메서드와 함께 순차적 액세스 방법을 사용하면 제한된 버퍼로 BLOB(이진 대형 개체)를 읽어야 하는 경우에 도움이 될 수 있습니다. 또한 수십 개의 필드가 들어 있는 테이블에서 몇 개의 필드만 읽어야 하는 경우에도 이 방법을 사용하면 데이터 액세스 시간을 최적화하는 데 도움이 될 수 있습니다.

데이터 판독기를 사용하려면 먼저 해당 Read 메서드를 호출해야 합니다. 이 메서드를 호출하면 내부 포인터가 앞으로 이동되고 다음 레코드(있는 경우)가 선택됩니다. 기본적으로 모든 열의 값은 내부적으로 캐시된 다음 다양한 GetXXXGetSqlXXX 메서드에 의해 반환됩니다. 소수의 필드만 필요한 경우에는 SequentialAccess 동작이 코드 최적화에 도움이 됩니다.

여러 결과 집합을 반환하는 쿼리를 실행할 때도 SingleRow를 지정할 수 있습니다. 이 경우 생성된 결과 집합은 모두 제대로 반환되지만 각 결과 집합에 단일 행이 포함됩니다.

캐싱 전략

데이터베이스에서 데이터를 검색하는 작업은 메모리에서 캐시된 데이터를 검색하는 것보다는 확실히 느리지만, 캐싱이 모든 종류의 응용 프로그램에 적합한 것은 아닙니다. 최신 정보를 제공해야 하는 응용 프로그램에서는 캐싱을 사용하기 어렵거나 제한된 시간 동안만 사용할 수 있습니다. 메모리에 데이터를 보관하는 시간이 짧을수록 캐싱의 이점을 활용할 기회도 줄어듭니다. 그러나 값을 캐시하는 시간이 길어질수록 안정성은 높아집니다. 응용 프로그램마다 다른 이러한 장단점을 고려하여 캐시 사용 여부를 결정해야 합니다.

ASP.NET의 기본 제공 캐싱 메커니즘인 Cache 개체는 매우 효율적이지만 이러한 사실만으로 모든 사용자에게 캐싱이 유용할 것이라고 생각하기는 어렵습니다. Cache 개체를 사용하면 전역 데이터의 캐시를 아주 쉽게 구현할 수 있지만 모든 응용 프로그램에서 이러한 방식이 가능한 것은 아닙니다. 누구나 알고 있는 사실이겠지만, 이러한 문제로 인해 프로그램 설계를 망치는 경우가 자주 있습니다.

응용 프로그램에 캐싱 기능을 사용할 때의 장단점을 평가할 때는 데이터 검색 측면과 액세스 속도의 측면을 고려해야 합니다. 필요한 새로 고침 빈도를 고려하여 결과를 조정하면 아주 적절한 공식을 얻을 수 있을 것입니다.

ASP.NET 응용 프로그램의 경우 조회 테이블 같이 자주 사용되는 전역 데이터를 캐시할 경우 특히 효과적입니다. 응용 프로그램의 확장성에 더 많은 영향을 미치는 세션별 데이터는 캐시하지 않는 것이 좋습니다. 또한 웹 팜 시나리오에서는 Cache 개체가 유용하지 않으며 웹 가든의 경우에는 전반적인 성능을 저하시킬 수 있습니다. 이러한 현상은 주로 ASP.NET 작업자 프로세스에서 여러 프로세서를 사용하기 때문에 발생합니다. 웹 가든 모델이 사용될 경우 프로세서마다 작업자 프로세스가 복제됩니다. 전역 개체는 공유 메모리에 저장되지만 응용 프로그램 데이터는 복제됩니다. Cache 개체에 많은 상태를 보유하고 있는 응용 프로그램에 웹 가든 모델을 사용하면 심각한 문제가 발생할 수 있습니다.

두 번째 고려 사항은 SQL Server의 우수한 내부 캐시 메커니즘과 관련되어 있습니다. 이 메커니즘을 사용하면 반복되는 쿼리 및 저장 프로시저를 재활용할 수 있습니다. 응용 프로그램에서 캐시를 사용하지 못하거나 제한적으로만 사용할 수 있도록 하면 응용 프로그램을 웹 팜에 보다 쉽게 이식할 수 있으므로 수평으로 확장할 수 있습니다. 캐시를 포괄적으로 사용하면 응용 프로그램을 수직으로 확장할 수 있으며 더 나은 성능의 시스템(반드시 다중 프로세서 시스템일 필요는 없음)을 활용할 수 있습니다.

페이징 및 정렬 전략

페이징 및 정렬은 어느 정도 공통적인 측면을 갖습니다. 논리적으로 말하면 이러한 두 기능은 새 데이터를 가져오지도, 데이터를 변형하지도 않지만 동일한 데이터의 다양한 뷰를 제공합니다. 대략적으로 보면, 데이터베이스까지 내려갈 필요 없이 최상위 계층(즉, 프레젠테이션 또는 중간 계층)에서 두 기능을 수행할 수 있습니다. 그러나 좀 더 깊이 들여다보면 페이징과 정렬 기능이 상당히 까다로운 기능이라는 것을 알 수 있습니다. 페이징과 정렬 기능은 클라이언트에서 수행할 수 있을 때만 실질적인 이점이 있으며, 그러나 적어도 페이징의 경우는 클라이언트에서 수행할 수 있습니다.

중간 계층에서 페이징을 수행하는 것은 캐싱이 가능한 경우, 즉 응용 프로그램 및 데이터의 특성이 캐싱에 적합하며 확장성 문제가 발생하지 않을 때만 효과적입니다. 데이터 바인딩된 컨트롤인 DataSetCache 개체는 효과적인 접근을 위해 함께 조합해서 사용해야 하는 기본 요소입니다. 데이터는 한 번 또는 주기적으로 검색되며 웹 서버에 캐시된 DataSet에 저장됩니다. DataGrid 컨트롤에 포함된 PagedDataSource 개체를 사용하면 사용자 코드 수준에서 페이징을 자유롭게 수행할 수 있습니다.

캐싱을 사용할 수 없으면 상황이 복잡해집니다. 가장 효과적인 접근 방법은 SQL Server 및 SQL Server에 기본적으로 제공되는 투명한 캐싱 메커니즘을 사용하는 것입니다. 그러나 이 방법을 사용하면 또 다른 문제가 발생할 수 있습니다. 어떤 방법으로 행 "페이지"를 쿼리할 수 있을까요? SQL 언어에는 페이지 개념이 없으며 절 및 조건과 관련된 개념 또한 없습니다. 절을 사용하여 "페이지"를 나타내는 것은 서버 커서의 경우처럼 위치에 따라 레코드를 검색할 수 있는 SELECT 문을 설계하는 것을 의미합니다.

Oracle 같은 일부 데이터베이스 프로그램은 선택한 행에 순번을 지정하는 특수 키워드를 지원합니다. Oracle의 경우 이 키워드는 ROWNUM입니다. 이 문제를 해결하는 가장 효과적인 방법은 관련된 테이블의 구조를 가정하는 것입니다. 예를 들어, 페이징 알고리즘의 기반이 되는 일반 연속 값이 들어 있는 ID 열을 추가할 수 있습니다. 다음/이전 페이징을 만족스럽게 사용할 수 있다면 작업은 간단해집니다. 이 경우 키 값 쌍(페이지의 첫째 키와 마지막 키)을 캐시하여 서버 쪽 페이징을 효과적으로 수행할 수 있습니다. SQL Server 쪽에서 페이징을 수행하면 데이터가 거의 이동하지 않고 웹 서버의 메모리가 소모되지 않는다는 이점이 있습니다.

SQL Server에서 데이터를 쿼리할 경우 데이터 정렬이 좀 더 쉬워집니다. 다른 위치에서 데이터 정렬을 수행하는 것은 무리한 연산 작업이 될 수 있으며 특히 웹 서버에서 수행할 경우 실행 시간이 늘어날 수 있습니다. 정렬은 고차원적인 N*Log(N) 작업입니다. 한 페이지에 표시된 20개의 레코드를 정렬하는 것은 별로 문제가 되지 않습니다. 그러나 사용자마다 캐시된 4만 개의 레코드를 정렬하는 것은 훨씬 더 어려운 일입니다. 다시 말하지만 캐싱이 핵심 요인입니다. 데이터가 전역적이며 세션 간에 공유될 경우 미리 정렬된 데이터를 캐시하는 것을 고려하십시오. 물론 이 정보는 여러분이 필요한 정렬을 사전에 알고 있을 때만 도움이 될 것입니다.

요약

이 문서의 앞 부분에서는 성능 및 척도 개념을 다루었습니다. 그런 후 일반적인 웹 응용 프로그램의 데이터 액세스가 처리량을 높이거나 낮추는 데 중요한 요인이 된다는 사실에 대해 설명했습니다. 최적화 이론 또한 분명히 중요합니다. 그러나 이론적인 수학 개념이나 일반 목적의 알고리즘에 대해서는 다루지 않았으며 실질적인 응용 및 고객 측면에 대해 논의했습니다. 그러므로 벤치마킹이 매우 중요합니다. 각각의 장단점을 고려하여 적절한 수치를 선택해야 합니다. 수치를 잘못 선택하면 전체적인 작업 방법까지 잘못 선택할 수 있습니다. 믿을 수 있는 수치를 얻는 데 도움을 주기 위해 Microsoft에서는 사용자 응용 프로그램에 여러 사용자가 액세스하는 상황을 시뮬레이트하는 테스트를 작성하는 데 사용할 수 있는 Microsoft Visual Studio .NET 도구인 Application Center Test를 제공하고 있습니다. 이 도구를 사용하면 응용 프로그램의 안정성과 응답성을 보다 명확하게 파악할 수 있습니다.

저자 소개

Dino Esposito는 이탈리아 로마에 살고 있는 강사이자 컨설턴트입니다. Wintellect 팀 회원인 Dino는 ASP.NET 및 ADO.NET 분야에 전문 지식을 갖고 있으며 주로 유럽과 미국 전역에서 강의와 컨설턴트 일을 하고 있습니다. 특히 Dino는 Wintellect용 ADO.NET 교재를 관리하며 MSDN Magazine Cutting Edge 칼럼을 맡고 있습니다.

출처:http://www.microsoft.com/korea

+ Recent posts