posted by 방랑군 2012. 1. 8. 00:46

출처 :  http://blog.naver.com/guruby?Redirect=Log&logNo=140031649144 

 이 글은 ASP.NET 개발을 책임지고 있는 Scott Guthrie의 블로그에서 가져온 글입니다.

원문은 http://weblogs.asp.net/scottgu/archive/2006/11/28/tip-trick-implement-donut-caching-with-the-asp-net-2-0-output-cache-substitution-feature.aspx에서 보실 수 있습니다.

 

이번 글은 좀 길어서 제대로 번역했는 지 의심스럽기도 합니다..
"번역은 반역이다"라는 말을 실감하고 있습니다..

 

-------------------------------------------------------------------------------


도넛 캐싱 구현 - ASP.NET 2.0 Output Cache Substitution

 

약간의 배경지식:
ASP.NET에서 매우 강력하지만 아직은 덜 사용되고 있는 것 중의 하나로 캐싱이 있다.
ASP.NET 캐싱 기능은 반복적인 사용자 요청에 대해 반복적으로 수행되는 것을 피할 수 있게 한다.
일단 한번 html 내용이나 data를 만들고 나서는 작업 결과를 저장해 놓았다가 재사용한다.
이것은 애플리케이션의 성능을 드라마틱하게 향상시킬 수 있다.
그리고 데이터베이스같은 중요한 자원의 부담을 줄일 수 있다.

 

Steve Smith는 ASP.NET 1.1에서의 캐싱에 대해 몇년 전 MSDN에 좋은 기사를 썼다.
그 기사는 ASP.NET 1.1 캐싱 기능의 몇가지 기본 사항과 사용방법에 대한 글이다.
만일 ASP.NET 캐싱 기능을 전에 사용해본 적이 없다면, 한번 시도해 볼 것을 권한다.
ASP.NET 2.0은 캐싱에 두가지 매우 중요한 기능을 추가했다.

 

1) SQL 캐쉬 무효화 지원 (SQL Cache Invalidation Support)
이것은 데이터베이스의 테이블이나 로우가 수정되면 자동적으로 캐쉬가 무효화되게 한다.
예를 들어 전자상거래 사이트의 제품 목록 페이지가 캐쉬되고 있는 상태에서
언제라도 가격을 바꾸면 바로 반영된다. (옛날 가격을 보여주지 않는다)

 

2) 출력 캐쉬 대체 (Output Cache Substitution)
내가 "도넛 캐싱"이라고 부르는 이 재치있는 기능은 출력 캐쉬된 페이지에서 일부분만 변하게 할 수 있다.
이것은 전체 페이지 출력 캐싱을 좀더 적극적으로 사용할 수 있게 한다.
부분 캐싱을 위해서 페이지를 여러개의 .ascx 사용자 컨트롤로 나눌 필요가 없다.
아래 팁은 이 기능을 만들게 된 동기와 구현 방법에 대해 설명한다.

 


실제 세계에서의 시나리오:

정해진 제품 카테고리로부터 제품 목록을 보여주는 페이지를 만들고자 한다.
제품목록 페이지인 Products.aspx 에는 <asp:datalist> 컨트롤이 있는데

middle-tier의 product data에 바인딩되어 있다.
이 페이지를 출력 캐쉬하면 매번 같은 요청에 대해 데이터베이스를 사용하지 않을 수 있다.
구현은 쉽다. 그저 페이지 맨위에 <%@ OutputCache %> 지시를 추가하면 된다.

 

아래 페이지에서 출력 캐쉬가 어떻게 설정되어 있는 지 보라.
100,000초 동안 캐쉬되고 있다가 northwind의 제품테이블이 수정되면 다음 요청부터 페이지가 즉시 갱신된다.
출력캐쉬 지시에 "VaryByParam" 속성이 있다. 이것은 ASP.NET이 categoryID별로 페이지를 따로 캐쉬하게 한다.
(예를 들어 Products.aspx?categoryId=1과 Products.aspx=2는 따로 저장된다.)

 

Products.aspx:

 

<%@ Page Language="VB" MasterPageFile="~/Site.master" AutoEventWireup="false" CodeFile="Products.aspx.vb" Inherits="Products" %>
<%@ OutputCache Duration="100000" VaryByParam="CategoryID" SqlDependency="northwind:products" %>

 

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div class="catalogue">
    <asp:DataList ID="DataList1" RepeatColumns="2" runat="server">
        <ItemTemplate>
            <div class="productimage">
                <img src="images/productimage.gif" />
            </div>
            <div class="productdetails">
            
                <div class="ProductListHead">
                    <%#Eval("ProductName")%>
                </div>
                
                <span class="ProductListItem">
                    <strong>Price:</strong>
                    <%# Eval("UnitPrice", "{0:c}") %>
                </span>
            </div>
        </ItemTemplate>
    </asp:DataList>
    <h3>Generated @ <%=Now.ToLongTimeString()%></h3>
</div>
</asp:Content>

 


Products.aspx.vb:

 

Partial Class Products
    Inherits System.Web.UI.Page

    Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Dim products As New NorthwindTableAdapters.ProductsTableAdapter

        DataList1.DataSource = products.GetProductsByCategoryID(Request.QueryString("categoryId"))
        DataList1.DataBind()

    End Sub

End Class


브라우저에서 접근하면 아래와 같이 보인다.


 


 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

아래쪽의 timestamp를 주의깊게 봐라.
이 timestamp는 100,000초가 지나거나 product 테이블의 데이터가 수정되면 바뀐다.
이 페이지는 모든 HTTP 요청에 대해 캐쉬된 것을 보여준다.
이 방법은 서버가 초당 1000여번의 요청 처리를 가능하게 하고 데이터베이스는 사용하지 않게 한다.
(그래서 무지하게 빠르다)

 

 

문제점:

위 예제의 한가지 문제점은 환영 메시지와 사용자 이름에서 발생한다. (위쪽 빨간 동그라미 참조)
이것은 ASP.NET 2.0 <asp:loginname> 컨트롤을 사용해서 Site.Master 페이지에서 생성되는 메시지이다.

 

<div class="header">
    <h1>Caching Samples</h1>
            
    <div class="loginstatus">
        <asp:LoginName FormatString="Welcome {0}!" runat="server" />
    </div>        
</div>

 

문제는 전체 페이지를 캐쉬하기 때문에 발생한다.
이 페이지에 접근하는 첫번째 사용자의 이름이 캐쉬되어서 다른 사용자에게 잘못된 환영 메세지를 보여주게 된다.
(이건 더 안좋은 상황이다..)

 


해결책:


이 방법을 해결하는데 두가지 방법이 있다.

 

첫번째 방법은 페이지를 전면 개편해서 사용자 컨트롤로 도배하는 것이다.
캐쉬될 필요가 있는 내용을 사용자 컨트롤 안에 넣고

각 .ascx 사용자 컨트롤의 맨 위에 <%@ OutputCache %>을 추가한다.
이 방법 역시 매번 데이터베이스에 접근하는 것을 피하고 사용자 이름도 정확하게 나오도록 한다.
이것은 ASP.NET 1.1에서 사용하는 방법인데 당연히 ASP.NET 2.0에서도 된다.


출력 캐쉬 대체 블럭 - <asp:substitution> 컨트롤 사용:
출력 캐쉬 대체 블럭은 전체가 캐쉬된 페이지에서 변경될 수 있는 부분을 표시할 수 있게 한다.
(예를 들면 위의 예제에서 사용자 이름이 그렇다)
나는 때때로 이것을 "도넛 캐싱 기능"이라고 부른다.
왜냐하면 페이지의 바깥 부분은 캐쉬되고 안쪽의 몇몇 구멍이 동적으로 변하기 때문이다.

이것은 사용자 컨트롤을 사용한 페이지 부분 캐쉬와 정확히 반대되는 개념이다.
페이지 부분 캐쉬는 페이지 전체가 동적으로 변하고 일부분이 캐쉬된다.

출력 캐쉬 대체는 (정확히 위의 Products.aspx 예에서 처럼) 페이지 전체를 출력 캐쉬해서 구현할 수 있다.
페이지 중에서 동적으로 변경되는 부분만 <asp:substitution> 컨트롤로 표시하면 된다.

 

<div class="header">
    <h1>Caching Samples</h1>
    
    <div class="loginstatus">
        <asp:Substitution ID="Substitution1" runat="server" MethodName="LoginText" />
    </div>
</div>

 

예를 들어 위 시나리오에서 환영 메세지를 동적으로 만들고 싶다면,
아래 메서드를  Site.Master 코드 비하인드 파일에 넣고 부르면 된다.

 

Partial Class Site
    Inherits System.Web.UI.MasterPage

    Shared Function LoginText(ByVal Context As HttpContext) As String
        Return "Hello " & Context.User.Identity.Name
    End Function

End Class

 

이제 전체 페이지 중에서 <asp:substitution> 컨트롤 부분만 빼고 출력 캐쉬되었다.

만일 쇼핑 카트같은 추가적인 개인 정보를 넣고 싶다면 당연히 그럴 수 있다.
좋은 점은 페이지의 다른 부분은 모두 캐쉬되었서 페이지를 생성하기 위해서
더이상 데이터베이스에 접근하지 않아도 된다는 것이다.
(이것이 의미하는 것은 한대의 서버로도 초당  수천번의 페이지 요청을 처리할 수 있다는 것이다)

사실은 요청을 처리하는 동안 어떠한 컨트롤도 생성되지 않는다. 단지 위의 static 메서드만 불린다.
그래서 모든 것이 무지 빠르다..

 


출력 캐쉬 대체 블럭 - Response.WriteSubstitution 메서드를 사용:

<asp:substitution>로 바뀌는 부분을 표시하는 방법 외에도 Response.WriteSubstitution를 사용하는 방법도 있다.

이 메서드는 HttpResponseSubstitutionCallback 위임 개체를 전달인자로 가진다.
그래서 애플리케이션 어디에서나 구현 가능하다.

(code-behind 클래스의 static 메서드에서 가능한 것이 아니다)

<asp:substitution> 도 내부적으로는 이 메서드를 사용해서 code-behind 클래스의 delegate와 연결한다.
같은 방법으로 당신의 사용자 컨트롤이나 페이지에서 사용할 수 있다.


결론:

아직도 캐쉬기능을 사용하지 않는 ASP.NET 애플리케이션을 발견하고 있다.
ASP.NET은 전체 페이지 출력 캐쉬, 페이지 부분 출력 캐쉬, 그리고 이제는 "도넛 캐싱"을 지원한다.
이것은 파라미터나 로직에 따라 캐쉬된 내용이 바뀔 수 있고 데이터베이스가 바뀌면 자동으로 내용을 갱신하게 한다.
일정 수준 이상의 애플리케이션이라면 캐쉬를 사용하지 않는 애플리케이션을 찾기 힘들 것이다.

 

나는 강력히 모든 ASP.NET의 캐쉬 기능을 알아 볼 것을 권한다.
다른 캐쉬 사용 예를 보기 원한다면 내 Tips/Tricks talk from the recent ASP.NET Connections event을

다운로드하길 바란다.
이 발표에서 나는 어떻게 전체 페이지 캐싱, 페이지 부분 캐싱, 대체 블럭 캐싱, SQL 캐쉬 무효화를 하는지 보여준다.
그리고 추가적인 ASP.NET 팁들 보려면 내 ASP.NET Tips, Tricks and Resources page를 보기 바란다.

 

이 글이 도움이 되기를
Scott

'PP > ASP.NET' 카테고리의 다른 글

[ASP.NET] Repeater(리피터) 2  (0) 2012.01.08
[ASP.NET] Repeater(리피터) 1  (0) 2012.01.08
[캐싱] Caching Process  (0) 2012.01.08
[캐싱] ASP.NET 캐싱 기능을 살펴보자.  (0) 2012.01.08
[캐싱] 웹사이트 캐싱전략  (0) 2012.01.08