Skip to content

Instantly share code, notes, and snippets.

@algrebe
Created June 17, 2016 16:33
Show Gist options
  • Save algrebe/d461d7341613537b8c3bfb6791aa9111 to your computer and use it in GitHub Desktop.
Save algrebe/d461d7341613537b8c3bfb6791aa9111 to your computer and use it in GitHub Desktop.
a resource allocation server using bleve.
// resource allocation server
/*
put in resources with tags such as
{ "ID": "some-id", "tags": []string{ "computer", "windows" } }
and if you want a computer that does not use windows
query for a resource using a string such as "+computer -windows"
*/
package resalloc
import (
"github.com/blevesearch/bleve"
"github.com/blevesearch/bleve/analysis/analyzers/keyword_analyzer"
"sync"
)
type Resource struct {
ID string
Tags []string
}
// Server adds resources to its index and allows search based on its tags.
type Server struct {
// a mapping of resourceID to resource
resourceMap map[string]*Resource
// bleve
index bleve.Index
mapping *bleve.IndexMapping
batch *bleve.Batch
// makes it safe for concurrent use
sync.RWMutex
}
// Init initializes the server.
func (s *Server) Init() error {
s.resourceMap = make(map[string]*Resource)
s.mapping = bleve.NewIndexMapping()
s.mapping.DefaultAnalyzer = keyword_analyzer.Name
index, err := bleve.New("", s.mapping)
if err != nil {
return err
}
s.index = index
s.batch = s.index.NewBatch()
s.batch.Reset()
return nil
}
// AddResource adds a resource to the index.
func (s *Server) AddResource(r *Resource) error {
s.Lock()
// I dont want the entire struct, just the ID to tags is enough
err := s.index.Index(r.ID, r.Tags)
s.resourceMap[r.ID] = r
s.Unlock()
return err
}
// AddResources adds many resources to the index.
func (s *Server) AddResources(resources []*Resource) error {
s.Lock()
s.batch.Reset()
for _, r := range resources {
s.batch.Index(r.ID, r.Tags)
s.resourceMap[r.ID] = r
}
err := s.index.Batch(s.batch)
s.batch.Reset()
s.Unlock()
return err
}
// GetResource gets a resource based on the tag query string.
func (s *Server) GetResource(qs string) (*Resource, error) {
query := bleve.NewQueryStringQuery(qs)
searchRequest := bleve.NewSearchRequest(query)
// NOTE i only need one that matches the tag query.
// I don't even need the "top score" . any that satisfy the tag query will do.
searchRequest.Size = 1
s.RLock()
searchResults, err := s.index.Search(searchRequest)
defer s.RUnlock()
if err != nil {
return nil, err
}
if searchResults.Total == 0 {
return nil, nil
}
resourceID := searchResults.Hits[0].ID
return s.resourceMap[resourceID], nil
}
package resalloc
import (
"fmt"
"testing"
)
// GenerateResources generates multiple resources having same tags but different ids.
func GenerateResources(num int, tags []string) []*Resource {
resources := make([]*Resource, num, num)
for i := 0; i < num; i++ {
resources[i] = &Resource{
ID: fmt.Sprintf("resource-%d", i),
Tags: tags,
}
}
return resources
}
// BenchmarkSearchUnknownTagIn10kResources searches for an unkown tag across 10k resources.
func BenchmarkSearchUnknownTagIn10kResources(b *testing.B) {
s := &Server{}
if err := s.Init(); err != nil {
b.Fatalf("failed to initialize server, err=%s", err)
}
// Generating 10k resources all having the tag "computer"
resources := GenerateResources(10000, []string{"computer"})
if err := s.AddResources(resources); err != nil {
b.Fatalf("failed to add resources, err=%s", err)
}
// I want any laptop that isn't occupied. But there are no laptops, only computers.
qs := "+laptop -occupied"
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.GetResource(qs)
}
}
// BenchmarkSearchOnlyTagIn10kResources searches for a tag that is present in
// all elements of the index.
func BenchmarkSearchOnlyTagIn10kResources(b *testing.B) {
s := &Server{}
if err := s.Init(); err != nil {
b.Fatalf("failed to initialize server, err=%s", err)
}
// Generating 10k resources all having the tag "computer"
resources := GenerateResources(10000, []string{"computer"})
if err := s.AddResources(resources); err != nil {
b.Fatalf("failed to add resources, err=%s", err)
}
// I want one of those computer resources that isn't occupied
// searching for a computer from all computers
qs := "+computer -occupied"
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.GetResource(qs)
}
}
// BenchmarkSearchRareTagIn10kResources searches for a tag that is present
// in minority of the resources in the index.
func BenchmarkSearchRareTagIn10kResources(b *testing.B) {
s := &Server{}
if err := s.Init(); err != nil {
b.Fatalf("failed to initaialize server, err=%s", err)
}
// Generating 9990 resources all having the tag "computer"
computers := GenerateResources(9990, []string{"computer"})
// Generating 10 resources all having the tag "laptop"
laptops := GenerateResources(10, []string{"laptop"})
if err := s.AddResources(computers); err != nil {
b.Fatalf("failed to add computers, err=%s", err)
}
if err := s.AddResources(laptops); err != nil {
b.Fatalf("failed to add laptops, err=%s", err)
}
// searching for a laptop out of 500 laptops
qs := "+laptop -occupied"
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.GetResource(qs)
}
}
// BenchmarkSearchCommonTagIn10kResources searches for a tag that is present
// in majority of the resources in the index.
func BenchmarkSearchCommonTagIn10kResources(b *testing.B) {
s := &Server{}
if err := s.Init(); err != nil {
b.Fatalf("failed to initaialize server, err=%s", err)
}
// Generating 9.5k resources all having the tag "computer"
computers := GenerateResources(9500, []string{"computer"})
// Generating .5k resources all having the tag "laptop"
laptops := GenerateResources(500, []string{"laptop"})
if err := s.AddResources(computers); err != nil {
b.Fatalf("failed to add computers, err=%s", err)
}
if err := s.AddResources(laptops); err != nil {
b.Fatalf("failed to add laptops, err=%s", err)
}
// searching for a computer out of 9500 computers
qs := "+computer -occupied"
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.GetResource(qs)
}
}
@algrebe
Copy link
Author

algrebe commented Jun 17, 2016

sample output

$ go test -bench=.
testing: warning: no tests to run
PASS
BenchmarkSearchUnknownTagIn10kResources-4      10000        120270 ns/op
BenchmarkSearchOnlyTagIn10kResources-4            10     116552844 ns/op
BenchmarkSearchRareTagIn10kResources-4         10000        247894 ns/op
BenchmarkSearchCommonTagIn10kResources-4          10     107287498 ns/op
ok      _/tmp/resalloc  12.116s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment