Skip to content

Commit a945973

Browse files
authored
fix(x): optimize gprc queries for collection use (#2046)
Signed-off-by: Artur Troian <troian@users.noreply.github.com> Co-authored-by: Artur Troian <troian@users.noreply.github.com>
1 parent 1d36cc1 commit a945973

2 files changed

Lines changed: 886 additions & 252 deletions

File tree

x/deployment/keeper/grpc_query.go

Lines changed: 199 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package keeper
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67

78
"google.golang.org/grpc/codes"
@@ -12,7 +13,7 @@ import (
1213
sdk "github.com/cosmos/cosmos-sdk/types"
1314
sdkquery "github.com/cosmos/cosmos-sdk/types/query"
1415

15-
"pkg.akt.dev/go/node/deployment/v1"
16+
v1 "pkg.akt.dev/go/node/deployment/v1"
1617
types "pkg.akt.dev/go/node/deployment/v1beta4"
1718

1819
"pkg.akt.dev/node/util/query"
@@ -44,14 +45,17 @@ func (k Querier) Deployments(c context.Context, req *types.QueryDeploymentsReque
4445

4546
ctx := sdk.UnwrapSDKContext(c)
4647

48+
// Step 1: Resolve states, resumePK, and iteration path
4749
states := make([]byte, 0, 2)
4850
var resumePK *keys.DeploymentPrimaryKey
51+
var ownerPath bool
52+
var owner string
4953

50-
// nolint: gocritic
5154
if len(req.Pagination.Key) > 0 {
52-
var pkBytes []byte
55+
// RESUME — all filters ignored, key provides everything
56+
var pkBytes, unsolicited []byte
5357
var err error
54-
states, _, pkBytes, _, err = query.DecodePaginationKey(req.Pagination.Key)
58+
states, _, pkBytes, unsolicited, err = query.DecodePaginationKey(req.Pagination.Key)
5559
if err != nil {
5660
return nil, status.Error(codes.InvalidArgument, err.Error())
5761
}
@@ -61,24 +65,208 @@ func (k Querier) Deployments(c context.Context, req *types.QueryDeploymentsReque
6165
return nil, status.Error(codes.Internal, err.Error())
6266
}
6367
resumePK = &pk
64-
} else if req.Filters.State != "" {
65-
stateVal := v1.Deployment_State(v1.Deployment_State_value[req.Filters.State])
6668

67-
if stateVal == v1.DeploymentStateInvalid {
68-
return nil, status.Error(codes.InvalidArgument, "invalid state value")
69+
if len(unsolicited) > 0 && unsolicited[0] == 1 {
70+
ownerPath = true
71+
owner = resumePK.K1()
6972
}
70-
71-
states = append(states, byte(stateVal))
7273
} else {
73-
states = append(states, byte(v1.DeploymentActive), byte(v1.DeploymentClosed))
74+
// INITIAL — resolve states from Filters.State
75+
if req.Filters.State != "" {
76+
stateVal := v1.Deployment_State(v1.Deployment_State_value[req.Filters.State])
77+
if stateVal == v1.DeploymentStateInvalid {
78+
return nil, status.Error(codes.InvalidArgument, "invalid state value")
79+
}
80+
states = append(states, byte(stateVal))
81+
} else {
82+
states = append(states, byte(v1.DeploymentActive), byte(v1.DeploymentClosed))
83+
}
84+
85+
// Resolve iteration path from Filters.Owner
86+
if req.Filters.Owner != "" {
87+
ownerPath = true
88+
owner = req.Filters.Owner
89+
}
90+
}
91+
92+
// Step 2: Direct Get path — Owner + DSeq = full PK known
93+
if ownerPath && resumePK == nil && req.Filters.DSeq != 0 {
94+
return k.deploymentsDirectGet(ctx, req, states)
95+
}
96+
97+
// Step 3: Owner path — iterate primary map with owner prefix
98+
if ownerPath {
99+
return k.deploymentsOwnerPath(ctx, req, states, owner, resumePK)
74100
}
75101

102+
// Step 4: State-index path — iterate by state index
76103
if len(req.Pagination.Key) == 0 && req.Pagination.Reverse {
77104
for i, j := 0, len(states)-1; i < j; i, j = i+1, j-1 {
78105
states[i], states[j] = states[j], states[i]
79106
}
80107
}
81108

109+
return k.deploymentsStatePath(ctx, req, states, resumePK)
110+
}
111+
112+
// deploymentsDirectGet handles the case where Owner + DSeq are both set, giving a full primary key.
113+
func (k Querier) deploymentsDirectGet(
114+
ctx sdk.Context,
115+
req *types.QueryDeploymentsRequest,
116+
states []byte,
117+
) (*types.QueryDeploymentsResponse, error) {
118+
pk := collections.Join(req.Filters.Owner, req.Filters.DSeq)
119+
deployment, err := k.deployments.Get(ctx, pk)
120+
if err != nil {
121+
if errors.Is(err, collections.ErrNotFound) {
122+
return &types.QueryDeploymentsResponse{
123+
Pagination: &sdkquery.PageResponse{},
124+
}, nil
125+
}
126+
return nil, status.Error(codes.Internal, err.Error())
127+
}
128+
129+
// Check state filter
130+
stateMatch := false
131+
for _, s := range states {
132+
if v1.Deployment_State(s) == deployment.State {
133+
stateMatch = true
134+
break
135+
}
136+
}
137+
if !stateMatch {
138+
return &types.QueryDeploymentsResponse{
139+
Pagination: &sdkquery.PageResponse{},
140+
}, nil
141+
}
142+
143+
account, err := k.ekeeper.GetAccount(ctx, deployment.ID.ToEscrowAccountID())
144+
if err != nil {
145+
return nil, status.Error(codes.Internal, fmt.Sprintf("fetching escrow account for DeploymentID=%s: %v", deployment.ID, err))
146+
}
147+
148+
groups, err := k.GetGroups(ctx, deployment.ID)
149+
if err != nil {
150+
return nil, status.Error(codes.Internal, fmt.Sprintf("fetching groups for DeploymentID=%s: %v", deployment.ID, err))
151+
}
152+
153+
return &types.QueryDeploymentsResponse{
154+
Deployments: types.DeploymentResponses{
155+
{
156+
Deployment: deployment,
157+
Groups: groups,
158+
EscrowAccount: account,
159+
},
160+
},
161+
Pagination: &sdkquery.PageResponse{
162+
Total: 1,
163+
},
164+
}, nil
165+
}
166+
167+
// deploymentsOwnerPath iterates the primary map with an owner prefix.
168+
// States are filtered in the Walk callback.
169+
func (k Querier) deploymentsOwnerPath(
170+
ctx sdk.Context,
171+
req *types.QueryDeploymentsRequest,
172+
states []byte,
173+
owner string,
174+
resumePK *keys.DeploymentPrimaryKey,
175+
) (*types.QueryDeploymentsResponse, error) {
176+
// Build state set for callback filtering
177+
stateSet := make(map[v1.Deployment_State]bool, len(states))
178+
for _, s := range states {
179+
stateSet[v1.Deployment_State(s)] = true
180+
}
181+
182+
// Build range on primary map
183+
ownerRange := collections.NewPrefixedPairRange[string, uint64](owner)
184+
185+
var r *collections.PairRange[string, uint64]
186+
if resumePK != nil {
187+
if req.Pagination.Reverse {
188+
r = collections.NewPrefixedPairRange[string, uint64](owner).EndInclusive(resumePK.K2()).Descending()
189+
} else {
190+
r = collections.NewPrefixedPairRange[string, uint64](owner).StartInclusive(resumePK.K2())
191+
}
192+
} else if req.Pagination.Reverse {
193+
r = ownerRange.Descending()
194+
} else {
195+
r = ownerRange
196+
}
197+
198+
var deployments types.DeploymentResponses
199+
var nextKey []byte
200+
total := uint64(0)
201+
offset := req.Pagination.Offset
202+
203+
walkErr := k.deployments.Walk(ctx, r, func(_ keys.DeploymentPrimaryKey, deployment v1.Deployment) (bool, error) {
204+
// State filter
205+
if !stateSet[deployment.State] {
206+
return false, nil
207+
}
208+
209+
// Offset
210+
if offset > 0 {
211+
offset--
212+
return false, nil
213+
}
214+
215+
// Page full — encode NextKey
216+
if req.Pagination.Limit == 0 {
217+
npk := keys.DeploymentIDToKey(deployment.ID)
218+
pkBuf := make([]byte, k.deployments.KeyCodec().Size(npk))
219+
if _, err := k.deployments.KeyCodec().Encode(pkBuf, npk); err != nil {
220+
return true, err
221+
}
222+
var err error
223+
nextKey, err = query.EncodePaginationKey(states, []byte{states[0]}, pkBuf, []byte{1})
224+
if err != nil {
225+
return true, err
226+
}
227+
return true, nil
228+
}
229+
230+
// Collect result
231+
account, err := k.ekeeper.GetAccount(ctx, deployment.ID.ToEscrowAccountID())
232+
if err != nil {
233+
return true, fmt.Errorf("%w: fetching escrow account for DeploymentID=%s", err, deployment.ID)
234+
}
235+
236+
groups, err := k.GetGroups(ctx, deployment.ID)
237+
if err != nil {
238+
return true, fmt.Errorf("%w: fetching groups for DeploymentID=%s", err, deployment.ID)
239+
}
240+
241+
deployments = append(deployments, types.QueryDeploymentResponse{
242+
Deployment: deployment,
243+
Groups: groups,
244+
EscrowAccount: account,
245+
})
246+
req.Pagination.Limit--
247+
total++
248+
return false, nil
249+
})
250+
if walkErr != nil {
251+
return nil, status.Error(codes.Internal, walkErr.Error())
252+
}
253+
254+
return &types.QueryDeploymentsResponse{
255+
Deployments: deployments,
256+
Pagination: &sdkquery.PageResponse{
257+
Total: total,
258+
NextKey: nextKey,
259+
},
260+
}, nil
261+
}
262+
263+
// deploymentsStatePath iterates deployments via the State index.
264+
func (k Querier) deploymentsStatePath(
265+
ctx sdk.Context,
266+
req *types.QueryDeploymentsRequest,
267+
states []byte,
268+
resumePK *keys.DeploymentPrimaryKey,
269+
) (*types.QueryDeploymentsResponse, error) {
82270
var deployments types.DeploymentResponses
83271
var nextKey []byte
84272
total := uint64(0)

0 commit comments

Comments
 (0)