feat: support nspath measurement shorthand recommendations

- add nspath-to-measurement recommend set keys and cleanup pattern
- collect nspath measurement tags when component nspath matches bay tag
- store token4.token7 autocomplete terms during measurement recommend init
- add token4.token7 recommendations alongside existing two-token paths
This commit is contained in:
douxu 2026-07-02 13:49:47 +08:00
parent 491c10e8c5
commit 33eb2d9be8
6 changed files with 180 additions and 8 deletions

View File

@ -44,6 +44,9 @@ const (
// RedisSpecCompTagMeasSetKey define redis set key which store all measurement tag keys under specific component tag // RedisSpecCompTagMeasSetKey define redis set key which store all measurement tag keys under specific component tag
RedisSpecCompTagMeasSetKey = "%s_measurement_tag_keys" RedisSpecCompTagMeasSetKey = "%s_measurement_tag_keys"
// RedisSpecCompNSPathMeasSetKey define redis set key which store all measurement tag keys under specific component nspath
RedisSpecCompNSPathMeasSetKey = "%s_nspath_measurement_tag_keys"
) )
const ( const (

View File

@ -28,6 +28,7 @@ func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSe
StationToCompNSPaths: make(map[string][]string), StationToCompNSPaths: make(map[string][]string),
CompNSPathToCompTags: make(map[string][]string), CompNSPathToCompTags: make(map[string][]string),
CompTagToMeasTags: make(map[string][]string), CompTagToMeasTags: make(map[string][]string),
CompNSPathToMeasTags: make(map[string][]string),
} }
g, gctx := errgroup.WithContext(ctx) g, gctx := errgroup.WithContext(ctx)
@ -113,11 +114,14 @@ func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSe
g.Go(func() error { g.Go(func() error {
var measurements []struct { var measurements []struct {
orm.Measurement orm.Measurement
CompTag string `gorm:"column:comp_tag"` CompTag string `gorm:"column:comp_tag"`
CompNSPath string `gorm:"column:comp_nspath"`
BayTag string `gorm:"column:bay_tag"`
} }
if err := db.Table("measurement"). if err := db.Table("measurement").
Select("measurement.*, component.tag as comp_tag"). Select("measurement.*, component.tag as comp_tag, component.nspath as comp_nspath, bay.tag as bay_tag").
Joins("left join component on measurement.component_uuid = component.global_uuid"). Joins("left join component on measurement.component_uuid = component.global_uuid").
Joins("left join bay on measurement.bay_uuid = bay.bay_uuid").
Scan(&measurements).Error; err != nil { Scan(&measurements).Error; err != nil {
return fmt.Errorf("query measurements: %w", err) return fmt.Errorf("query measurements: %w", err)
} }
@ -126,6 +130,9 @@ func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSe
if m.CompTag != "" { if m.CompTag != "" {
mSet.CompTagToMeasTags[m.CompTag] = append(mSet.CompTagToMeasTags[m.CompTag], m.Tag) mSet.CompTagToMeasTags[m.CompTag] = append(mSet.CompTagToMeasTags[m.CompTag], m.Tag)
} }
if m.CompNSPath != "" && m.CompNSPath == m.BayTag {
mSet.CompNSPathToMeasTags[m.CompNSPath] = append(mSet.CompNSPathToMeasTags[m.CompNSPath], m.Tag)
}
} }
return nil return nil
}) })

View File

@ -187,6 +187,19 @@ func TraverseMeasurementGroupTables(ctx context.Context, measSet orm.Measurement
ac.AddTerms(sug...) ac.AddTerms(sug...)
} }
// building the component nspath -> measurement tags hierarchy for token4-token7 shorthand
for compNSPath, measTags := range measSet.CompNSPathToMeasTags {
sug := make([]redisearch.Suggestion, 0, len(measTags))
for _, measTag := range measTags {
sug = append(sug, redisearch.Suggestion{
Term: fmt.Sprintf("%s.%s", compNSPath, measTag),
Score: constants.DefaultScore,
})
}
safeSAdd(fmt.Sprintf(constants.RedisSpecCompNSPathMeasSetKey, compNSPath), measTags)
ac.AddTerms(sug...)
}
var allErrs []error var allErrs []error
cmders, execErr := pipe.Exec(ctx) cmders, execErr := pipe.Exec(ctx)
if execErr != nil { if execErr != nil {

View File

@ -48,11 +48,12 @@ func CleanupRecommendRedisCache(ctx context.Context) error {
} }
patterns := []string{ patterns := []string{
"*_zone_tag_keys", // correspond RedisSpecGridZoneSetKey "*_zone_tag_keys", // correspond RedisSpecGridZoneSetKey
"*_station_tag_keys", // correspond RedisSpecZoneStationSetKey "*_station_tag_keys", // correspond RedisSpecZoneStationSetKey
"*_component_nspath_keys", // correspond RedisSpecStationCompNSPATHSetKey "*_component_nspath_keys", // correspond RedisSpecStationCompNSPATHSetKey
"*_component_tag_keys", // correspond RedisSpecCompNSPathCompTagSetKey "*_component_tag_keys", // correspond RedisSpecCompNSPathCompTagSetKey
"*_measurement_tag_keys", // correspond RedisSpecCompTagMeasSetKey "*_measurement_tag_keys", // correspond RedisSpecCompTagMeasSetKey
"*_nspath_measurement_tag_keys", // correspond RedisSpecCompNSPathMeasSetKey
} }
for _, pattern := range patterns { for _, pattern := range patterns {

View File

@ -183,8 +183,10 @@ func RedisSearchRecommend(ctx context.Context, input string) map[string]SearchRe
go handleLevelFuzzySearch(ctx, rdb, constants.ZoneRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllZoneSetKey, inputSlice, fanInChan) go handleLevelFuzzySearch(ctx, rdb, constants.ZoneRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllZoneSetKey, inputSlice, fanInChan)
// component tagname search // component tagname search
go handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllCompTagSetKey, inputSlice, fanInChan) go handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllCompTagSetKey, inputSlice, fanInChan)
// measurement tagname search by token4-token7 shorthand
go handleNSPathMeasSearch(ctx, rdb, inputSlice, fanInChan)
results := make(map[string]SearchResult) results := make(map[string]SearchResult)
for range 2 { for range 3 {
result := <-fanInChan result := <-fanInChan
if result.Err != nil { if result.Err != nil {
logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err) logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err)
@ -518,6 +520,151 @@ func getSpecificKeyByLength(hierarchy constants.RecommendHierarchyType, keyPrefi
} }
} }
func handleNSPathMeasSearch(ctx context.Context, rdb *redis.Client, inputSlice []string, fanInChan chan SearchResult) {
hierarchy := constants.MeasTagRecommendHierarchyType
nsPath := inputSlice[0]
searchInput := inputSlice[1]
nsPathExists, err := rdb.SIsMember(ctx, constants.RedisAllCompNSPathSetKey, nsPath).Result()
if err != nil {
logger.Error(ctx, "check nspath exist from redis set failed", "key", constants.RedisAllCompNSPathSetKey, "nspath", nsPath, "error", err)
fanInChan <- SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
if !nsPathExists {
fanInChan <- SearchResult{
RecommendType: hierarchy,
QueryDatas: []string{},
IsFuzzy: false,
Err: nil,
}
return
}
queryKey := fmt.Sprintf(constants.RedisSpecCompNSPathMeasSetKey, nsPath)
if searchInput == "" {
members, err := rdb.SMembers(ctx, queryKey).Result()
if err != nil && err != redis.Nil {
logger.Error(ctx, "query measurement tags by nspath failed", "key", queryKey, "nspath", nsPath, "error", err)
fanInChan <- SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
fanInChan <- SearchResult{
RecommendType: hierarchy,
QueryDatas: combineNSPathMeasResults(nsPath, members),
IsFuzzy: false,
Err: nil,
}
return
}
measTagExists, err := rdb.SIsMember(ctx, queryKey, searchInput).Result()
if err != nil {
logger.Error(ctx, "check measurement tag exist from nspath set failed", "key", queryKey, "meas_tag", searchInput, "error", err)
fanInChan <- SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
if measTagExists {
fanInChan <- SearchResult{
RecommendType: hierarchy,
QueryDatas: []string{""},
IsFuzzy: false,
Err: nil,
}
return
}
recommends, offset, err := runNSPathMeasFuzzySearch(ctx, nsPath, searchInput)
if err != nil {
fanInChan <- SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: true,
Err: err,
}
return
}
fanInChan <- SearchResult{
RecommendType: hierarchy,
QueryDatas: recommends,
IsFuzzy: true,
Offset: offset,
Err: nil,
}
}
func combineNSPathMeasResults(nsPath string, measTags []string) []string {
results := make([]string, 0, len(measTags))
for _, measTag := range measTags {
results = append(results, fmt.Sprintf("%s.%s", nsPath, measTag))
}
return results
}
func runNSPathMeasFuzzySearch(ctx context.Context, nsPath string, searchInput string) ([]string, int, error) {
searchInputLen := len([]rune(searchInput))
for searchInputLen != 0 {
fuzzyInput := fmt.Sprintf("%s.%s", nsPath, searchInput)
results, err := ac.SuggestOpts(fuzzyInput, redisearch.SuggestOptions{
Num: math.MaxInt16,
Fuzzy: true,
WithScores: false,
WithPayloads: false,
})
if err != nil {
logger.Error(ctx, "query measurement tag by nspath fuzzy search failed", "query_key", fuzzyInput, "error", err)
return nil, 0, fmt.Errorf("redisearch suggest failed: %w", err)
}
if len(results) == 0 {
searchInput = trimLastRune(searchInput)
searchInputLen = len([]rune(searchInput))
continue
}
recommends := make([]string, 0, len(results))
for _, result := range results {
termPrefix, termLastPart := splitLastToken(result.Term)
if termPrefix == nsPath && strings.HasPrefix(termLastPart, searchInput) {
recommends = append(recommends, result.Term)
}
}
if len(recommends) == 0 {
searchInput = trimLastRune(searchInput)
searchInputLen = len([]rune(searchInput))
continue
}
return recommends, len([]rune(fmt.Sprintf("%s.%s", nsPath, searchInput))), nil
}
return []string{}, 0, nil
}
func splitLastToken(term string) (string, string) {
lastDotIndex := strings.LastIndex(term, ".")
if lastDotIndex == -1 {
return "", term
}
return term[:lastDotIndex], term[lastDotIndex+1:]
}
// handleLevelFuzzySearch define func to process recommendation logic for specific levels(level >= 2) // handleLevelFuzzySearch define func to process recommendation logic for specific levels(level >= 2)
func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, recommendLenType string, redisSetKey string, inputSlice []string, fanInChan chan SearchResult) { func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, recommendLenType string, redisSetKey string, inputSlice []string, fanInChan chan SearchResult) {
inputSliceLen := len(inputSlice) inputSliceLen := len(inputSlice)

View File

@ -16,4 +16,5 @@ type MeasurementSet struct {
StationToCompNSPaths map[string][]string // Key: StationTag, Value: NSPaths StationToCompNSPaths map[string][]string // Key: StationTag, Value: NSPaths
CompNSPathToCompTags map[string][]string // Key: NSPaths, Value: CompTags CompNSPathToCompTags map[string][]string // Key: NSPaths, Value: CompTags
CompTagToMeasTags map[string][]string // Key: CompTag, Value: MeasTags CompTagToMeasTags map[string][]string // Key: CompTag, Value: MeasTags
CompNSPathToMeasTags map[string][]string // Key: NSPaths, Value: MeasTags
} }