Folders: Reduce DB queries when counting and deleting resources under folders (#81153)
* Add folder store method for fetching all folder descendants * Modify GetDescendantCounts() to fetch folder descendants at once * Reduce DB calls when counting library panels under dashboard * Reduce DB calls when counting dashboards under folder * Reduce DB calls during folder delete * Modify folder registry to count/delete entities under multiple folders * Reduce DB calls when counting * Reduce DB calls when deleting
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm"
|
||||
@@ -657,7 +658,12 @@ func (d *dashboardStore) DeleteDashboard(ctx context.Context, cmd *dashboards.De
|
||||
}
|
||||
|
||||
func (d *dashboardStore) deleteDashboard(cmd *dashboards.DeleteDashboardCommand, sess *db.Session, emitEntityEvent bool) error {
|
||||
dashboard := dashboards.Dashboard{ID: cmd.ID, OrgID: cmd.OrgID}
|
||||
dashboard := dashboards.Dashboard{OrgID: cmd.OrgID}
|
||||
if cmd.UID != "" {
|
||||
dashboard.UID = cmd.UID
|
||||
} else {
|
||||
dashboard.ID = cmd.ID
|
||||
}
|
||||
has, err := sess.Get(&dashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1008,42 +1014,58 @@ func (d *dashboardStore) GetDashboardTags(ctx context.Context, query *dashboards
|
||||
|
||||
// CountDashboardsInFolder returns a count of all dashboards associated with the
|
||||
// given parent folder ID.
|
||||
//
|
||||
// This will be updated to take CountDashboardsInFolderQuery as an argument and
|
||||
// lookup dashboards using the ParentFolderUID when dashboards are associated with a parent folder UID instead of ID.
|
||||
func (d *dashboardStore) CountDashboardsInFolder(
|
||||
func (d *dashboardStore) CountDashboardsInFolders(
|
||||
ctx context.Context, req *dashboards.CountDashboardsInFolderRequest) (int64, error) {
|
||||
if len(req.FolderUIDs) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var count int64
|
||||
var err error
|
||||
err = d.store.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
err := d.store.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc()
|
||||
// nolint:staticcheck
|
||||
session := sess.In("folder_id", req.FolderID).In("org_id", req.OrgID).
|
||||
In("is_folder", d.store.GetDialect().BooleanStr(false))
|
||||
count, err = session.Count(&dashboards.Dashboard{})
|
||||
s := strings.Builder{}
|
||||
args := make([]any, 0, 3)
|
||||
s.WriteString("SELECT COUNT(*) FROM dashboard WHERE ")
|
||||
if len(req.FolderUIDs) == 1 && req.FolderUIDs[0] == "" {
|
||||
s.WriteString("folder_uid IS NULL")
|
||||
} else {
|
||||
s.WriteString(fmt.Sprintf("folder_uid IN (%s)", strings.Repeat("?,", len(req.FolderUIDs)-1)+"?"))
|
||||
for _, folderUID := range req.FolderUIDs {
|
||||
args = append(args, folderUID)
|
||||
}
|
||||
}
|
||||
s.WriteString(" AND org_id = ? AND is_folder = ?")
|
||||
args = append(args, req.OrgID, d.store.GetDialect().BooleanStr(false))
|
||||
sql := s.String()
|
||||
_, err := sess.SQL(sql, args...).Get(&count)
|
||||
return err
|
||||
})
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (d *dashboardStore) DeleteDashboardsInFolder(
|
||||
func (d *dashboardStore) DeleteDashboardsInFolders(
|
||||
ctx context.Context, req *dashboards.DeleteDashboardsInFolderRequest) error {
|
||||
return d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
||||
dashboard := dashboards.Dashboard{OrgID: req.OrgID}
|
||||
has, err := sess.Where("uid = ? AND org_id = ?", req.FolderUID, req.OrgID).Get(&dashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return dashboards.ErrFolderNotFound
|
||||
}
|
||||
// TODO delete all dashboards in the folder in a bulk query
|
||||
for _, folderUID := range req.FolderUIDs {
|
||||
dashboard := dashboards.Dashboard{OrgID: req.OrgID}
|
||||
has, err := sess.Where("org_id = ? AND uid = ?", req.OrgID, folderUID).Get(&dashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return dashboards.ErrFolderNotFound
|
||||
}
|
||||
|
||||
if err := d.deleteChildrenDashboardAssociations(sess, &dashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.deleteChildrenDashboardAssociations(sess, &dashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sess.Where("folder_id = ? AND org_id = ? AND is_folder = ?", dashboard.ID, dashboard.OrgID, false).Delete(&dashboards.Dashboard{})
|
||||
return err
|
||||
_, err = sess.Where("folder_id = ? AND org_id = ? AND is_folder = ?", dashboard.ID, dashboard.OrgID, false).Delete(&dashboards.Dashboard{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,11 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
|
||||
var err error
|
||||
dashboardStore, err = ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore), quotaService)
|
||||
require.NoError(t, err)
|
||||
// insertTestDashboard creates the following hierarchy:
|
||||
// 1 test dash folder
|
||||
// test dash 23
|
||||
// test dash 45
|
||||
// test dash 67
|
||||
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, "", true, "prod", "webapp")
|
||||
savedDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, savedFolder.ID, savedFolder.UID, false, "prod", "webapp")
|
||||
insertTestDashboard(t, dashboardStore, "test dash 45", 1, savedFolder.ID, savedFolder.UID, false, "prod")
|
||||
@@ -470,17 +475,15 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
|
||||
t.Run("Can count dashboards by parent folder", func(t *testing.T) {
|
||||
setup()
|
||||
// setup() saves one dashboard in the general folder and two in the "savedFolder".
|
||||
count, err := dashboardStore.CountDashboardsInFolder(
|
||||
count, err := dashboardStore.CountDashboardsInFolders(
|
||||
context.Background(),
|
||||
// nolint:staticcheck
|
||||
&dashboards.CountDashboardsInFolderRequest{FolderID: 0, OrgID: 1})
|
||||
&dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{""}, OrgID: 1})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), count)
|
||||
|
||||
count, err = dashboardStore.CountDashboardsInFolder(
|
||||
count, err = dashboardStore.CountDashboardsInFolders(
|
||||
context.Background(),
|
||||
// nolint:staticcheck
|
||||
&dashboards.CountDashboardsInFolderRequest{FolderID: savedFolder.ID, OrgID: 1})
|
||||
&dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{savedFolder.UID}, OrgID: 1})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(2), count)
|
||||
})
|
||||
@@ -491,16 +494,16 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
|
||||
_ = insertTestDashboard(t, dashboardStore, "delete me 1", 1, folder.ID, folder.UID, false, "delete this 1")
|
||||
_ = insertTestDashboard(t, dashboardStore, "delete me 2", 1, folder.ID, folder.UID, false, "delete this 2")
|
||||
|
||||
err := dashboardStore.DeleteDashboardsInFolder(
|
||||
err := dashboardStore.DeleteDashboardsInFolders(
|
||||
context.Background(),
|
||||
&dashboards.DeleteDashboardsInFolderRequest{
|
||||
FolderUID: folder.UID,
|
||||
OrgID: 1,
|
||||
FolderUIDs: []string{folder.UID},
|
||||
OrgID: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// nolint:staticcheck
|
||||
count, err := dashboardStore.CountDashboardsInFolder(context.Background(), &dashboards.CountDashboardsInFolderRequest{FolderID: 2, OrgID: 1})
|
||||
count, err := dashboardStore.CountDashboardsInFolders(context.Background(), &dashboards.CountDashboardsInFolderRequest{FolderUIDs: []string{folder.UID}, OrgID: 1})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, count, int64(0))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user