﻿<?xml version="1.0" encoding="utf-8"?><rss version="2.0"><channel><title>Ayende @ Rahien</title><link>http://ayende.com</link><description>Ayende @ Rahien</description><copyright>Copyright (C) Ayende Rahien  2004 - 2021 (c) 2026</copyright><ttl>60</ttl><item><title>Ayende Rahien commented on Elegancy challenge: Cacheable Batches</title><description>Hendry,
You don't need locking to do that.
And the issues isn't of concurrent request, the issue is of concurrent _system_. 2 concurrent requests would make 2 separate requests, most probably, nothing wrong there.
But you have to account for concurrency in the design because that is where it will run.</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment27</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment27</guid><pubDate>Sun, 28 Aug 2011 18:48:29 GMT</pubDate></item><item><title>Hendry Luk commented on Elegancy challenge: Cacheable Batches</title><description>@Mark, regarding list/array optimization, I think Linq would be most efficient solution. E.g. ids.Select(GetDocument).ToArray();

@Ayende, regarding thread-safetiness, why does that matter. Why is it important that 2 concurrent requests with the same id MUST make exactly 1 request? I think the performance overhead to ensure this requirement (i.e. locking) would outweigh its gain.</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment26</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment26</guid><pubDate>Sun, 28 Aug 2011 16:51:28 GMT</pubDate></item><item><title>Ayende Rahien commented on Elegancy challenge: Cacheable Batches</title><description>Olcay,
Take a look at the RaccoonBlog code on github :-)</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment25</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment25</guid><pubDate>Fri, 26 Aug 2011 10:18:07 GMT</pubDate></item><item><title>Olcay Şeker commented on Elegancy challenge: Cacheable Batches</title><description>@Ayende, can we see your solution about this challenge?</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment24</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment24</guid><pubDate>Fri, 26 Aug 2011 10:16:33 GMT</pubDate></item><item><title>Mark commented on Elegancy challenge: Cacheable Batches</title><description>Matt: I am using GetValues.</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment23</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment23</guid><pubDate>Thu, 25 Aug 2011 17:30:17 GMT</pubDate></item><item><title>Matt Warren commented on Elegancy challenge: Cacheable Batches</title><description>I think that only applies if you're using the GetValues(..) method, but not if you're using the Indexer or Item property (see http://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache.item.aspx).

Either way it's a small detail and I like the your solution</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment22</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment22</guid><pubDate>Wed, 24 Aug 2011 15:08:21 GMT</pubDate></item><item><title>Mark commented on Elegancy challenge: Cacheable Batches</title><description>Matt,

MemoryCache returns all the keys in the Dictionary, with nulls where there was no matching value.</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment21</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment21</guid><pubDate>Wed, 24 Aug 2011 14:22:51 GMT</pubDate></item><item><title>Matt Warren commented on Elegancy challenge: Cacheable Batches</title><description>Mark,

1 small bug, the line
  foreach (var value in _uncachedResolver(keys.Where(k =&gt; values[k] == null)))

should be
  foreach (var value in _uncachedResolver(keys.Where(k =&gt; !values.ContainsKey(k))))

Otherwise it throws an exception when the key isn't in a cache rather than fetching it.</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment20</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment20</guid><pubDate>Wed, 24 Aug 2011 13:32:05 GMT</pubDate></item><item><title>Jesse commented on Elegancy challenge: Cacheable Batches</title><description>I rather like Mark's solution.</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment19</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment19</guid><pubDate>Wed, 24 Aug 2011 13:00:17 GMT</pubDate></item><item><title>Mark commented on Elegancy challenge: Cacheable Batches</title><description>I've posted that class along with how it would be used by ClientProxy as a gist: https://gist.github.com/1167813</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment18</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment18</guid><pubDate>Wed, 24 Aug 2011 11:16:41 GMT</pubDate></item><item><title>Mark commented on Elegancy challenge: Cacheable Batches</title><description>Yes, I forgot to fix that check line to account for Nulls in the dictionary that comes back from the cache. So that line should be

    if (values.Any(kvp =&gt; kvp.Value == null))

and done.

Interesting to note that if you post enough comments, the Captcha stops appearing. Nice UX.</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment17</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment17</guid><pubDate>Wed, 24 Aug 2011 11:05:20 GMT</pubDate></item><item><title>Ayende Rahien commented on Elegancy challenge: Cacheable Batches</title><description>Mark,
That is pretty bad, yes. Mostly because unless managed properly, caches are actually memory leaks :-)
No worries for this scenario, but you might want to check how much trouble that got us with the RavenDB caching</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment16</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment16</guid><pubDate>Wed, 24 Aug 2011 10:59:28 GMT</pubDate></item><item><title>Ayende Rahien commented on Elegancy challenge: Cacheable Batches</title><description>Mark,
Oh! Good point, I didn't see that, I am sorry.
Last thing to fix is what to do when everything is cached, you don't need to call the server then.</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment15</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment15</guid><pubDate>Wed, 24 Aug 2011 10:58:31 GMT</pubDate></item><item><title>Mark commented on Elegancy challenge: Cacheable Batches</title><description>Also, I'm probably doing something hideous with my creation of the MemoryCache class with a GUID as the name. I suspect a better way would be to use a named region in the Default cache.</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment14</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment14</guid><pubDate>Wed, 24 Aug 2011 10:57:28 GMT</pubDate></item><item><title>Mark commented on Elegancy challenge: Cacheable Batches</title><description>While I'm learning about MemoryCache, which is great, more and more requirements are rearing their heads here.

    class ElegantCache&lt;TValue&gt; : IDisposable
    {
        private readonly MemoryCache _cache = new MemoryCache(Guid.NewGuid().ToString());
        private readonly CacheItemPolicy _cacheItemPolicy;
        private readonly Func&lt;TValue, string&gt; _keyResolver;
        private readonly Func&lt;IEnumerable&lt;string&gt;, IEnumerable&lt;TValue&gt;&gt; _uncachedResolver;

        public ElegantCache(CacheItemPolicy cacheItemPolicy, Func&lt;TValue, string&gt; keyResolver, Func&lt;IEnumerable&lt;string&gt;, IEnumerable&lt;TValue&gt;&gt; uncachedResolver)
        {
            _cacheItemPolicy = cacheItemPolicy;
            _keyResolver = keyResolver;
            _uncachedResolver = uncachedResolver;
        }

        public IEnumerable&lt;TValue&gt; Get(string[] keys)
        {
            var values = _cache.GetValues(keys) ?? new Dictionary&lt;string, object&gt;();
            if (values.Count &lt; keys.Length)
            {
                foreach (var value in _uncachedResolver(keys.Where(k =&gt; values[k] == null)))
                {
                    var key = _keyResolver(value);
                    values[key] = value;
                    _cache.Set(key, value, _cacheItemPolicy);
                }
            }
            return keys.Select(key =&gt; values[key]).Cast&lt;TValue&gt;();
        }

        public void Dispose()
        {
            _cache.Dispose();
        }
    }


I'm not making multiple DB calls, the _uncachedResolver function takes a list of strings and returns a list of values. The function would look like:

    ids =&gt;
    {
        var request = WebRequest.Create("http://server/get?id=" + string.Join("&amp;id=", ids));
        using (var stream = request.GetResponse().GetResponseStream())
        {
            return GetResults(stream);
        }
    }
</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment13</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment13</guid><pubDate>Wed, 24 Aug 2011 10:53:41 GMT</pubDate></item><item><title>Ayende Rahien commented on Elegancy challenge: Cacheable Batches</title><description>Mark,
I apologize, my (unvoiced) assumption was that the cache lifetime is longer than the lifetime of the proxy

Good catch with regards to the server code</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment12</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment12</guid><pubDate>Wed, 24 Aug 2011 10:44:15 GMT</pubDate></item><item><title>Mark commented on Elegancy challenge: Cacheable Batches</title><description>Ayende,

Your use case for the proxy showed it being used and disposed in a very short space of time, so memory leakage, thread-safety and performance around the overall size of the cache were not an obvious part of your requirements. You simplified the problem drastically, I simplified the solution likewise.

While we're in code review mode, your server method is inefficient in terms of collection management. You should create the List with the known size to prevent array copies as the list resizes its internals, or, even better, just create the array and then use a for loop to populate it:

    public JsonDocument[] GetDocument(string[] ids)
    {
      var  results = new JsonDocument[ids.Length];
      for(int i = 0; i &lt; ids.Length; i++)
      {
        results[i] = GetDocument(ids[i]);
      }
      return results;  
    }</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment11</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment11</guid><pubDate>Wed, 24 Aug 2011 10:41:00 GMT</pubDate></item><item><title>Ayende Rahien commented on Elegancy challenge: Cacheable Batches</title><description>Mark,
In the second version, same problem in that GetValues return all keys, even not on the cache.
You are still making N calls to the DB.
Your call to AddOrGetExisting will get an existing (potentially older) value that has been already set in the cache.
You should prefer to use the value from the server</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment10</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment10</guid><pubDate>Wed, 24 Aug 2011 10:40:45 GMT</pubDate></item><item><title>Ayende Rahien commented on Elegancy challenge: Cacheable Batches</title><description>Mark,
a) The GetValues will give you all the keys back (with the value set to null), so the code would fail.
b) You are resolving those items one at a time, not all at once, which means that you just broke batching.
</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment9</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment9</guid><pubDate>Wed, 24 Aug 2011 10:39:06 GMT</pubDate></item><item><title>Mark commented on Elegancy challenge: Cacheable Batches</title><description>Non-leaking, more performant, thread-safe...

    class ElegantCache&lt;TValue&gt; : IDisposable
    {
        private readonly MemoryCache _cache = new MemoryCache(Guid.NewGuid().ToString());
        private readonly CacheItemPolicy _cacheItemPolicy;
        private readonly Func&lt;TValue, string&gt; _keyResolver;
        private readonly Func&lt;IEnumerable&lt;string&gt;, IEnumerable&lt;TValue&gt;&gt; _uncachedResolver;

        public ElegantCache(CacheItemPolicy cacheItemPolicy, Func&lt;TValue, string&gt; keyResolver, Func&lt;IEnumerable&lt;string&gt;, IEnumerable&lt;TValue&gt;&gt; uncachedResolver)
        {
            _cacheItemPolicy = cacheItemPolicy;
            _keyResolver = keyResolver;
            _uncachedResolver = uncachedResolver;
        }

        public IEnumerable&lt;TValue&gt; Get(string[] keys)
        {
            var values = _cache.GetValues(keys) ?? new Dictionary&lt;string, object&gt;();
            if (values.Count &lt; keys.Length)
            {
                foreach (var value in _uncachedResolver(keys.Where(k =&gt; !values.ContainsKey(k))))
                {
                    var key = _keyResolver(value);
                    values.Add(key, _cache.AddOrGetExisting(key, value, _cacheItemPolicy));
                }
            }
            return keys.Select(key =&gt; values[key]).Cast&lt;TValue&gt;();
        }

        public void Dispose()
        {
            _cache.Dispose();
        }
    }</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment8</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment8</guid><pubDate>Wed, 24 Aug 2011 10:31:05 GMT</pubDate></item><item><title>Mark commented on Elegancy challenge: Cacheable Batches</title><description>Non-leaking version:

    class ElegantCache&lt;TValue&gt; : IDisposable
    {
        private readonly MemoryCache _cache = new MemoryCache(Guid.NewGuid().ToString());
        private readonly CacheItemPolicy _cacheItemPolicy;
        private readonly Func&lt;TValue, string&gt; _keyResolver;
        private readonly Func&lt;IEnumerable&lt;string&gt;, IEnumerable&lt;TValue&gt;&gt; _uncachedResolver;

        public ElegantCache(CacheItemPolicy cacheItemPolicy, Func&lt;TValue, string&gt; keyResolver, Func&lt;IEnumerable&lt;string&gt;, IEnumerable&lt;TValue&gt;&gt; uncachedResolver)
        {
            _cacheItemPolicy = cacheItemPolicy;
            _keyResolver = keyResolver;
            _uncachedResolver = uncachedResolver;
        }

        public IEnumerable&lt;TValue&gt; Get(string[] keys)
        {
            var values = _cache.GetValues(keys) ?? new Dictionary&lt;string, object&gt;();
            if (values.Count &lt; keys.Length)
            {
                foreach (var value in _uncachedResolver(keys.Except(values.Keys)))
                {
                    values.Add(_keyResolver(value), value);
                    _cache.Add(_keyResolver(value), value, _cacheItemPolicy);
                }
            }
            return keys.Select(key =&gt; values[key]).Cast&lt;TValue&gt;();
        }

        public void Dispose()
        {
            _cache.Dispose();
        }
    }
</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment7</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment7</guid><pubDate>Wed, 24 Aug 2011 10:25:16 GMT</pubDate></item><item><title>Ayende Rahien commented on Elegancy challenge: Cacheable Batches</title><description>Mark,
Aside from everything else:
keys.Except(_cache.Keys)
Is probably much more expensive than doing a dictionary lookup for each key, since you are forcing iteration of the entire thing, which is very expensive for large number of items.

Same issue regarding the memory leak as before.
Also, not safe for multi threading</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment6</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment6</guid><pubDate>Wed, 24 Aug 2011 10:17:46 GMT</pubDate></item><item><title>Mark commented on Elegancy challenge: Cacheable Batches</title><description>Revised: keys.Join line was abominable.

    class ElegantCache&lt;TKey, TValue&gt;
    {
        private readonly Dictionary&lt;TKey, TValue&gt; _cache = new Dictionary&lt;TKey, TValue&gt;(); 
        private readonly Func&lt;TValue, TKey&gt; _keyResolver;
        private readonly Func&lt;IEnumerable&lt;TKey&gt;, IEnumerable&lt;TValue&gt;&gt; _uncachedResolver;

        public ElegantCache(Func&lt;TValue, TKey&gt; keyResolver, Func&lt;IEnumerable&lt;TKey&gt;, IEnumerable&lt;TValue&gt;&gt; uncachedResolver)
        {
            _keyResolver = keyResolver;
            _uncachedResolver = uncachedResolver;
        }

        public IEnumerable&lt;TValue&gt; Get(TKey[] keys)
        {
            var uncachedKeys = keys.Except(_cache.Keys).ToList();
            if (uncachedKeys.Count &gt; 0)
            {
                foreach (var value in _uncachedResolver(uncachedKeys))
                {
                    _cache.Add(_keyResolver(value), value);
                }
            }
            return keys.Select(key =&gt; _cache[key]);
        }
    }
</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment5</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment5</guid><pubDate>Wed, 24 Aug 2011 10:12:56 GMT</pubDate></item><item><title>Ayende Rahien commented on Elegancy challenge: Cacheable Batches</title><description>Mark,
You don't have a cache, you have a memory leak, since nothing ever expires from this cache</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment4</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment4</guid><pubDate>Wed, 24 Aug 2011 10:12:52 GMT</pubDate></item><item><title>Ayende Rahien commented on Elegancy challenge: Cacheable Batches</title><description>Jesper,
This code suffers from a multi threading bug.
We first check if an item exists, and only later we extract it, by that time, it might have been removed from the cache.
Same for setting the item in the cache and then trying to get it from there.</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment3</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment3</guid><pubDate>Wed, 24 Aug 2011 10:10:51 GMT</pubDate></item><item><title>Mark commented on Elegancy challenge: Cacheable Batches</title><description>    class ElegantCache&lt;TKey, TValue&gt;
    {
        private readonly Dictionary&lt;TKey, TValue&gt; _cache = new Dictionary&lt;TKey, TValue&gt;(); 
        private readonly Func&lt;TValue, TKey&gt; _keyResolver;
        private readonly Func&lt;IEnumerable&lt;TKey&gt;, IEnumerable&lt;TValue&gt;&gt; _uncachedResolver;

        public ElegantCache(Func&lt;TValue, TKey&gt; keyResolver, Func&lt;IEnumerable&lt;TKey&gt;, IEnumerable&lt;TValue&gt;&gt; uncachedResolver)
        {
            _keyResolver = keyResolver;
            _uncachedResolver = uncachedResolver;
        }

        public IEnumerable&lt;TValue&gt; Get(TKey[] keys)
        {
            var uncachedKeys = keys.Except(_cache.Keys).ToList();
            if (uncachedKeys.Count &gt; 0)
            {
                foreach (var value in _uncachedResolver(uncachedKeys))
                {
                    _cache.Add(_keyResolver(value), value);
                }
            }
            return keys.Join(_cache, k =&gt; k, kvp =&gt; kvp.Key, (k, kvp) =&gt; kvp.Value);
        }
    }
</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment2</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment2</guid><pubDate>Wed, 24 Aug 2011 10:01:39 GMT</pubDate></item><item><title>Jesper Larsen-Ledet commented on Elegancy challenge: Cacheable Batches</title><description>public JsonDocument[] GetDocument(params string[] ids)
{
    // only ask server for uncached documents
    var uncachedIds = (from id in ids where !cache.Contains(id) select id).ToArray();

    // make the request to the server, for example
    var request = WebRequest.Create("http://server/get?id=" + string.Join("&amp;id=", uncachedIds));
    using (var stream = request.GetResponse().GetResponseStream())
    {
        var results = GetResults(stream);
        // add fetched documents to cache
        foreach (var x in uncachedIds.Zip(results, (id, doc) =&gt; new { id, doc }))
        {
            cache[x.id] = x.doc;
        }
    }
                
    // return all documents form cache
    return (from id in ids select (JsonDocument)cache[id]).ToArray();
}</description><link>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment1</link><guid>http://ayende.com/60417/elegancy-challenge-cacheable-batches#comment1</guid><pubDate>Wed, 24 Aug 2011 09:54:49 GMT</pubDate></item></channel></rss>