Skip to content

Commit

Permalink
feat: Add txn abandon_age option for VTAdmin
Browse files Browse the repository at this point in the history
Signed-off-by: Noble Mittal <[email protected]>
  • Loading branch information
beingnoble03 committed Sep 24, 2024
1 parent ff6ac08 commit 62c6113
Show file tree
Hide file tree
Showing 10 changed files with 914 additions and 755 deletions.
1,440 changes: 725 additions & 715 deletions go/vt/proto/vtadmin/vtadmin.pb.go

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions go/vt/proto/vtadmin/vtadmin_vtproto.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion go/vt/vtadmin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1523,7 +1523,8 @@ func (api *API) GetUnresolvedTransactions(ctx context.Context, req *vtadminpb.Ge
}

return c.Vtctld.GetUnresolvedTransactions(ctx, &vtctldatapb.GetUnresolvedTransactionsRequest{
Keyspace: req.Keyspace,
Keyspace: req.Keyspace,
AbandonAge: req.AbandonAge,
})
}

Expand Down
21 changes: 20 additions & 1 deletion go/vt/vtadmin/http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (r Request) ParseQueryParamAsUint32(name string, defaultVal uint32) (uint32
}

// ParseQueryParamAsInt32 attempts to parse the query parameter of the given
// name into a uint32 value. If the parameter is not set, the provided default
// name into a int32 value. If the parameter is not set, the provided default
// value is returned.
func (r Request) ParseQueryParamAsInt32(name string, defaultVal int32) (int32, error) {
if param := r.URL.Query().Get(name); param != "" {
Expand All @@ -96,6 +96,25 @@ func (r Request) ParseQueryParamAsInt32(name string, defaultVal int32) (int32, e
return defaultVal, nil
}

// ParseQueryParamAsInt64 attempts to parse the query parameter of the given
// name into a int64 value. If the parameter is not set, the provided default
// value is returned.
func (r Request) ParseQueryParamAsInt64(name string, defaultVal int64) (int64, error) {
if param := r.URL.Query().Get(name); param != "" {
val, err := strconv.ParseInt(param, 10, 64)
if err != nil {
return defaultVal, &errors.BadRequest{
Err: err,
ErrDetails: fmt.Sprintf("could not parse query parameter %s (= %v) into int64 value", name, param),
}
}

return val, nil
}

return defaultVal, nil
}

// Vars is a mapping of the route variable values in a given request.
//
// See (gorilla/mux).Vars for details. We define a type here to add some
Expand Down
14 changes: 11 additions & 3 deletions go/vt/vtadmin/http/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,26 @@ import (
)

// GetUnresolvedTransactions implements the http wrapper for the
// /transactions/{cluster_id}/{keyspace} route.
// /transactions/{cluster_id}/{keyspace}[?abandon_age=] route.
func GetUnresolvedTransactions(ctx context.Context, r Request, api *API) *JSONResponse {
vars := r.Vars()

abandonAge, err := r.ParseQueryParamAsInt64("abandon_age", 0)
if err != nil {
return NewJSONResponse(nil, err)
}

res, err := api.server.GetUnresolvedTransactions(ctx, &vtadminpb.GetUnresolvedTransactionsRequest{
ClusterId: vars["cluster_id"],
Keyspace: vars["keyspace"],
ClusterId: vars["cluster_id"],
Keyspace: vars["keyspace"],
AbandonAge: abandonAge,
})

return NewJSONResponse(res, err)
}

// ConcludeTransaction implements the http wrapper for the
// /transaction/{cluster_id}/{dtid}/conclude route.
func ConcludeTransaction(ctx context.Context, r Request, api *API) *JSONResponse {
vars := r.Vars()

Expand Down
1 change: 1 addition & 0 deletions proto/vtadmin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@ message GetTopologyPathRequest {
message GetUnresolvedTransactionsRequest {
string cluster_id = 1;
string keyspace = 2;
int64 abandon_age = 3;
}

message GetVSchemaRequest {
Expand Down
8 changes: 6 additions & 2 deletions web/vtadmin/src/api/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,10 +424,14 @@ export const fetchVSchema = async ({ clusterID, keyspace }: FetchVSchemaParams)
export interface FetchTransactionsParams {
clusterID: string;
keyspace: string;
abandonAge?: string;
}

export const fetchTransactions = async ({ clusterID, keyspace }: FetchTransactionsParams) => {
const { result } = await vtfetch(`/api/transactions/${clusterID}/${keyspace}`);
export const fetchTransactions = async ({ clusterID, keyspace, abandonAge = '' }: FetchTransactionsParams) => {
const req = new URLSearchParams();
req.append('abandon_age', abandonAge);

const { result } = await vtfetch(`/api/transactions/${clusterID}/${keyspace}?${req}`);

const err = vtctldata.GetUnresolvedTransactionsResponse.verify(result);
if (err) throw Error(err);
Expand Down
111 changes: 78 additions & 33 deletions web/vtadmin/src/components/routes/Transactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useKeyspaces, useTransactions } from '../../hooks/api';
import { DataCell } from '../dataTable/DataCell';
import { DataTable } from '../dataTable/DataTable';
Expand All @@ -36,6 +36,33 @@ import { isReadOnlyMode } from '../../util/env';
const COLUMNS = ['ID', 'State', 'Participants', 'Time Created', 'Actions'];
const READ_ONLY_COLUMNS = ['ID', 'State', 'Participants', 'Time Created'];

const ABANDON_AGE_OPTIONS = [
{
displayText: '5sec',
abandonAge: '5',
},
{
displayText: '30sec',
abandonAge: '30',
},
{
displayText: '1min',
abandonAge: '60',
},
{
displayText: '5min',
abandonAge: '300',
},
{
displayText: '15min',
abandonAge: '900',
},
{
displayText: '1hr',
abandonAge: '3600',
},
];

export const Transactions = () => {
useDocumentTitle('In Flight Distributed Transactions');

Expand All @@ -49,13 +76,21 @@ export const Transactions = () => {
(ks) => ks.keyspace?.name === params.keyspace && ks.cluster?.id === params.clusterID
);

const selectedAbandonAge = ABANDON_AGE_OPTIONS.find((option) => option.abandonAge === params.abandonAge);

const transactionsQuery = useTransactions(params, {
enabled: !!params.keyspace,
});

const transactions =
(transactionsQuery.data && orderBy(transactionsQuery.data.transactions, ['time_created'], 'asc')) || [];

useEffect(() => {
if (params.clusterID && params.keyspace) {
transactionsQuery.refetch();
}
}, [params.abandonAge]);

Check warning on line 92 in web/vtadmin/src/components/routes/Transactions.tsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has missing dependencies: 'params.clusterID', 'params.keyspace', and 'transactionsQuery'. Either include them or remove the dependency array

Check warning on line 92 in web/vtadmin/src/components/routes/Transactions.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has missing dependencies: 'params.clusterID', 'params.keyspace', and 'transactionsQuery'. Either include them or remove the dependency array

const renderRows = (rows: query.ITransactionMetadata[]) => {
return rows.map((row) => {
return (
Expand Down Expand Up @@ -107,39 +142,49 @@ export const Transactions = () => {
</WorkspaceHeader>

<ContentContainer>
<div className='flex flex-row gap-1 max-w-[740px]'>
<Select
className="block grow-1 min-w-[300px]"
disabled={keyspacesQuery.isLoading}
inputClassName="block w-full"
items={keyspaces}
label="Keyspace"
onChange={(ks) => setParams({ clusterID: ks?.cluster?.id!, keyspace: ks?.keyspace?.name! })}
placeholder={
keyspacesQuery.isLoading
? 'Loading keyspaces...'
: 'Select a keyspace to view unresolved transactions'
}
renderItem={(ks) => `${ks?.keyspace?.name} (${ks?.cluster?.id})`}
selectedItem={selectedKeyspace}
/>
<Select
className="block grow-1 min-w-[300px]"
disabled={keyspacesQuery.isLoading}
inputClassName="block w-full"
items={keyspaces}
label="Keyspace"
onChange={(ks) => setParams({ clusterID: ks?.cluster?.id!, keyspace: ks?.keyspace?.name! })}
placeholder={
keyspacesQuery.isLoading
? 'Loading keyspaces...'
: 'Select a keyspace to view unresolved transactions'
}
renderItem={(ks) => `${ks?.keyspace?.name} (${ks?.cluster?.id})`}
selectedItem={selectedKeyspace}
/>
<div className="flex flex-row flex-wrap gap-4 max-w-[740px]">
<Select
className="block grow-1 min-w-[400px]"
disabled={keyspacesQuery.isLoading}
inputClassName="block w-full"
items={keyspaces}
label="Keyspace"
onChange={(ks) =>
setParams((prevParams) => ({
...prevParams,
clusterID: ks?.cluster?.id!,
keyspace: ks?.keyspace?.name!,
}))
}
placeholder={
keyspacesQuery.isLoading
? 'Loading keyspaces...'
: 'Select a keyspace to view unresolved transactions'
}
renderItem={(ks) => `${ks?.keyspace?.name} (${ks?.cluster?.id})`}
selectedItem={selectedKeyspace}
/>
<Select
className="block grow-1 min-w-[300px]"
inputClassName="block w-full"
items={ABANDON_AGE_OPTIONS}
label="Abandon Age"
helpText={
'List unresolved transactions which are older than the specified age (Leave empty for default abandon age)'
}
onChange={(option) =>
setParams((prevParams) => ({ ...prevParams, abandonAge: option?.abandonAge }))
}
placeholder={'Select abandon age'}
renderItem={(option) => `${option?.displayText}`}
selectedItem={selectedAbandonAge}
/>
</div>
<DataTable columns={isReadOnlyMode() ? READ_ONLY_COLUMNS : COLUMNS} data={transactions} renderRows={renderRows} />
<DataTable
columns={isReadOnlyMode() ? READ_ONLY_COLUMNS : COLUMNS}
data={transactions}
renderRows={renderRows}
/>
<QueryLoadingPlaceholder query={transactionsQuery} />
</ContentContainer>
</div>
Expand Down
6 changes: 6 additions & 0 deletions web/vtadmin/src/proto/vtadmin.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 62c6113

Please sign in to comment.