분산 캐시에서 데이터 관계 관리

저자: 이크발 칸

개요

분산 캐시를 사용하면 애플리케이션 성능과 확장성을 크게 향상할 수 있습니다. 인메모리 캐시는 데이터베이스보다 데이터 액세스 속도가 훨씬 빠르기 때문에 애플리케이션 성능이 향상됩니다. 또한 분산 캐시로 여러 서버에 캐시를 확장하고 더 많은 저장 용량뿐만 아니라 초당 더 많은 트랜잭션 처리량을 확보하여 확장성을 달성합니다.

이러한 강력한 이점에도 불구하고 많은 인메모리 캐시가 직면한 한 가지 문제가 있습니다. 그리고 그것은 대부분의 데이터가 관계형인 반면 캐시는 일반적으로 키-값 쌍 개념이 있는 간단한 해시 테이블이라는 사실과 관련이 있습니다. 각 항목은 다른 관련 항목에 대한 지식 없이 독립적으로 캐시에 저장됩니다. 그리고 이것은 하나의 항목이 업데이트되거나 제거되고 관련 항목도 데이터베이스에서 업데이트되거나 제거되는 경우 이를 가져오기 위해 그리고 데이터 무결성을 위해 애플리케이션이 캐시된 여러 항목 간의 관계를 추적하는 것을 어렵게 만듭니다. 이 경우 캐시는 이에 대해 알지 못하고 처리할 수 없습니다.

일반적인 실제 응용 프로그램은 데이터베이스의 다른 데이터 요소와 일대일, 다대일, 일대다 및 다대다 관계가 있는 관계형 데이터를 처리합니다. 이를 위해서는 서로 다른 관련 데이터 요소에서 참조 무결성을 유지해야 합니다. 따라서 하기 위해서는 캐시의 데이터 무결성 유지, 캐시는 이러한 관계를 이해하고 동일한 참조 무결성을 유지해야 합니다.

이러한 상황을 처리하기 위해 Microsoft는 ASP.NET Cache에 캐시 종속성을 도입했습니다. 캐시 종속성을 사용하면 다양한 캐시된 요소를 연결할 수 있으며 캐시된 항목을 업데이트하거나 제거할 때마다 캐시는 데이터 무결성을 보장하기 위해 관련된 모든 캐시된 항목을 자동으로 제거합니다. 그런 다음 응용 프로그램이 다음에 필요할 때 캐시에서 이러한 관련 항목을 찾지 못하면 응용 프로그램은 데이터베이스로 이동하여 이러한 항목의 최신 복사본을 가져온 다음 올바른 참조 무결성을 유지하면서 다시 캐시합니다.

이것은 ASP.NET Cache의 훌륭한 기능이지만 ASP.NET Cache는 의도적으로 단일 서버 내부 프로세스 환경에만 적합한 독립 실행형 캐시입니다. 그러나 확장성을 위해 다음을 사용해야 합니다. 분산 캐시 애플리케이션 프로세스 외부에 있을 수 있고 여러 캐시 서버로 확장할 수 있습니다. NCache 이러한 캐시이며 다행히 분산 환경에서 동일한 캐시 종속성 기능을 제공합니다. 동일한 논리적 클러스터 캐시의 두 부분인 한 다른 물리적 캐시 서버의 캐시된 항목에 따라 한 물리적 캐시 서버의 캐시된 항목을 가질 수 있습니다. 그리고, NCache 위에서 언급한 모든 데이터 무결성 문제를 처리합니다.

이 문서에서는 캐시 종속성을 사용하여 캐시에서 일대일, 일대다 및 다대다 관계를 처리하는 방법을 설명합니다. 그것은 사용 NCache 예를 들어 ASP.NET 캐시에도 동일한 개념이 적용됩니다.

이기는 하지만, NCache 다음을 포함한 다양한 유형의 종속성을 제공합니다. 데이터 종속성, 파일 종속성, SQL 종속성사용자 지정 종속성, 이 문서에서는 캐시된 항목 간의 관계를 처리하기 위한 데이터 종속성에 대해서만 설명합니다.

캐시의 데이터 종속성이란 무엇입니까?

데이터 종속성은 캐시된 항목이 다른 캐시된 항목에 종속되도록 지정할 수 있는 기능입니다. 그런 다음 두 번째 캐시된 항목이 업데이트되거나 제거되면 해당 항목에 종속된 첫 번째 항목도 캐시에서 제거됩니다. 데이터 종속성을 사용하면 A가 B에 종속되고 C에 종속되는 다중 수준 종속성을 지정할 수 있습니다. 그런 다음 C가 업데이트되거나 제거되면 A와 B가 모두 캐시에서 제거됩니다.

다음은 데이터 종속성을 사용하여 다중 수준 종속성을 지정하는 방법에 대한 간단한 예입니다.

public static void CreateDependencies(ICache _cache)
{
    try
    {
        string keyC = "objectC-1000";
        Object objC = new Object();
        string keyB = "objectB-1000";
        Object objB = new Object();
        string keyA = "objectA-1000";
        Object objA = new Object();
        // Initializing cacheItems
        var itemOne = new CacheItem(objA);
        var itemTwo = new CacheItem(objB);
        var itemThree = new CacheItem(objC);
        // Adding objA dependent on ObjB
        itemOne.Dependency = new KeyDependency(keyB);
        itemTwo.Dependency = new KeyDependency(keyC);
        //Adding items to cache
        _cache.Add(keyC, itemThree);
        _cache.Add(keyB, itemTwo);
        _cache.Add(keyA, itemOne);

        // Removing "objC" automatically removes “objB” as well as "ObjA"
        _cache.Remove(keyC);
        _cache.Dispose();
    }
    catch (Exception e)
    {
        throw;
    }
}

다중 수준 데이터 종속성


데이터 관계

이 문서에서는 다음 예제를 사용하여 캐시에서 다양한 유형의 관계를 처리하는 방법을 보여줍니다.

데이터 관계 관리
그림 2: 데이터베이스의 관계

위의 다이어그램에서 다음 관계가 표시됩니다.

  • 일대다: 두 가지 관계가 있으며 다음과 같습니다.
    1. 고객 주문
    2. 주문할 제품
  • 다대일: 두 가지 관계가 있으며 다음과 같습니다.
    1. 고객에게 주문
    2. 제품 주문
  • 다대다: 그러한 관계가 하나 있으며 다음과 같습니다.
    1. 고객 대 제품(주문을 통해)

위의 관계에 대해 다음과 같은 도메인 개체가 설계되었습니다.

class Customer
    {
        public string CustomerID;
        public string CompanyName;
        public string ContactName;
        public string ContactTitle;
        public string Phone;
        public string Country;
        public IList<Order> _OrderList;
    }
    class Product
    {
        public int ProductID;
        public string ProductName;
        public Decimal UnitPrice;
        public int UnitsInStock;
        public int UnitsOnOrder;
        public int ReorderLevel;
  
        public IList<Order> _OrderList;
    }
    class Order
    {
        public int OrderId;
        public string CustomerID;
        public DateTime OrderDate;
        public DateTime RequiredDate;
        public DateTime ShippedDate;
        public int ProductID;
        public Decimal UnitPrice;
        public int Quantity;
        public Single Discount;
        public Customer _Customer;
        public Product _Product;
    }

보시다시피 Customer 및 Product 클래스에는 _주문 목록 이 고객과 관련된 모든 주문 개체 목록을 포함합니다. 마찬가지로 Order 클래스에는 다음이 포함됩니다. _고객_제품 관련 고객 또는 제품 개체를 가리키는 데이터 멤버. 이제 Customer가 로드될 때마다 모든 Order 개체도 로드되도록 하기 위해 데이터베이스에서 이러한 개체를 로드하는 것은 지속성 코드의 작업입니다.

아래에서는 이러한 각 관계가 캐시에서 처리되는 방법을 보여줍니다.

일대일/다대일 관계 처리

캐시에서 다른 개체와 일대일 또는 다대일 관계가 있는 개체를 가져올 때마다 지속성 코드가 관련 개체도 로드했을 수 있습니다. 그러나 응용 프로그램이 해당 시점에 필요하지 않을 수 있으므로 관련 개체를 로드할 필요가 항상 있는 것은 아닙니다. 지속성 코드가 관련 개체를 로드한 경우 이를 처리해야 합니다.

이를 처리할 수 있는 두 가지 방법이 있습니다. 저는 낙관적 방법과 비관적 방법을 각각 부르고 아래에서 각각 설명하겠습니다.

  1. 관계의 낙관적 처리: 여기서는 관계가 있더라도 다른 누구도 관련 개체를 별도로 수정하지 않을 것이라고 가정합니다. 관련 개체를 수정하려는 사람은 캐시의 기본 개체를 통해 이를 가져오므로 기본 및 관련 개체를 모두 수정할 수 있는 위치에 있게 됩니다. 이 경우 이 두 개체를 캐시에 별도로 저장할 필요가 없습니다. 따라서 기본 개체에는 관련 개체가 포함되어 있으며 둘 다 캐시에 하나의 캐시된 항목으로 저장됩니다. 그리고 그들 사이에 데이터 종속성이 생성되지 않습니다.
  2. 비관적인 관계 처리: 이 경우 관련 개체는 다른 사용자가 독립적으로 가져오고 업데이트할 수 있으므로 관련 개체를 별도의 캐시 항목으로 저장해야 한다고 가정합니다. 그런 다음 누군가 관련 개체를 업데이트하거나 제거하면 기본 개체도 캐시에서 제거되기를 원합니다. 이 경우 두 개체 간에 데이터 종속성을 만듭니다.

다음은 낙관적인 상황을 처리하기 위한 소스 코드입니다. 기본 개체의 직렬화에는 관련 개체도 포함되기 때문에 기본 개체와 관련 개체는 모두 하나의 항목으로 캐시됩니다.

static void Main(string[] args)
{
    string cacheName = "myReplicatedCache";
    ICache _cache = CacheManager.GetCache(cacheName);
    OrderFactory oFactory = new OrderFactory();
    Order order = new Order();
    order.OrderId = 1000;
    oFactory.LoadFromDb(order);
    Customer cust = order._Customer;
    Product prod = order._Product;
    var itemOne = new CacheItem(order);
    // please note that Order object serialization will
    // also include Customer and Product objects
    _cache.Add(order.OrderId.ToString(), itemOne);
    _cache.Dispose();
}

다대일 관계의 낙관적 처리

다음은 낙관적 시나리오에서는 데이터 종속성을 사용할 필요가 없기 때문에 비관적 상황을 처리하기 위한 소스 코드입니다.

static void Main(string[] args)
{
    string cacheName = "myReplicatedCache";
    ICache _cache = CacheManager.GetCache(cacheName);
    OrderFactory oFactory = new OrderFactory();
    Order order = new Order();
    order.OrderId = 1000;
    oFactory.LoadFromDb(order);
    Customer cust = order._Customer;
    Product prod = order._Product;
    string custKey = "Customer:CustomerID:" + cust.CustomerID;
    _cache.Insert(custKey, cust);
    string prodKey = "Product:ProductID:" + prod.ProductID;
    _cache.Insert(prodKey, prod);
    string[] depKeys = { prodKey, custKey };
    string orderKey = "Order:OrderID:" + order.OrderId;
    // We are setting _Customer and _Product to null so they
    // don't get serialized with Order object
    order._Customer = null;
    order._Product = null;
    var item = new CacheItem(order);
    item.Dependency = new CacheDependency(null, depKeys);
    _cache.Add(orderKey, item);
    _cache.Dispose();
}

다대일 관계의 비관적 처리

위의 코드는 데이터베이스에서 Order 개체를 로드하고 Order 개체는 다대일 관계를 가지고 있기 때문에 Customer 및 Product 개체는 자동으로 함께 로드됩니다. 그런 다음 응용 프로그램은 Customer 및 Product 개체를 캐시에 추가한 다음 Order 개체를 캐시에 추가하지만 Customer 및 Product 개체 모두에 종속됩니다. 이렇게 하면 이러한 고객 또는 제품 개체가 캐시에서 업데이트되거나 제거되는 경우 데이터 무결성을 유지하기 위해 주문 개체가 캐시에서 자동으로 제거됩니다. 애플리케이션은 이 관계를 추적할 필요가 없습니다.

일대다 관계 처리

캐시에서 다른 객체와 일대다 관계가 있는 객체를 가져올 때마다 지속성 코드는 기본 객체와 모든 일대다 관련 객체의 컬렉션을 모두 로드할 수 있습니다. 그러나 응용 프로그램이 현재 관련 개체를 필요로 하지 않을 수 있으므로 관련 개체를 로드할 필요가 항상 있는 것은 아닙니다. 지속성 코드가 관련 개체를 로드한 경우 캐시에서 해당 개체를 처리해야 합니다. 관련 개체는 모두 하나의 컬렉션에 보관되며 이로 인해 아래에서 논의되는 자체 문제가 발생합니다.

이를 처리할 수 있는 세 가지 방법이 있습니다. 나는 낙관적, 약간 비관적, 매우 비관적이라고 부르고 각각을 아래에서 설명하겠습니다.

  1. 관계의 낙관적 처리: 여기서는 관계가 있더라도 다른 누구도 관련 개체를 개별적으로 수정하지 않을 것이라고 가정합니다. 관련 개체를 수정하려는 사람은 캐시의 기본 개체를 통해 해당 개체를 가져오므로 기본 개체와 관련 개체를 모두 수정할 수 있는 위치에 있게 됩니다. 이 경우 이 두 개체를 캐시에 별도로 저장할 필요가 없습니다. 따라서 기본 개체에는 관련 개체가 포함되어 있으며 둘 다 캐시에 하나의 캐시된 항목으로 저장됩니다. 그리고 그들 사이에 데이터 종속성이 생성되지 않습니다.
  2. 약간 비관적인 관계 처리: 이 경우 관련 개체를 독립적으로 가져올 수 있지만 전체 컬렉션으로만 가져올 수 있으며 개별 개체로는 가져올 수 없다고 가정합니다. 따라서 컬렉션을 하나의 캐시된 항목으로 저장하고 컬렉션에서 기본 개체에 대한 종속성을 만듭니다. 그런 다음 누군가 기본 개체를 업데이트하거나 제거하면 컬렉션도 캐시에서 제거되기를 원합니다.
  3. 정말 비관적인 관계 처리: 이 경우 관련 컬렉션의 모든 개체도 응용 프로그램에서 개별적으로 가져와 수정할 수 있다고 가정합니다. 따라서 컬렉션뿐만 아니라 모든 개별 개체를 캐시에 별도로 저장해야 합니다. 그러나 캐시 서버의 네트워크를 통해 상주할 수 있는 캐시로 여러 번 이동하기 때문에 성능 문제가 발생할 수 있습니다. "캐시에서 컬렉션 처리"를 다루는 다음 섹션에서 이에 대해 논의할 것입니다.

다음은 일대다 관계를 낙관적으로 처리하는 방법의 예입니다. 관련 개체를 포함하는 컬렉션은 캐시에 넣을 때 기본 개체의 일부로 직렬화됩니다.

static void Main(string[] args)
{
    string cacheName = "ltq";
    ICache _cache = CacheManager.GetCache(cacheName);
    CustomerFactory cFactory = new CustomerFactory();
    Customer cust = new Customer();
    cust.CustomerID = "ALFKI";
    cFactory.LoadFromDb(cust);
    // please note that _OrderList will automatically get
    // serialized along with the Customer object
    string custKey = "Customer:CustomerID:" + cust.CustomerID;
    _cache.Add(custKey, cust);
    _cache.Dispose();
}

일대다 관계를 낙관적으로 다루기


다음은 일대다 관계를 다소 비관적으로 처리하는 방법의 예입니다.

static void Main(string[] args)
{
    string cacheName = "myReplicatedCache";
    ICache _cache = CacheManager.GetCache(cacheName);
    CustomerFactory cFactory = new CustomerFactory();
    Customer cust = new Customer();
    cust.CustomerID = "ALFKI";
    cFactory.LoadFromDb(cust);
    IList<Order> orderList = cust._OrderList;
    // please note that _OrderList will not be get
    // serialized along with the Customer object
    cust._OrderList = null;
    string custKey = "Customer:CustomerID:" + cust.CustomerID;
    var custItem = new CacheItem(cust);
    _cache.Add(custKey, custItem);
    // let's reset the _OrderList back
    cust._OrderList = orderList;
    string[] depKeys = { custKey };
    string orderListKey = "Customer:OrderList:CustomerId" + cust.CustomerID;
    IDictionary<string, CacheItem> dictionary = new Dictionary<string, CacheItem>();
    foreach (var order in orderList)
    {
        var orderItem = new CacheItem(order);
        orderItem.Dependency = new CacheDependency(null, depKeys);
        dictionary.Add(orderListKey, orderItem);

    }
    _cache.AddBulk(dictionary);
    _cache.Dispose();
}

일대다 관계를 약간 비관적으로 다루기

위의 예에서 이것과 관련된 Order 객체의 목록은 빠른 별도로 캐시됩니다. 아무도 개별 Order 개체를 개별적으로 직접 수정할 수 없다고 가정하기 때문에 전체 컬렉션이 하나의 항목으로 캐시됩니다. 애플리케이션은 항상 이 고객을 통해 이를 가져오고 전체 컬렉션을 다시 수정하고 다시 캐시합니다.

또 다른 경우는 일대다 관계를 비관적으로 처리하는 것인데, 이는 캐시에서 컬렉션을 처리하는 방법과 유사합니다. 해당 주제는 다음 섹션에서 논의됩니다.

캐시에서 컬렉션 처리

데이터베이스에서 개체 컬렉션을 가져오는 경우가 많습니다. 이것은 실행한 쿼리 때문이거나 "다" 쪽에서 관련 개체 컬렉션을 반환하는 일대다 관계일 수 있습니다. 어느 쪽이든, 얻는 것은 캐시에서 적절하게 처리되어야 하는 개체 모음입니다.

아래에 설명된 대로 컬렉션을 처리하는 두 가지 방법이 있습니다.

  1. 컬렉션의 낙관적 처리: 여기에서 아무도 컬렉션 내부에 보관된 개체를 개별적으로 가져오고 수정할 수 없기 때문에 전체 컬렉션이 하나의 항목으로 캐시되어야 한다고 가정합니다. 컬렉션은 짧은 기간 동안 캐시될 수 있으며 이 가정은 매우 유효할 수 있습니다.
  2. 컬렉션의 비관적 처리: 이 경우 컬렉션 내의 개별 개체를 별도로 가져와 수정할 수 있다고 가정합니다. 따라서 전체 컬렉션을 캐시하지만 각 개별 개체도 캐시하고 컬렉션에서 개별 개체에 대한 종속성을 만듭니다.

다음은 컬렉션을 낙관적으로 처리하는 방법의 예입니다.

static void Main(string[] args)
{
    string cacheName = "myReplicatedCache";
    ICache _cache = CacheManager.GetCache(cacheName);
    CustomerFactory cFactory = new CustomerFactory();
    Customer cust = new Customer();
    string custListKey = "CustomerList:LoadByCountry:Country:United States";
    IList<Customer> custList = cFactory.LoadByCountry("United States");
    IDistributedList<Customer> list = _cache.DataTypeManager.CreateList<Customer>(custListKey);

    // please note that all Customer objects kept in custList
    // will be serialized along with the custList
    foreach (var customer in custList)
    {
        // Add products to list
        list.Add(customer);
    }
    _cache.Dispose();
}

낙관적으로 컬렉션 처리

위의 예에서 전체 컬렉션은 하나의 항목으로 캐시되고 컬렉션 내부에 보관된 모든 Customer 개체는 컬렉션 및 캐시와 함께 자동으로 직렬화됩니다. 따라서 여기에서 데이터 종속성을 생성할 필요가 없습니다.

다음은 컬렉션을 비관적으로 처리하는 방법의 예입니다.

static void Main(string[] args)
{
    string cacheName = "myReplicatedCache";
    ICache _cache = CacheManager.GetCache(cacheName);
    CustomerFactory cFactory = new CustomerFactory();
    Customer cust = new Customer();
    IList<Customer> custList = cFactory.LoadByCountry("United States");
    ArrayList custKeys = new ArrayList();
    // Let's cache individual Customer objects and also build
    // an array of keys to be used later in CacheDependency
    foreach (Customer c in custList)
    {
        string custKey = "Customer:CustomerID:" + c.CustomerID;
        custKeys.Add(custKey);
        _cache.Insert(custKey, c);
    }
    string custListKey = "CustomerList:LoadByCountry:Country:United States";
    // please note that this collection has a dependency on all
    // objects in it separately. So, if any of them are updated or
    // removed, this collection will also be removed from cache
    IDistributedList<Customer> list = _cache.DataTypeManager.CreateList<Customer>(custListKey);
    foreach (var customer in custList)
    {
        // Add products to list
        var item = new CacheItem(customer);
        item.Dependency = new CacheDependency(null, (string[])custKeys.ToArray());
        list.Add(customer);
    }

    _cache.Dispose();
}

컬렉션을 비관적으로 다루기

위의 예에서 컬렉션의 각 개체는 별도의 항목으로 캐시된 다음 전체 컬렉션과 하나의 항목이 캐시됩니다. 컬렉션에는 별도로 캐시되는 모든 개체에 대한 데이터 종속성이 있습니다. 이렇게 하면 이러한 개체가 업데이트되거나 제거되면 컬렉션도 캐시에서 제거됩니다.


저자: 이크발 칸 ~을위한 작품 Alachisoft , .NET 및 Java 분산 캐싱, O/R 매핑 및 SharePoint 스토리지 최적화 솔루션을 제공하는 선도적인 소프트웨어 회사입니다. 당신은 그에게 연락할 수 있습니다 이크발@alachisoft.COM.

© 저작권 Alachisoft 2002 - . 판권 소유. NCache 는 Diyatech Corp.의 등록상표입니다.