Skip to content

Commit 8e29eb8

Browse files
committed
Count scheduled compaction runs as idle KV/Query ops
1 parent 0efa2a4 commit 8e29eb8

File tree

7 files changed

+125
-65
lines changed

7 files changed

+125
-65
lines changed

base/stats.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ const (
8787
StatAddedVersion3dot1dot4 = "3.1.4"
8888
StatAddedVersion3dot2dot0 = "3.2.0"
8989
StatAddedVersion3dot2dot1 = "3.2.1"
90+
StatAddedVersion3dot2dot2 = "3.2.2"
9091
StatAddedVersion3dot3dot0 = "3.3.0"
9192

9293
StatDeprecatedVersionNotDeprecated = ""
@@ -312,6 +313,10 @@ func (g *GlobalStat) initResourceUtilizationStats() error {
312313
if err != nil {
313314
return err
314315
}
316+
resUtil.NumIdleQueryOps, err = NewIntStat(SubsystemDatabaseKey, "num_idle_query_ops", StatUnitNoUnits, NumIdleQueryOpsDesc, StatAddedVersion3dot2dot2, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, nil, nil, prometheus.CounterValue, 0)
317+
if err != nil {
318+
return err
319+
}
315320

316321
resUtil.Uptime, err = NewDurStat(ResourceUtilizationSubsystem, "uptime", StatUnitNanoseconds, UptimeDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, nil, nil, prometheus.CounterValue, time.Now())
317322
if err != nil {
@@ -366,8 +371,9 @@ type ResourceUtilization struct {
366371
// The node CPU usage calculation based values from /proc of user + system since the last time this function was called.
367372
NodeCpuPercentUtil *SgwFloatStat `json:"node_cpu_percent_utilization"`
368373

369-
// The number of background kv operations.
370-
NumIdleKvOps *SgwIntStat `json:"idle_kv_ops"`
374+
// The number of background kv/query operations.
375+
NumIdleKvOps *SgwIntStat `json:"idle_kv_ops"`
376+
NumIdleQueryOps *SgwIntStat `json:"idle_query_ops"`
371377

372378
// The memory utilization (Resident Set Size) for the process, in bytes.
373379
ProcessMemoryResident *SgwIntStat `json:"process_memory_resident"`

base/stats_descriptions.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,8 @@ const (
314314

315315
SyncProcessComputeDesc = "The compute unit for syncing with clients measured through cpu time and memory used for sync"
316316

317-
NumIdleKvOpsDesc = "The total number of idle kv operations."
317+
NumIdleKvOpsDesc = "The total number of idle kv operations."
318+
NumIdleQueryOpsDesc = "The total number of idle query operations."
318319
)
319320

320321
// Delta Sync stats descriptions

db/background_mgr_tombstone_compaction.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ func (t *TombstoneCompactionManager) Run(ctx context.Context, options map[string
4545
database := options["database"].(*Database)
4646

4747
defer atomic.CompareAndSwapUint32(&database.CompactState, DBCompactRunning, DBCompactNotRunning)
48-
callback := func(docsPurged *int) {
48+
updateStatusCallback := func(docsPurged *int) {
4949
atomic.StoreInt64(&t.PurgedDocCount, int64(*docsPurged))
5050
}
5151

52-
_, err := database.Compact(ctx, true, callback, terminator)
52+
_, err := database.Compact(ctx, true, updateStatusCallback, terminator, false)
5353
if err != nil {
5454
return err
5555
}

db/database.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,15 +1445,19 @@ func (db *DatabaseContext) GetRoleIDs(ctx context.Context, useViews, includeDele
14451445
return roles, nil
14461446
}
14471447

1448-
// Trigger tombstone compaction from view and/or GSI indexes. Several Sync Gateway indexes server tombstones (deleted documents with an xattr).
1448+
type compactProgressCallbackFunc func(purgedDocCount *int)
1449+
1450+
// Compact runs tombstone compaction from view and/or GSI indexes - ensuring there's nothing left in the indexes for tombstoned documents that have been purged by the server.
1451+
//
1452+
// Several Sync Gateway indexes server tombstones (deleted documents with an xattr).
14491453
// There currently isn't a mechanism for server to remove these docs from the index when the tombstone is purged by the server during
14501454
// metadata purge, because metadata purge doesn't trigger a DCP event.
14511455
// When compact is run, Sync Gateway initiates a normal delete operation for the document and xattr (a Sync Gateway purge). This triggers
14521456
// removal of the document from the index. In the event that the document has already been purged by server, we need to recreate and delete
14531457
// the document to accomplish the same result.
1454-
type compactCallbackFunc func(purgedDocCount *int)
1455-
1456-
func (db *Database) Compact(ctx context.Context, skipRunningStateCheck bool, callback compactCallbackFunc, terminator *base.SafeTerminator) (int, error) {
1458+
//
1459+
// The `isScheduledBackgroundTask` parameter is used to indicate if the compaction is being run as part of a scheduled background task, or an ad-hoc user-initiated `/{db}/_compact` request.
1460+
func (db *Database) Compact(ctx context.Context, skipRunningStateCheck bool, optionalProgressCallback compactProgressCallbackFunc, terminator *base.SafeTerminator, isScheduledBackgroundTask bool) (purgedDocCount int, err error) {
14571461
if !skipRunningStateCheck {
14581462
if !atomic.CompareAndSwapUint32(&db.CompactState, DBCompactNotRunning, DBCompactRunning) {
14591463
return 0, base.HTTPErrorf(http.StatusServiceUnavailable, "Compaction already running")
@@ -1474,12 +1478,13 @@ func (db *Database) Compact(ctx context.Context, skipRunningStateCheck bool, cal
14741478
startTime := time.Now()
14751479
purgeOlderThan := startTime.Add(-purgeInterval)
14761480

1477-
purgedDocCount := 0
14781481
purgeErrorCount := 0
14791482
addErrorCount := 0
14801483
deleteErrorCount := 0
14811484

1482-
defer callback(&purgedDocCount)
1485+
if optionalProgressCallback != nil {
1486+
defer optionalProgressCallback(&purgedDocCount)
1487+
}
14831488

14841489
base.InfofCtx(ctx, base.KeyAll, "Starting compaction of purged tombstones for %s ...", base.MD(db.Name))
14851490

@@ -1498,6 +1503,9 @@ func (db *Database) Compact(ctx context.Context, skipRunningStateCheck bool, cal
14981503
for {
14991504
purgedDocs := make([]string, 0)
15001505
results, err := collection.QueryTombstones(ctx, purgeOlderThan, QueryTombstoneBatch)
1506+
if isScheduledBackgroundTask {
1507+
base.SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleQueryOps.Add(1)
1508+
}
15011509
if err != nil {
15021510
return 0, err
15031511
}
@@ -1518,11 +1526,17 @@ func (db *Database) Compact(ctx context.Context, skipRunningStateCheck bool, cal
15181526
base.DebugfCtx(ctx, base.KeyCRUD, "\tDeleting %q", tombstonesRow.Id)
15191527
// First, attempt to purge.
15201528
purgeErr := collection.Purge(ctx, tombstonesRow.Id, false)
1529+
if isScheduledBackgroundTask {
1530+
base.SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleKvOps.Add(1)
1531+
}
15211532
if purgeErr == nil {
15221533
purgedDocs = append(purgedDocs, tombstonesRow.Id)
15231534
} else if base.IsDocNotFoundError(purgeErr) {
15241535
// If key no longer exists, need to add and remove to trigger removal from view
15251536
_, addErr := collection.dataStore.Add(tombstonesRow.Id, 0, purgeBody)
1537+
if isScheduledBackgroundTask {
1538+
base.SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleKvOps.Add(1)
1539+
}
15261540
if addErr != nil {
15271541
addErrorCount++
15281542
base.InfofCtx(ctx, base.KeyAll, "Couldn't compact key %s (add): %v", base.UD(tombstonesRow.Id), addErr)
@@ -1533,7 +1547,11 @@ func (db *Database) Compact(ctx context.Context, skipRunningStateCheck bool, cal
15331547
// so mark it to be removed from cache, even if the subsequent delete fails
15341548
purgedDocs = append(purgedDocs, tombstonesRow.Id)
15351549

1536-
if delErr := collection.dataStore.Delete(tombstonesRow.Id); delErr != nil {
1550+
delErr := collection.dataStore.Delete(tombstonesRow.Id)
1551+
if isScheduledBackgroundTask {
1552+
base.SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleKvOps.Add(1)
1553+
}
1554+
if delErr != nil {
15371555
deleteErrorCount++
15381556
base.InfofCtx(ctx, base.KeyAll, "Couldn't compact key %s (delete): %v", base.UD(tombstonesRow.Id), delErr)
15391557
}
@@ -1557,7 +1575,9 @@ func (db *Database) Compact(ctx context.Context, skipRunningStateCheck bool, cal
15571575
}
15581576
base.InfofCtx(ctx, base.KeyAll, "Compacted %v tombstones", count)
15591577

1560-
callback(&purgedDocCount)
1578+
if optionalProgressCallback != nil {
1579+
optionalProgressCallback(&purgedDocCount)
1580+
}
15611581

15621582
if resultCount < QueryTombstoneBatch {
15631583
break
@@ -2428,7 +2448,7 @@ func (db *DatabaseContext) StartOnlineProcesses(ctx context.Context) (returnedEr
24282448
bgtTerminator.Close()
24292449
}()
24302450
bgt, err := NewBackgroundTask(ctx, "Compact", func(ctx context.Context) error {
2431-
_, err := db.Compact(ctx, false, func(purgedDocCount *int) {}, bgtTerminator)
2451+
_, err := db.Compact(ctx, false, nil, bgtTerminator, true)
24322452
if err != nil {
24332453
base.WarnfCtx(ctx, "Error trying to compact tombstoned documents for %q with error: %v", db.Name, err)
24342454
}

docs/api/components/schemas.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ ExpVars:
138138
num_idle_kv_ops:
139139
type: integer
140140
description: "The total number of idle kv operations."
141+
num_idle_query_ops:
142+
type: integer
143+
description: "The total number of idle query operations."
141144
process_cpu_percent_utilization:
142145
type: number
143146
format: float

rest/adminapitest/admin_api_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4166,7 +4166,7 @@ func TestTombstoneCompactionPurgeInterval(t *testing.T) {
41664166

41674167
// Start compact to modify purge interval
41684168
database, _ := db.GetDatabase(dbc, nil)
4169-
_, err = database.Compact(ctx, false, func(purgedDocCount *int) {}, base.NewSafeTerminator())
4169+
_, err = database.Compact(ctx, false, nil, base.NewSafeTerminator(), false)
41704170
require.NoError(t, err)
41714171

41724172
assert.EqualValues(t, test.expectedPurgeIntervalAfterCompact, dbc.GetMetadataPurgeInterval(ctx))

rest/changestest/changes_api_test.go

Lines changed: 80 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3500,6 +3500,20 @@ func TestTombstoneCompaction(t *testing.T) {
35003500
t.Skip("If running with no xattrs compact acts as a no-op")
35013501
}
35023502

3503+
tests := []struct {
3504+
numDocs int
3505+
runAsScheduledBackgroundTask bool
3506+
}{
3507+
// Multiples of Batch Size
3508+
{numDocs: db.QueryTombstoneBatch},
3509+
{numDocs: db.QueryTombstoneBatch * 4},
3510+
// Smaller Than Batch Size
3511+
{numDocs: 2},
3512+
{numDocs: db.QueryTombstoneBatch / 4},
3513+
// Larger than Batch Size
3514+
{numDocs: db.QueryTombstoneBatch + 20},
3515+
}
3516+
35033517
var rt *rest.RestTester
35043518
numCollections := 1
35053519

@@ -3510,64 +3524,80 @@ func TestTombstoneCompaction(t *testing.T) {
35103524
rt = rest.NewRestTester(t, nil)
35113525
}
35123526
defer rt.Close()
3513-
zero := time.Duration(0)
3514-
rt.GetDatabase().Options.PurgeInterval = &zero
3515-
3516-
compactionTotal := 0
3517-
expectedBatches := 0
3527+
rt.GetDatabase().Options.PurgeInterval = base.Ptr(time.Duration(0))
35183528

3519-
TestCompact := func(numDocs int) {
3520-
3521-
count := 0
3529+
for _, test := range tests {
3530+
for _, runAsScheduledBackgroundTask := range []bool{false, true} {
3531+
t.Run(fmt.Sprintf("numDocs:%d asBackgroundTask:%v", test.numDocs, runAsScheduledBackgroundTask), func(t *testing.T) {
3532+
3533+
// seed with tombstones
3534+
for count := 0; count < test.numDocs; count++ {
3535+
for _, keyspace := range rt.GetKeyspaces() {
3536+
response := rt.SendAdminRequest("POST", fmt.Sprintf("/%s/", keyspace), `{"foo":"bar"}`)
3537+
assert.Equal(t, http.StatusOK, response.Code)
3538+
var body db.Body
3539+
err := base.JSONUnmarshal(response.Body.Bytes(), &body)
3540+
assert.NoError(t, err)
3541+
revID := body["rev"].(string)
3542+
docID := body["id"].(string)
3543+
3544+
response = rt.SendAdminRequest("DELETE", fmt.Sprintf("/%s/%s?rev=%s", keyspace, docID, revID), "")
3545+
assert.Equal(t, http.StatusOK, response.Code)
3546+
}
3547+
}
35223548

3523-
for count < numDocs {
3524-
count++
3525-
for _, keyspace := range rt.GetKeyspaces() {
3526-
response := rt.SendAdminRequest("POST", fmt.Sprintf("/%s/", keyspace), `{"foo":"bar"}`)
3527-
assert.Equal(t, 200, response.Code)
3528-
var body db.Body
3529-
err := base.JSONUnmarshal(response.Body.Bytes(), &body)
3530-
assert.NoError(t, err)
3531-
revId := body["rev"].(string)
3532-
docId := body["id"].(string)
3549+
expectedCompactions := (test.numDocs * numCollections)
3550+
expectedBatches := (test.numDocs/db.QueryTombstoneBatch + 1) * numCollections
35333551

3534-
response = rt.SendAdminRequest("DELETE", fmt.Sprintf("/%s/%s?rev=%s", keyspace, docId, revId), "")
3535-
assert.Equal(t, 200, response.Code)
3536-
}
3537-
}
3538-
resp := rt.SendAdminRequest("POST", "/{{.db}}/_compact", "")
3539-
rest.RequireStatus(t, resp, http.StatusOK)
3552+
numCompactionsBefore := int(rt.GetDatabase().DbStats.Database().NumTombstonesCompacted.Value())
3553+
var numBatchesBefore int
3554+
if base.TestsDisableGSI() {
3555+
numBatchesBefore = int(rt.GetDatabase().DbStats.Query(fmt.Sprintf(base.StatViewFormat, db.DesignDocSyncHousekeeping(), db.ViewTombstones)).QueryCount.Value())
3556+
} else {
3557+
numBatchesBefore = int(rt.GetDatabase().DbStats.Query(db.QueryTypeTombstones).QueryCount.Value())
3558+
}
35403559

3541-
err := rt.WaitForCondition(func() bool {
3542-
time.Sleep(1 * time.Second)
3543-
return rt.GetDatabase().TombstoneCompactionManager.GetRunState() == db.BackgroundProcessStateCompleted
3544-
})
3545-
assert.NoError(t, err)
3560+
numIdleKvOpsBefore := int(base.SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleKvOps.Value())
3561+
numIdleQueryOpsBefore := int(base.SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleQueryOps.Value())
3562+
3563+
if runAsScheduledBackgroundTask {
3564+
database, err := db.CreateDatabase(rt.GetDatabase())
3565+
require.NoError(t, err)
3566+
_, err = database.Compact(base.TestCtx(t), false, nil, base.NewSafeTerminator(), true)
3567+
require.NoError(t, err)
3568+
3569+
numIdleKvOps := int(base.SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleKvOps.Value()) - numIdleKvOpsBefore
3570+
numIdleQueryOps := int(base.SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleQueryOps.Value()) - numIdleQueryOpsBefore
3571+
assert.Equal(t, expectedCompactions, numIdleKvOps)
3572+
assert.Equal(t, expectedBatches, numIdleQueryOps)
3573+
} else {
3574+
resp := rt.SendAdminRequest("POST", "/{{.db}}/_compact", "")
3575+
rest.RequireStatus(t, resp, http.StatusOK)
3576+
err := rt.WaitForCondition(func() bool {
3577+
return rt.GetDatabase().TombstoneCompactionManager.GetRunState() == db.BackgroundProcessStateCompleted
3578+
})
3579+
assert.NoError(t, err)
3580+
3581+
numIdleKvOps := int(base.SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleKvOps.Value()) - numIdleKvOpsBefore
3582+
numIdleQueryOps := int(base.SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleQueryOps.Value()) - numIdleQueryOpsBefore
3583+
// ad-hoc compactions don't invoke idle ops
3584+
assert.Equal(t, 0, numIdleKvOps)
3585+
assert.Equal(t, 0, numIdleQueryOps)
3586+
}
35463587

3547-
compactionTotal += (numDocs * numCollections)
3548-
require.Equal(t, compactionTotal, int(rt.GetDatabase().DbStats.Database().NumTombstonesCompacted.Value()))
3588+
actualCompactions := int(rt.GetDatabase().DbStats.Database().NumTombstonesCompacted.Value()) - numCompactionsBefore
3589+
require.Equal(t, expectedCompactions, actualCompactions)
35493590

3550-
var actualBatches int64
3551-
if base.TestsDisableGSI() {
3552-
actualBatches = rt.GetDatabase().DbStats.Query(fmt.Sprintf(base.StatViewFormat, db.DesignDocSyncHousekeeping(), db.ViewTombstones)).QueryCount.Value()
3553-
} else {
3554-
actualBatches = rt.GetDatabase().DbStats.Query(db.QueryTypeTombstones).QueryCount.Value()
3591+
var actualBatches int
3592+
if base.TestsDisableGSI() {
3593+
actualBatches = int(rt.GetDatabase().DbStats.Query(fmt.Sprintf(base.StatViewFormat, db.DesignDocSyncHousekeeping(), db.ViewTombstones)).QueryCount.Value()) - numBatchesBefore
3594+
} else {
3595+
actualBatches = int(rt.GetDatabase().DbStats.Query(db.QueryTypeTombstones).QueryCount.Value()) - numBatchesBefore
3596+
}
3597+
require.Equal(t, expectedBatches, actualBatches)
3598+
})
35553599
}
3556-
3557-
expectedBatches += (numDocs/db.QueryTombstoneBatch + 1) * numCollections
3558-
require.Equal(t, expectedBatches, int(actualBatches))
35593600
}
3560-
3561-
// Multiples of Batch Size
3562-
TestCompact(db.QueryTombstoneBatch)
3563-
TestCompact(db.QueryTombstoneBatch * 4)
3564-
3565-
// Smaller Than Batch Size
3566-
TestCompact(2)
3567-
TestCompact(db.QueryTombstoneBatch / 4)
3568-
3569-
// Larger than Batch Size
3570-
TestCompact(db.QueryTombstoneBatch + 20)
35713601
}
35723602

35733603
// TestOneShotGrantTiming simulates a one-shot changes feed returning before a previously issued grant has been

0 commit comments

Comments
 (0)