Skip to content

Commit 327a6f2

Browse files
authored
node/object: Optimize filtered attribute-less SearchV2 queries (#3331)
2 parents af121ab + 5249ab0 commit 327a6f2

File tree

2 files changed

+29
-6
lines changed

2 files changed

+29
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Changelog for NeoFS Node
5353
- Search API is served from SearchV2 indexes now (#3316)
5454
- Blobstor can be of exactly one type, with no substorages (#3330)
5555
- SN uses SearchV2 to verify tombstones (#3312)
56+
- SN handles filtered attribute-less SearchV2 queries faster on big containers (#3329)
5657

5758
### Removed
5859
- SN `apiclient.allow_external` config (#3235)

pkg/services/object/server.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1932,7 +1932,17 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req
19321932
if err := fs.FromProtoMessage(body.Filters); err != nil {
19331933
return nil, nil, fmt.Errorf("invalid filters: %w", err)
19341934
}
1935-
ofs, cursor, err := objectcore.PreprocessSearchQuery(fs, body.Attributes, body.Cursor)
1935+
1936+
filteredAttributeless := len(body.Attributes) == 0 && len(body.Filters) > 0
1937+
attrs := body.Attributes
1938+
if filteredAttributeless {
1939+
// 1st attribute is required for merge function to provide proper paging. This
1940+
// requires collecting attribute values from other nodes. Therefore, if the
1941+
// attribute is not requested, it is forcefully returned for local queries, and
1942+
// will end up in resulting cursor.
1943+
attrs = []string{body.Filters[0].Key}
1944+
}
1945+
ofs, cursor, err := objectcore.PreprocessSearchQuery(fs, attrs, body.Cursor)
19361946
if err != nil {
19371947
if errors.Is(err, objectcore.ErrUnreachableQuery) {
19381948
return nil, nil, nil
@@ -1961,11 +1971,11 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req
19611971
count := uint16(body.Count) // legit according to the limit
19621972
switch {
19631973
case ttl == 1:
1964-
if res, newCursor, err = s.storage.SearchObjects(cID, ofs, body.Attributes, cursor, count); err != nil {
1974+
if res, newCursor, err = s.storage.SearchObjects(cID, ofs, attrs, cursor, count); err != nil {
19651975
return nil, nil, err
19661976
}
19671977
case handleWithMetaService:
1968-
res, newCursor, err = s.meta.Search(cID, ofs, body.Attributes, cursor, count)
1978+
res, newCursor, err = s.meta.Search(cID, ofs, attrs, cursor, count)
19691979
if err != nil {
19701980
return nil, nil, err
19711981
}
@@ -1993,7 +2003,7 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req
19932003
wg.Add(1)
19942004
go func() {
19952005
defer wg.Done()
1996-
if set, crsr, err := s.storage.SearchObjects(cID, ofs, body.Attributes, cursor, count); err == nil {
2006+
if set, crsr, err := s.storage.SearchObjects(cID, ofs, attrs, cursor, count); err == nil {
19972007
add(set, crsr != nil)
19982008
} // TODO: else log error
19992009
}()
@@ -2029,7 +2039,7 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req
20292039
firstAttr string
20302040
firstFilter *object.SearchFilter
20312041
)
2032-
if len(body.Attributes) > 0 {
2042+
if len(attrs) > 0 {
20332043
firstAttr = fs[0].Header()
20342044
firstFilter = &ofs[0].SearchFilter
20352045
}
@@ -2039,12 +2049,23 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req
20392049
return nil, nil, fmt.Errorf("merge results from container nodes: %w", err)
20402050
}
20412051
if more {
2052+
if filteredAttributeless && body.Filters[0].MatchType == protoobject.MatchType_STRING_EQUAL {
2053+
// temporary add the only possible value just to calculate the cursor. Condition below reverts it.
2054+
res[len(res)-1].Attributes = []string{body.Filters[0].Value}
2055+
}
20422056
if newCursor, err = objectcore.CalculateCursor(firstFilter, res[len(res)-1]); err != nil {
20432057
return nil, nil, fmt.Errorf("recalculate cursor: %w", err)
20442058
}
20452059
}
20462060
}
20472061

2062+
if filteredAttributeless && (ttl != 1 || body.Filters[0].MatchType == protoobject.MatchType_STRING_EQUAL) {
2063+
// for K=V queries, V is unambiguous, so there is no need to transmit it in each item
2064+
for i := range res {
2065+
res[i].Attributes = nil
2066+
}
2067+
}
2068+
20482069
return res, newCursor, nil
20492070
}
20502071

@@ -2116,13 +2137,14 @@ func searchOnRemoteAddress(ctx context.Context, conn *grpc.ClientConn, nodePub [
21162137
// TODO: we can theoretically do without type conversion, thus avoiding
21172138
// additional allocation. At the same time, this will require generic code for merging.
21182139
res := make([]sdkclient.SearchResultItem, n)
2140+
filteredAttributeless := len(req.Body.Attributes) == 0 && len(req.Body.Filters) > 0
21192141
for i, r := range resp.Body.Result {
21202142
switch {
21212143
case r == nil:
21222144
return nil, false, fmt.Errorf("invalid response body: nil element #%d", i)
21232145
case r.Id == nil:
21242146
return nil, false, fmt.Errorf("invalid response body: invalid element #%d: missing ID", i)
2125-
case len(r.Attributes) != len(req.Body.Attributes):
2147+
case !filteredAttributeless && len(r.Attributes) != len(req.Body.Attributes) || filteredAttributeless && len(r.Attributes) > 1:
21262148
return nil, false, fmt.Errorf("invalid response body: invalid element #%d: wrong attribute count %d", i, len(r.Attributes))
21272149
}
21282150
if err := res[i].ID.FromProtoMessage(r.Id); err != nil {

0 commit comments

Comments
 (0)