Jun
19
2007
Two layer cache mechanism
Posted by admin under
ASP.NET 2.0
Overview
Here we will create a two level cache mechanism, where objects are cached in memory using a dictionary, in database, and if not found there the data is taken from a third "real" source.
Background
For the domain name to IP thing I was doing recently I tried to get better performance by adding an inmemory cache of resolved names - cause I knew there were at least some duplicates. I then saw the reason to generelize that code - and that's what I am presenting here.
The client code
string sIP = IPCacher.Instance().Resolve("aspcode.net");
That code will, as stated above, first look in memory, then in database and last (if not found) do a System.Net.Dns.GetHostEntry call. Whenever an extry is found it is of course added to the cache levels above it so to speak, i.e when finding it in the database it's added to the memory. And likewise, when needing to do System.Net.Dns.GetHostEntry it's then added to database AND memory.
Code:
public class IPCache
{
public string Domainname;
public string IP;
public DateTime Datum;
}
public class IPCacher : Dictionary<string,IPCache>
{
private IPCacher()
{
}
private static IPCacher m_Cache = null;
public static IPCacher Instance()
{
if (m_Cache == null)
{
m_Cache = new IPCacher();
}
return m_Cache;
}
public string Resolve(string sDomainName)
{
IPCache val;
//Finns i minnet?
if (this.TryGetValue(sDomainName, out val) == true)
return val.IP;
//Finns i dbtabellen cache?
DataSet ds = DBLayer.GeneratedSQLFunctions.IPCache_Find(DBLayer.Globals.GetConnString(),
sDomainName);
if (ds.Tables[0].Rows.Count > 0)
{
//Lägg till i minnet
IPCache oCache = new IPCache();
oCache.Datum = Convert.ToDateTime(ds.Tables[0].Rows[0]["datum"]);
oCache.Domainname = Convert.ToString(ds.Tables[0].Rows[0]["domainname"]);
oCache.IP = Convert.ToString(ds.Tables[0].Rows[0]["ip"]);
Add(oCache.Domainname, oCache);
return oCache.IP;
}
//Kör resolve och lägg in
try
{
System.Net.IPHostEntry he = System.Net.Dns.GetHostEntry(sDomainName);
if (he.AddressList.Length > 0)
{
string sIP = he.AddressList[0].ToString();
IPCache oCache = new IPCache();
oCache.Datum = DateTime.Now;
oCache.Domainname = sDomainName;
oCache.IP = sIP;
Add(oCache.Domainname, oCache);
DBLayer.GeneratedSQLFunctions.IPCache_Set(DBLayer.Globals.GetConnString(),
sDomainName, sIP);
return oCache.IP;
}
}
catch
{
}
return "";
}
}
Database tables
CREATE TABLE [dbo].[IPCache] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[domainname] [varchar] (255) NULL ,
[ip] [varchar] (20) NULL ,
[datum] [datetime] NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[IPCache] WITH NOCHECK ADD
CONSTRAINT [PK_IPCache] PRIMARY KEY CLUSTERED
(
[id]
) ON [PRIMARY]
GO
CREATE INDEX [IX_IPCache] ON [dbo].[IPCache]([domainname]) ON [PRIMARY]
GO
Stored procedures
CREATE proc IPCache_Find(@domain varchar(255))
as
select * from ipcache where domainname=@domain
and datediff(day,datum,getdate()) < 7
CREATE proc IPCache_Set(@domain varchar(255), @ip varchar(255))
as
If ((select count(*) from ipcache where domainname=@domain) = 0)
BEGIN
insert ipcache select @domain, @ip, getdate()
END
ELSE
BEGIN
update ipcache set ip=@ip, datum=getdate() where domainname=@domain
END
The database code
I have left out the actual database code - DBLayer.GeneratedSQLFunctions.IPCache_Set(DBLayer.Globals.GetConnString(), sDomainName, sIP); DataSet ds = DBLayer.GeneratedSQLFunctions.IPCache_Find(DBLayer.Globals.GetConnString(), sDomainName); I use a codegenerator which draws in some (for tthe purpose of this article) unwanted dependencies so therefore I ask you to implement those yourself.
Shortcomings
I have NO control of the size. Meaning it's useless in a service or something running constantly. One could of course implement some sort FIFO, or even better use the .NET cache object! However that exercise if for you my coding friend! My first guess was trying to improve DNS caching. I knew I had a lot of duplicates in the list so therefore I created a
generic database caching mechanism. The caching engine caches in two layers. 1 look up in memory by using a dictionary. 2. look up in database. 3. if no match, then lookup in dns server(s) chain Of course that didn't help much in this particular scenario, but I needed the caching engine for other stuff as well so I was not feeling I had wasted my time. The real reason for the requests being so slow was instead my own internet connection. Since summer time means no time at the office these days I am using Wrireless LAN internet connection and my guess here was that there's a latency for each single DNS call caused by the network.