Commit a22ae2eb authored by Marty Schoch's avatar Marty Schoch Committed by Andrei Mihu
Browse files

Match listing and matchmaker support for query boost. (#711)

parent 4ab8a763
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -3,8 +3,9 @@ module github.com/heroiclabs/nakama/v3
go 1.17

require (
	github.com/blugelabs/bluge v0.1.7
	github.com/blugelabs/query_string v0.2.0
	github.com/blugelabs/bluge v0.1.8
	github.com/blugelabs/bluge_segment_api v0.2.0
	github.com/blugelabs/query_string v0.3.0
	github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06
	github.com/gofrs/uuid v4.0.0+incompatible
	github.com/golang-jwt/jwt/v4 v4.1.0
@@ -41,7 +42,6 @@ require (
	github.com/blevesearch/segment v0.9.0 // indirect
	github.com/blevesearch/snowballstem v0.9.0 // indirect
	github.com/blevesearch/vellum v1.0.7 // indirect
	github.com/blugelabs/bluge_segment_api v0.2.0 // indirect
	github.com/blugelabs/ice v0.2.0 // indirect
	github.com/caio/go-tdigest v3.1.0+incompatible // indirect
	github.com/cespare/xxhash/v2 v2.1.1 // indirect
+4 −0
Original line number Diff line number Diff line
@@ -80,12 +80,16 @@ github.com/blevesearch/vellum v1.0.7 h1:+vn8rfyCRHxKVRgDLeR0FAXej2+6mEb5Q15aQE/X
github.com/blevesearch/vellum v1.0.7/go.mod h1:doBZpmRhwTsASB4QdUZANlJvqVAUdUyX0ZK7QJCTeBE=
github.com/blugelabs/bluge v0.1.7 h1:CIP3OlzWZ46GbqyIdxXIeLgcSRkCopFIcn81I4T+QG8=
github.com/blugelabs/bluge v0.1.7/go.mod h1:5d7LktUkQgvbh5Bmi6tPWtvo4+6uRTm6gAwP+5z6FqQ=
github.com/blugelabs/bluge v0.1.8 h1:80O2EhbAlNykvPGA0SgHc7/9oKi1Xh/g0uk/4/9tksE=
github.com/blugelabs/bluge v0.1.8/go.mod h1:5d7LktUkQgvbh5Bmi6tPWtvo4+6uRTm6gAwP+5z6FqQ=
github.com/blugelabs/bluge_segment_api v0.2.0 h1:cCX1Y2y8v0LZ7+EEJ6gH7dW6TtVTW4RhG0vp3R+N2Lo=
github.com/blugelabs/bluge_segment_api v0.2.0/go.mod h1:95XA+ZXfRj/IXADm7gZ+iTcWOJPg5jQTY1EReIzl3LA=
github.com/blugelabs/ice v0.2.0 h1:9N/TRBqAr43emheD1ptk9mohuT6xAVq83gesgE60Qqk=
github.com/blugelabs/ice v0.2.0/go.mod h1:7foiDf4V83FIYYnGh2LOoRWsbNoCqAAMNgKn879Iyu0=
github.com/blugelabs/query_string v0.2.0 h1:ITgD9zF7HQiXstJgRZ+W4kWYUUKJNjhwwRXUtwX6WZs=
github.com/blugelabs/query_string v0.2.0/go.mod h1:H0YFWhYAf8/xcv1zoswoUC8kM/fE9L/KEfsgySsnhfs=
github.com/blugelabs/query_string v0.3.0 h1:/2XMd/3A0lIo33STXMUon4khLcTTybrAcyC0mBLmeA8=
github.com/blugelabs/query_string v0.3.0/go.mod h1:H0YFWhYAf8/xcv1zoswoUC8kM/fE9L/KEfsgySsnhfs=
github.com/cactus/go-statsd-client v3.1.1+incompatible/go.mod h1:cMRcwZDklk7hXp+Law83urTHUiHMzCev/r4JMYr/zU0=
github.com/caio/go-tdigest v3.1.0+incompatible h1:uoVMJ3Q5lXmVLCCqaMGHLBWnbGoN6Lpu7OAUPR60cds=
github.com/caio/go-tdigest v3.1.0+incompatible/go.mod h1:sHQM/ubZStBUmF1WbB8FAm8q9GjDajLC5T7ydxE3JHI=
+19 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import (
	"github.com/blugelabs/bluge"
	"github.com/blugelabs/bluge/analysis/analyzer"
	"github.com/blugelabs/bluge/search"
	"github.com/blugelabs/bluge/search/similarity"
	segment "github.com/blugelabs/bluge_segment_api"
	queryStr "github.com/blugelabs/query_string"
	"go.uber.org/zap"
)
@@ -243,3 +245,20 @@ func ParseQueryString(query string) (bluge.Query, error) {
	opt := queryStr.DefaultOptions().WithDefaultAnalyzer(BlugeKeywordAnalyzer)
	return queryStr.ParseQueryString(query, opt)
}

type constantSimilarity struct{}

func (c constantSimilarity) ComputeNorm(_ int) float32 {
	return 0
}

func (c constantSimilarity) Scorer(boost float64, _ segment.CollectionStats, _ segment.TermStats) search.Scorer {
	return similarity.ConstantScorer(boost)
}

func BlugeInMemoryConfig() bluge.Config {
	cfg := bluge.InMemoryOnlyConfig()
	cfg.DefaultSimilarity = constantSimilarity{}
	cfg.DefaultSearchAnalyzer = BlugeKeywordAnalyzer
	return cfg
}
+2 −2
Original line number Diff line number Diff line
@@ -146,7 +146,7 @@ type LocalMatchRegistry struct {

func NewLocalMatchRegistry(logger, startupLogger *zap.Logger, config Config, sessionRegistry SessionRegistry, tracker Tracker, router MessageRouter, metrics Metrics, node string) MatchRegistry {

	cfg := bluge.InMemoryOnlyConfig()
	cfg := BlugeInMemoryConfig()
	indexWriter, err := bluge.OpenWriter(cfg)
	if err != nil {
		startupLogger.Fatal("Failed to create match registry index", zap.Error(err))
@@ -410,7 +410,7 @@ func (r *LocalMatchRegistry) ListMatches(ctx context.Context, limit int, authori
		}

		searchReq := bluge.NewTopNSearch(count, q)
		searchReq.SortBy([]string{"-create_time"})
		searchReq.SortBy([]string{"-_score", "-create_time"})

		labelResultsItr, err := indexReader.Search(ctx, searchReq)
		if err != nil {
+110 −0
Original line number Diff line number Diff line
@@ -213,6 +213,116 @@ func TestMatchRegistryAuthoritativeMatchAndListMatchesWithQueryingArrays(t *test
	}
}

// should create authoritative match, list matches with querying
func TestMatchRegistryAuthoritativeMatchAndListMatchesWithQueryingAndBoost(t *testing.T) {
	consoleLogger := loggerForTest(t)
	matchRegistry, runtimeMatchCreateFunc, err := createTestMatchRegistry(t, consoleLogger)
	if err != nil {
		t.Fatalf("error creating test match registry: %v", err)
	}
	defer matchRegistry.Stop(0)

	matchLabels := []string{
		`{"foo": 5, "bar": 1, "option": "a", "baz": 4}`,
		`{"foo": 5, "bar": 1, "option": "b", "baz": 4}`,
		`{"foo": 5, "bar": 1, "option": "a", "baz": 3}`,
		`{"foo": 5, "bar": 1, "option": "b", "baz": 3}`,
		`{"foo": 5, "bar": 1, "option": "a", "baz": 2}`,
		`{"foo": 5, "bar": 1, "option": "b", "baz": 2}`,
		`{"foo": 5, "bar": 1, "option": "a", "baz": 1}`,
		`{"foo": 5, "bar": 1, "option": "b", "baz": 1}`,
		`{"foo": 5, "bar": 1, "option": "a", "baz": 0}`,
		`{"foo": 5, "bar": 1, "option": "b", "baz": 0}`,
	}

	// create all matches
	for _, matchLabel := range matchLabels {
		_, err = matchRegistry.CreateMatch(context.Background(), consoleLogger,
			runtimeMatchCreateFunc, "match", map[string]interface{}{
				"label": matchLabel,
			})
		if err != nil {
			t.Fatal(err)
		}
	}

	time.Sleep(5 * time.Second)

	tests := []struct {
		name         string
		query        string
		total        int
		labelMatches map[int]string
	}{
		{
			// query should find all matches, with baz 4 in first 2 positions
			// and baz 2 in next 2 positions
			// we can only match on the baz value, not the entire string, because order
			// is not imposed over the option a/b
			name:  "exact numeric boost",
			query: "+label.foo:5 +label.bar:1 label.baz:4^10 label.baz:2^5",
			total: 10,
			labelMatches: map[int]string{
				0: `"baz": 4`,
				1: `"baz": 4`,
				2: `"baz": 2`,
				3: `"baz": 2`,
			},
		},
		{
			// this variant introduces a required text match (bm25 scoring)
			// query should find only option a, with baz 4 in first position
			// and baz 2 in next position
			name:  "exact numeric boost with required text match",
			query: "+label.foo:5 +label.bar:1 +label.option:a label.baz:4^10 label.baz:2^5",
			total: 5,
			labelMatches: map[int]string{
				0: matchLabels[0],
				1: matchLabels[4],
			},
		},
		{
			// this variant makes the text match (bm25 scoring) optional
			// query should find all matches, with baz 4 in first 2 positions
			// and baz 2 in next 2 positions
			name:  "exact numeric boost with optional text match",
			query: "+label.foo:5 +label.bar:1 label.option:a label.baz:4^10 label.baz:2^5",
			total: 10,
			labelMatches: map[int]string{
				0: matchLabels[0],
				1: matchLabels[1],
				2: matchLabels[4],
				3: matchLabels[5],
			},
		},
	}

	for _, test := range tests {
		test := test

		t.Run(test.name, func(t *testing.T) {
			matches, err := matchRegistry.ListMatches(context.Background(), 10, wrapperspb.Bool(true),
				wrapperspb.String("label"), wrapperspb.Int32(0), wrapperspb.Int32(5),
				wrapperspb.String(test.query))
			if err != nil {
				t.Fatalf("error listing matches: %v", err)
			}
			if len(matches) != test.total {
				t.Fatalf("expected %d match, got %d", test.total, len(matches))
			}

			for labelMatchI, labelMatch := range test.labelMatches {
				if !strings.Contains(matches[labelMatchI].Label.Value, labelMatch) {
					for i, match := range matches {
						t.Errorf("%d match: %s label: %s", i, match.MatchId, match.Label)
					}
					t.Fatalf("results in wrong order")
				}
			}
		})
	}
}

func matchUUIDFromString(matchIDString string) (uuid.UUID, error) {
	matchIDComponents := strings.SplitN(matchIDString, ".", 2)
	if len(matchIDComponents) != 2 {
Loading