问题
I need to create Web API search functionality through all users in my system. Client (using phone) sends me requests using endpoint:
HTTP 1.1 GET http://sf.cluster:80/
Path /search/users?q=Aa&take=10
Where q is a string user entered in a search field. take - how much entries phone wants to show.
I uploaded in my Reliable Dictionary 89000 items from Azure Storage Table. It has structure:
IReliableDictionary<Guid, string>
My search method looks like:
public async Task<IEnumerable<UserInfo>> Search(string q, int take)
{
var usersDictionary = await GetUsersDictionary();
IEnumerable<UserInfo> results;
using (var tx = StateManager.CreateTransaction())
{
var searchResults = (from r in (await usersDictionary.CreateEnumerableAsync(tx)).ToEnumerable()
where r.Value.StartsWith(q, StringComparison.InvariantCultureIgnoreCase)
select new UserInfo()
{
Id = r.Key,
Name = r.Value
}).Take(take);
results = new List<UserInfo>(searchResults);
await tx.CommitAsync();
}
return results;
}
The problem: It works nice on the phone, I got what I expected. But when I have started push my endpoint with a bunch of requests (approx. ~60 threads simultaneously using Soap UI tool), a timeout started goes up from 1s to 35s! It looks like I made a mistake somewhere or choose a wrong way of search implementation.
Had somebody implemented some functionality like this? Can anybody help with correct search approach?
UPD: Implemented stateless service where I store List<string>
with names and do the same things their (search through the list). Results: 150-300ms. It looks like I should store List in a state (in stateful service) and get it upon request..
回答1:
I'm not sure what the implementation of your ToEnumerable
method is, but most that I've seen are a fairly lazy implementation of just taking the async enumerable and copying it to a list. Now, with a reliable dictionary of 890,000 elems, that's quite inefficient. Also, transactions act like a mutex, so while you're copying this huge list you're locking the underlying collection. I would suggest checking out the AsyncEnumerable linq implementation in this library as it implements an efficient way to use linq with service fabrics AsyncEnumerable. Using that, your search would look something like this:
using (var tx = StateManager.CreateTransaction())
{
var enumerable = await usersDictionary.CreateEnumerableAsync(tx);
results = await enumerable.Where(kvp=>kvp.Value.StartsWith(q, StringComparison.InvariantCultureIgnoreCase))
.Select(kvp=> new UserInfo()
{
Id = r.Key,
Name = r.Value
})
.Take(take)
.ToListAsync(tx);
}
Also, as a side note, since you're not modifying the underlying collection in any way you don't need to commit the transaction. Committing the transaction is simply a way to tell the state manager that you have modified the state and you've finished making your changes, which it will then propagate the changed values to the secondaries. You could even call this method on the secondaries if this a read-heavy piece of state, but note that writes may not be propagated yet.
回答2:
ReliableDictinonary returns a IAsyncEnumerable because ReliableDictionary pages out some of the values. This means that disk IO may be required to read some of the values. IAsyncEnumerable allows us to block few threads as possible.
If read latency is a concern, you can use notifications to build a fully in-memory secondary index. You can also order the secondary index by value to increase the efficiency of the prefix match search. Following is the relevant documentation: https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-notifications
Minor correction to pdylanross's answer: CreateEnumerableAsync provides Snapshot Isolation using an mvcc model that does not lock the collection. Hence, other transactions can continue doing read and write operations while snapshot read transactions are in flight. For more information on isolation levels: https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-reliable-collections
Hope this helps,
来源:https://stackoverflow.com/questions/38187629/search-through-reliable-dictionary