diff --git a/changes/35484-improve-policy_membership-contention b/changes/35484-improve-policy_membership-contention new file mode 100644 index 0000000000..538b6828f4 --- /dev/null +++ b/changes/35484-improve-policy_membership-contention @@ -0,0 +1 @@ +* Fixed database locking issues on the policy_membership table by batching cleanup DELETE operations and moving them outside the primary GitOps apply transaction. \ No newline at end of file diff --git a/server/datastore/mysql/migrations/tables/20260316000001_AddPolicyNeedsFullMembershipCleanup.go b/server/datastore/mysql/migrations/tables/20260316000001_AddPolicyNeedsFullMembershipCleanup.go new file mode 100644 index 0000000000..d405d9d274 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20260316000001_AddPolicyNeedsFullMembershipCleanup.go @@ -0,0 +1,20 @@ +package tables + +import "database/sql" + +func init() { + MigrationClient.AddMigration(Up_20260316000001, Down_20260316000001) +} + +func Up_20260316000001(tx *sql.Tx) error { + _, err := tx.Exec(` + ALTER TABLE policies + ADD COLUMN needs_full_membership_cleanup TINYINT(1) NOT NULL DEFAULT 0, + ALGORITHM=INSTANT + `) + return err +} + +func Down_20260316000001(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/policies.go b/server/datastore/mysql/policies.go index b7cc223827..0909205f62 100644 --- a/server/datastore/mysql/policies.go +++ b/server/datastore/mysql/policies.go @@ -61,6 +61,21 @@ var ( var policySearchColumns = []string{"p.name"} +// policyMembershipDeleteBatchSize is the number of host IDs to delete per batch +// when cleaning up policy_membership rows. Exposed as a var (not a const) so that +// integration tests can use a smaller value to exercise multi-batch code paths. +// Do not mutate concurrently (no synchronization); tests that override this must +// not use t.Parallel(). +var policyMembershipDeleteBatchSize = 500 + +// policyCleanupArgs holds the arguments needed to run cleanupPolicy +type policyCleanupArgs struct { + policyID uint + platform string + shouldRemoveAllPolicyMemberships bool + removePolicyStats bool +} + func (ds *Datastore) NewGlobalPolicy(ctx context.Context, authorID *uint, args fleet.PolicyPayload) (*fleet.Policy, error) { var newPolicy *fleet.Policy @@ -479,6 +494,7 @@ func cleanupPolicy( removePolicyStats bool, logger *slog.Logger, ) error { var err error + if shouldRemoveAllPolicyMemberships { err = cleanupPolicyMembershipForPolicy(ctx, queryerContext, extContext, policyID) } else { @@ -487,6 +503,7 @@ func cleanupPolicy( if err != nil { return err } + if removePolicyStats { // delete all policy stats for the policy fn := func(tx sqlx.ExtContext) error { @@ -627,15 +644,14 @@ func (ds *Datastore) RecordPolicyQueryExecutions(ctx context.Context, host *flee // semantically equivalent, even though here it processes a single host and // in async mode it processes a batch of hosts). - err = ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + err = ds.withTx(ctx, func(tx sqlx.ExtContext) error { if len(results) > 0 { query := fmt.Sprintf( `INSERT INTO policy_membership (updated_at, policy_id, host_id, passes) - VALUES %s ON DUPLICATE KEY UPDATE updated_at=VALUES(updated_at), passes=VALUES(passes)`, + VALUES %s ON DUPLICATE KEY UPDATE updated_at=VALUES(updated_at), passes=VALUES(passes)`, strings.Join(bindvars, ","), ) - _, err := tx.ExecContext(ctx, query, vals...) - if err != nil { + if _, err := tx.ExecContext(ctx, query, vals...); err != nil { return ctxerr.Wrapf(ctx, err, "insert policy_membership (%v)", vals) } @@ -658,14 +674,11 @@ func (ds *Datastore) RecordPolicyQueryExecutions(ctx context.Context, host *flee } } } - // if we are deferring host updates, we return at this point and do the change outside of the tx - if deferredSaveHost { - return nil - } - - if _, err := tx.ExecContext(ctx, `UPDATE hosts SET policy_updated_at = ? WHERE id=?`, updated, host.ID); err != nil { - return ctxerr.Wrap(ctx, err, "updating hosts policy updated at") + if !deferredSaveHost { + if _, err := tx.ExecContext(ctx, `UPDATE hosts SET policy_updated_at = ? WHERE id=?`, updated, host.ID); err != nil { + return ctxerr.Wrap(ctx, err, "updating hosts policy updated at") + } } return nil }) @@ -1334,14 +1347,15 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs } } - // Get the query and platforms of the current policies so that we can check if query or platform changed later, if needed + // Get the query and platforms of the current policies so that we can check if the query or platform changed later, if needed type policyLite struct { - Name string `db:"name"` - Query string `db:"query"` - Platforms string `db:"platforms"` - SoftwareInstallerID *uint `db:"software_installer_id"` - VPPAppsTeamsID *uint `db:"vpp_apps_teams_id"` - ScriptID *uint `db:"script_id"` + Name string `db:"name"` + Query string `db:"query"` + Platforms string `db:"platforms"` + SoftwareInstallerID *uint `db:"software_installer_id"` + VPPAppsTeamsID *uint `db:"vpp_apps_teams_id"` + ScriptID *uint `db:"script_id"` + NeedsFullMembershipCleanup bool `db:"needs_full_membership_cleanup"` } teamIDToPoliciesByName := make(map[*uint]map[string]policyLite, len(teamIDToPolicies)) for teamID, teamPolicySpecs := range teamIDToPolicies { @@ -1355,10 +1369,10 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs var args []interface{} var err error if teamID == nil { - query, args, err = sqlx.In("SELECT name, query, platforms, software_installer_id, vpp_apps_teams_id, script_id FROM policies WHERE team_id IS NULL AND name IN (?)", policyNames) + query, args, err = sqlx.In("SELECT name, query, platforms, software_installer_id, vpp_apps_teams_id, script_id, needs_full_membership_cleanup FROM policies WHERE team_id IS NULL AND name IN (?)", policyNames) } else { query, args, err = sqlx.In( - "SELECT name, query, platforms, software_installer_id, vpp_apps_teams_id, script_id FROM policies WHERE team_id = ? AND name IN (?)", *teamID, policyNames, + "SELECT name, query, platforms, software_installer_id, vpp_apps_teams_id, script_id, needs_full_membership_cleanup FROM policies WHERE team_id = ? AND name IN (?)", *teamID, policyNames, ) } if err != nil { @@ -1374,7 +1388,11 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs } } - return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + var pendingCleanups []policyCleanupArgs + err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + // Reset on retry so we don't accumulate duplicate cleanup entries. + pendingCleanups = pendingCleanups[:0] + query := fmt.Sprintf( ` INSERT INTO policies ( @@ -1515,6 +1533,14 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs return ctxerr.Wrap(ctx, err, "select policies id") } } + policyID := uint(lastID) //nolint:gosec // dismiss G115 + + // If a previous cleanup was interrupted (e.g. server crash between + // transaction commit and cleanup completion), re-trigger cleanup on any GitOps retry, + // even if the policy itself didn't change this run. + if prev, ok := teamIDToPoliciesByName[teamID][spec.Name]; ok && prev.NeedsFullMembershipCleanup { + shouldRemoveAllPolicyMemberships = true + } // Create LabelIdents to send to updatePolicyLabelsTx. // Right now we only need the names. @@ -1527,9 +1553,10 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs for _, labelExclude := range spec.LabelsExcludeAny { labelsExcludeAnyIdents = append(labelsExcludeAnyIdents, fleet.LabelIdent{LabelName: labelExclude}) } + err = updatePolicyLabelsTx(ctx, tx, &fleet.Policy{ PolicyData: fleet.PolicyData{ - ID: uint(lastID), //nolint:gosec // dismiss G115 + ID: policyID, LabelsIncludeAny: labelsIncludeAnyIdents, LabelsExcludeAny: labelsExcludeAnyIdents, }, @@ -1538,22 +1565,62 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs return ctxerr.Wrap(ctx, err, "exec policies update labels") } - // Run cleanup after labels are updated so the cleanup function can - // query the current label criteria from the database. - // Always run cleanup since labels may have changed even if the main policy - // fields didn't (the cleanup function is safe to call and will only delete - // memberships that don't match current criteria). - if err = cleanupPolicy( - ctx, tx, tx, uint(lastID), spec.Platform, shouldRemoveAllPolicyMemberships, //nolint:gosec // dismiss G115 - removePolicyStats, ds.logger, - ); err != nil { - return err + // Mark this policy for cleanup, so that the policy cleanup cron job can pick it up + // in case we fail and don't retry + if shouldRemoveAllPolicyMemberships { + if _, err := tx.ExecContext(ctx, + `UPDATE policies SET needs_full_membership_cleanup = 1 WHERE id = ?`, + policyID); err != nil { + return ctxerr.Wrap(ctx, err, "setting needs_full_membership_cleanup flag") + } } - + // Defer cleanup outside the transaction to avoid long-held row locks on + // policy_membership. + pendingCleanups = append(pendingCleanups, policyCleanupArgs{ + policyID: policyID, + platform: spec.Platform, + shouldRemoveAllPolicyMemberships: shouldRemoveAllPolicyMemberships, + removePolicyStats: removePolicyStats, + }) } } return nil }) + if err != nil { + // It's OK to return 'early' if err, since the policy cleanup + // cron job can pick up any remnants + return err + } + + // Run cleanup after labels are updated so the cleanup function can + // query the current label criteria from the database. + // Always run cleanup since labels may have changed even if the main policy + // fields didn't (the cleanup function is safe to call and will only delete + // memberships that don't match current criteria). + dbCtx := ds.writer(ctx) + for _, args := range pendingCleanups { + if err := cleanupPolicy( + ctx, + dbCtx, + dbCtx, + args.policyID, + args.platform, + args.shouldRemoveAllPolicyMemberships, + args.removePolicyStats, + ds.logger, + ); err != nil { + return err + } + + if args.shouldRemoveAllPolicyMemberships { + if _, err := ds.writer(ctx).ExecContext(ctx, + `UPDATE policies SET needs_full_membership_cleanup = 0 WHERE id = ?`, + args.policyID); err != nil { + return ctxerr.Wrap(ctx, err, "clearing needs_full_membership_cleanup flag") + } + } + } + return nil } func amountPoliciesDB(ctx context.Context, db sqlx.QueryerContext) (int, error) { @@ -1739,109 +1806,111 @@ func cleanupConditionalAccessOnTeamChange(ctx context.Context, tx sqlx.ExtContex func cleanupPolicyMembershipOnPolicyUpdate( ctx context.Context, queryerContext sqlx.QueryerContext, db sqlx.ExecerContext, policyID uint, platforms string, ) error { - var allHostIDs []uint - - // Clean up hosts that don't match the platform criteria + // Clean up hosts that don't match the platform criteria. + // Page through rows using the (policy_id, host_id) PK as a cursor so each SELECT+DELETE + // batch holds locks for as little as possible. if platforms != "" { - delStmt := ` - DELETE - pm - FROM - policy_membership pm - LEFT JOIN - hosts h - ON - pm.host_id = h.id - WHERE - pm.policy_id = ? AND - ( h.id IS NULL OR - FIND_IN_SET(h.platform, ?) = 0 )` - - selectStmt := ` - SELECT DISTINCT - h.id - FROM - policy_membership pm - INNER JOIN - hosts h - ON - pm.host_id = h.id - WHERE - pm.policy_id = ? AND - FIND_IN_SET(h.platform, ?) = 0` - var expandedPlatforms []string for platform := range strings.SplitSeq(platforms, ",") { expandedPlatforms = append(expandedPlatforms, fleet.ExpandPlatform(strings.TrimSpace(platform))...) } + expandedPlatformsStr := strings.Join(expandedPlatforms, ",") - // Find the impacted host IDs, so we can update their host issues entries - var hostIDs []uint - err := sqlx.SelectContext(ctx, queryerContext, &hostIDs, selectStmt, policyID, strings.Join(expandedPlatforms, ",")) - if err != nil { - return ctxerr.Wrap(ctx, err, "select hosts to cleanup policy membership for platform") + var afterHostID uint + for { + var batchHostIDs []uint + err := sqlx.SelectContext(ctx, queryerContext, &batchHostIDs, ` + SELECT pm.host_id + FROM policy_membership pm + INNER JOIN hosts h ON pm.host_id = h.id + WHERE pm.policy_id = ? AND FIND_IN_SET(h.platform, ?) = 0 + AND pm.host_id > ? + ORDER BY pm.host_id ASC + LIMIT ?`, policyID, expandedPlatformsStr, afterHostID, policyMembershipDeleteBatchSize) + if err != nil { + return ctxerr.Wrap(ctx, err, "select batch of hosts to cleanup policy membership for platform") + } + if len(batchHostIDs) == 0 { + break + } + + batchStmt, args, err := sqlx.In( + `DELETE FROM policy_membership WHERE policy_id = ? AND host_id IN (?)`, + policyID, batchHostIDs, + ) + if err != nil { + return ctxerr.Wrap(ctx, err, "building batch delete for platform policy membership") + } + if _, err = db.ExecContext(ctx, batchStmt, args...); err != nil { + return ctxerr.Wrap(ctx, err, "batch cleanup policy membership for platform") + } + if err := updateHostIssuesFailingPolicies(ctx, db, batchHostIDs); err != nil { + return err + } + afterHostID = batchHostIDs[len(batchHostIDs)-1] } - allHostIDs = append(allHostIDs, hostIDs...) - - _, err = db.ExecContext(ctx, delStmt, policyID, strings.Join(expandedPlatforms, ",")) - if err != nil { - return ctxerr.Wrap(ctx, err, "cleanup policy membership for platform") + // Clean up orphaned memberships (host_id refs to deleted hosts, not covered by INNER JOIN above) + if _, err := db.ExecContext(ctx, ` + DELETE pm FROM policy_membership pm + LEFT JOIN hosts h ON pm.host_id = h.id + WHERE pm.policy_id = ? AND h.id IS NULL`, policyID); err != nil { + return ctxerr.Wrap(ctx, err, "cleanup orphaned policy membership for platform") } } - labelQuery := ` - FROM - policy_membership pm - WHERE - pm.policy_id = ? - AND NOT ( - ( - -- If the policy has no include labels, all hosts match this part. - NOT EXISTS ( - SELECT 1 FROM policy_labels pl - WHERE pl.policy_id = pm.policy_id AND pl.exclude = 0 - ) - -- If the policy has include labels, the host must be in at least one of them. - OR EXISTS ( - SELECT 1 FROM policy_labels pl - JOIN label_membership lm ON lm.label_id = pl.label_id AND lm.host_id = pm.host_id - WHERE pl.policy_id = pm.policy_id AND pl.exclude = 0 - ) - ) - -- If the policy has exclude labels, the host must not be in any of them. - AND NOT EXISTS ( - SELECT 1 FROM policy_labels pl - JOIN label_membership lm ON lm.label_id = pl.label_id AND lm.host_id = pm.host_id - WHERE pl.policy_id = pm.policy_id AND pl.exclude = 1 - ) - )` + // Clean up hosts that don't match the label criteria. + var afterLabelHostID uint + for { + var batchHostIDs []uint + err := sqlx.SelectContext(ctx, queryerContext, &batchHostIDs, ` + SELECT pm.host_id + FROM policy_membership pm + WHERE pm.policy_id = ? + AND pm.host_id > ? + AND NOT ( + ( + -- If the policy has no include labels, all hosts match this part. + NOT EXISTS ( + SELECT 1 FROM policy_labels pl + WHERE pl.policy_id = pm.policy_id AND pl.exclude = 0 + ) + -- If the policy has include labels, the host must be in at least one of them. + OR EXISTS ( + SELECT 1 FROM policy_labels pl + JOIN label_membership lm ON lm.label_id = pl.label_id AND lm.host_id = pm.host_id + WHERE pl.policy_id = pm.policy_id AND pl.exclude = 0 + ) + ) + -- If the policy has exclude labels, the host must not be in any of them. + AND NOT EXISTS ( + SELECT 1 FROM policy_labels pl + JOIN label_membership lm ON lm.label_id = pl.label_id AND lm.host_id = pm.host_id + WHERE pl.policy_id = pm.policy_id AND pl.exclude = 1 + ) + ) + ORDER BY pm.host_id ASC + LIMIT ?`, policyID, afterLabelHostID, policyMembershipDeleteBatchSize) + if err != nil { + return ctxerr.Wrap(ctx, err, "select batch of hosts to cleanup policy membership for labels") + } + if len(batchHostIDs) == 0 { + break + } - // Find the impacted host IDs, so we can update their host issues entries. - labelSelectStmt := ` - SELECT DISTINCT - pm.host_id - ` + labelQuery - - // Delete memberships for hosts that don't match the label criteria. - labelDelStmt := ` - DELETE pm - ` + labelQuery - - var labelHostIDs []uint - err := sqlx.SelectContext(ctx, queryerContext, &labelHostIDs, labelSelectStmt, policyID) - if err != nil { - return ctxerr.Wrap(ctx, err, "select hosts to cleanup policy membership for labels") - } - allHostIDs = append(allHostIDs, labelHostIDs...) - - _, err = db.ExecContext(ctx, labelDelStmt, policyID) - if err != nil { - return ctxerr.Wrap(ctx, err, "cleanup policy membership for labels") - } - - // Update host issues entries. This method is rarely called, so performance should not be a concern. - if err = updateHostIssuesFailingPolicies(ctx, db, allHostIDs); err != nil { - return err + batchStmt, args, err := sqlx.In( + `DELETE FROM policy_membership WHERE policy_id = ? AND host_id IN (?)`, + policyID, batchHostIDs, + ) + if err != nil { + return ctxerr.Wrap(ctx, err, "building batch delete for label policy membership") + } + if _, err = db.ExecContext(ctx, batchStmt, args...); err != nil { + return ctxerr.Wrap(ctx, err, "batch cleanup policy membership for labels") + } + if err := updateHostIssuesFailingPolicies(ctx, db, batchHostIDs); err != nil { + return err + } + afterLabelHostID = batchHostIDs[len(batchHostIDs)-1] } return nil @@ -1850,49 +1919,52 @@ func cleanupPolicyMembershipOnPolicyUpdate( // cleanupPolicyMembership is similar to cleanupPolicyMembershipOnPolicyUpdate but without the platform constraints. // Used when we want to remove all policy membership. func cleanupPolicyMembershipForPolicy( - ctx context.Context, queryerContext sqlx.QueryerContext, exec sqlx.ExecerContext, policyID uint, + ctx context.Context, + queryerContext sqlx.QueryerContext, + exec sqlx.ExecerContext, + policyID uint, ) error { - selectStmt := ` - SELECT DISTINCT - h.id - FROM - policy_membership pm - INNER JOIN - hosts h - ON - pm.host_id = h.id - WHERE - pm.policy_id = ?` + // Page through policy_membership using (policy_id, host_id) as a cursor. Selecting and deleting one + // batch at a time means we never load all host IDs into memory at once, and each DELETE holds + // its row-locks for an extremely short period of time. + var afterHostID uint + for { + var batchHostIDs []uint + err := sqlx.SelectContext(ctx, queryerContext, &batchHostIDs, ` + SELECT pm.host_id + FROM policy_membership pm + INNER JOIN hosts h ON pm.host_id = h.id + WHERE pm.policy_id = ? AND pm.host_id > ? + ORDER BY pm.host_id ASC + LIMIT ?`, policyID, afterHostID, policyMembershipDeleteBatchSize) + if err != nil { + return ctxerr.Wrap(ctx, err, "select batch of hosts for policy membership cleanup") + } + if len(batchHostIDs) == 0 { + break + } - // delete all policy memberships for the policy - delStmt := ` - DELETE - pm - FROM - policy_membership pm - LEFT JOIN - hosts h - ON - pm.host_id = h.id - WHERE - pm.policy_id = ? - ` - - // Find the impacted host IDs, so we can update their host issues entries - var hostIDs []uint - err := sqlx.SelectContext(ctx, queryerContext, &hostIDs, selectStmt, policyID) - if err != nil { - return ctxerr.Wrap(ctx, err, "select hosts to cleanup policy membership for policy") + batchStmt, args, err := sqlx.In( + `DELETE FROM policy_membership WHERE policy_id = ? AND host_id IN (?)`, + policyID, batchHostIDs, + ) + if err != nil { + return ctxerr.Wrap(ctx, err, "building batch delete for policy membership") + } + if _, err = exec.ExecContext(ctx, batchStmt, args...); err != nil { + return ctxerr.Wrap(ctx, err, "batch cleanup policy membership") + } + if err := updateHostIssuesFailingPolicies(ctx, exec, batchHostIDs); err != nil { + return err + } + afterHostID = batchHostIDs[len(batchHostIDs)-1] } - - _, err = exec.ExecContext(ctx, delStmt, policyID) - if err != nil { - return ctxerr.Wrap(ctx, err, "cleanup policy membership") - } - - // Update host issues entries. This method is rarely called, so performance should not be a concern. - if err = updateHostIssuesFailingPolicies(ctx, exec, hostIDs); err != nil { - return err + // Clean up orphaned memberships (host_id refs to deleted hosts, not covered by INNER JOIN above) + if _, err := exec.ExecContext(ctx, ` + DELETE pm FROM policy_membership pm + LEFT JOIN hosts h ON pm.host_id = h.id + WHERE pm.policy_id = ? AND h.id IS NULL`, policyID); err != nil { + return ctxerr.Wrap(ctx, err, "cleanup orphaned policy membership") } return nil @@ -1900,7 +1972,7 @@ func cleanupPolicyMembershipForPolicy( // CleanupPolicyMembership deletes the host's membership from policies that // have been updated recently if those hosts don't meet the policy's criteria -// anymore (e.g. if the policy's platforms has been updated from "any" - the +// anymore (e.g. if the policy's platforms have been updated from "any" - the // empty string - to "windows", this would delete that policy's membership rows // for any non-windows host). func (ds *Datastore) CleanupPolicyMembership(ctx context.Context, now time.Time) error { @@ -1930,6 +2002,25 @@ func (ds *Datastore) CleanupPolicyMembership(ctx context.Context, now time.Time) } } + // We perform a policies clean when running gitops outside the apply transaction, the following is a 'fail-safe' + // in case the cleanup process couldn't complete due to server crashes or other unexpected events. + var fullCleanupPolIDs []uint + if err := sqlx.SelectContext(ctx, ds.reader(ctx), &fullCleanupPolIDs, + `SELECT id FROM policies WHERE needs_full_membership_cleanup = 1`, + ); err != nil { + return ctxerr.Wrap(ctx, err, "select policies needing full membership cleanup") + } + for _, polID := range fullCleanupPolIDs { + if err := cleanupPolicyMembershipForPolicy(ctx, ds.reader(ctx), ds.writer(ctx), polID); err != nil { + return ctxerr.Wrapf(ctx, err, "full membership cleanup for policy %d", polID) + } + if _, err := ds.writer(ctx).ExecContext(ctx, + `UPDATE policies SET needs_full_membership_cleanup = 0 WHERE id = ?`, polID, + ); err != nil { + return ctxerr.Wrapf(ctx, err, "clear full membership cleanup flag for policy %d", polID) + } + } + return nil } diff --git a/server/datastore/mysql/policies_test.go b/server/datastore/mysql/policies_test.go index dab9395266..1462899571 100644 --- a/server/datastore/mysql/policies_test.go +++ b/server/datastore/mysql/policies_test.go @@ -88,6 +88,10 @@ func TestPolicies(t *testing.T) { {"PolicyModificationResetsAttemptNumber", testPolicyModificationResetsAttemptNumber}, {"TeamPatchPolicy", testTeamPatchPolicy}, {"TeamPolicyAutomationFilter", testTeamPolicyAutomationFilter}, + {"BatchedPolicyMembershipCleanup", testBatchedPolicyMembershipCleanup}, + {"BatchedPolicyMembershipCleanupOnPolicyUpdate", testBatchedPolicyMembershipCleanupOnPolicyUpdate}, + {"ApplyPolicySpecsNeedsFullMembershipCleanupFlag", testApplyPolicySpecsNeedsFullMembershipCleanupFlag}, + {"CleanupPolicyMembershipCrashRecovery", testCleanupPolicyMembershipCrashRecovery}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { @@ -7096,6 +7100,425 @@ func testPolicyModificationResetsAttemptNumber(t *testing.T, ds *Datastore) { require.Equal(t, int64(0), *scriptResults[1].AttemptNumber) } +// testBatchedPolicyMembershipCleanup verifies that cleanupPolicyMembershipForPolicy and +// cleanupPolicyMembershipOnPolicyUpdate correctly delete rows in small batches (to reduce lock +// contention) rather than in a single large DELETE, and that all memberships are fully removed. +func testBatchedPolicyMembershipCleanup(t *testing.T, ds *Datastore) { + ctx := context.Background() + user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true) + + // Override batch size to force multiple batches with a small number of hosts. + orig := policyMembershipDeleteBatchSize + policyMembershipDeleteBatchSize = 2 + t.Cleanup(func() { policyMembershipDeleteBatchSize = orig }) + + // Create a policy and 5 hosts (more than the batch size of 2). + pol := newTestPolicy(t, ds, user1, "batch cleanup policy", "", nil) + hosts := make([]*fleet.Host, 5) + for i := range hosts { + id := fmt.Sprintf("batch-cleanup-%d", i) + h, err := ds.NewHost(ctx, &fleet.Host{ + OsqueryHostID: &id, + DetailUpdatedAt: time.Now(), + LabelUpdatedAt: time.Now(), + PolicyUpdatedAt: time.Now(), + SeenTime: time.Now(), + NodeKey: &id, + UUID: id, + Hostname: id, + Platform: "linux", + }) + require.NoError(t, err) + hosts[i] = h + } + + // Record failing results for all hosts so they all have policy_membership rows and host_issues entries. + for _, h := range hosts { + err := ds.RecordPolicyQueryExecutions(ctx, h, map[uint]*bool{pol.ID: ptr.Bool(false)}, time.Now(), false) + require.NoError(t, err) + } + + // Collect host IDs for scoped host_issues assertions (avoids flakiness if other tests + // leave rows in host_issues). + hostIDs := make([]uint, len(hosts)) + for i, h := range hosts { + hostIDs[i] = h.ID + } + hostIssuesQ, hostIssuesArgs, err := sqlx.In( + `SELECT COUNT(*) FROM host_issues WHERE host_id IN (?) AND total_issues_count > 0`, hostIDs, + ) + require.NoError(t, err) + + // Confirm all memberships exist. + var count int + require.NoError(t, ds.writer(ctx).Get(&count, `SELECT COUNT(*) FROM policy_membership WHERE policy_id = ?`, pol.ID)) + require.Equal(t, 5, count) + + // Confirm all hosts have failing policy issues. + require.NoError(t, ds.writer(ctx).Get(&count, hostIssuesQ, hostIssuesArgs...)) + require.Equal(t, 5, count) + + // Run the full cleanup function directly (simulates what ApplyPolicySpecs triggers when a + // query changes — shouldRemoveAllPolicyMemberships == true). + err = cleanupPolicyMembershipForPolicy(ctx, ds.reader(ctx), ds.writer(ctx), pol.ID) + require.NoError(t, err) + + // All policy_membership rows must be gone. + require.NoError(t, ds.writer(ctx).Get(&count, `SELECT COUNT(*) FROM policy_membership WHERE policy_id = ?`, pol.ID)) + assert.Zero(t, count) + + // host_issues must be updated (no more failing policies for those hosts). + require.NoError(t, ds.writer(ctx).Get(&count, hostIssuesQ, hostIssuesArgs...)) + assert.Zero(t, count) +} + +// testBatchedPolicyMembershipCleanupOnPolicyUpdate verifies that cleanupPolicyMembershipOnPolicyUpdate +// deletes rows in batches for both the platform and label sections. +func testBatchedPolicyMembershipCleanupOnPolicyUpdate(t *testing.T, ds *Datastore) { + ctx := context.Background() + user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true) + + // Override batch size to force multiple batches. + orig := policyMembershipDeleteBatchSize + policyMembershipDeleteBatchSize = 2 + t.Cleanup(func() { policyMembershipDeleteBatchSize = orig }) + + // ── Part 1: platform-based cleanup ────────────────────────────────────── + // Create a windows-only policy. + pol := newTestPolicy(t, ds, user1, "batch platform cleanup", "windows", nil) + + // Create 5 linux hosts (wrong platform) + 1 windows host (should remain). + winID := "batch-win-0" + winHost, err := ds.NewHost(ctx, &fleet.Host{ + OsqueryHostID: &winID, + DetailUpdatedAt: time.Now(), + LabelUpdatedAt: time.Now(), + PolicyUpdatedAt: time.Now(), + SeenTime: time.Now(), + NodeKey: &winID, + UUID: winID, + Hostname: winID, + Platform: "windows", + }) + require.NoError(t, err) + + linuxHosts := make([]*fleet.Host, 5) + for i := range linuxHosts { + id := fmt.Sprintf("batch-lin-%d", i) + h, err := ds.NewHost(ctx, &fleet.Host{ + OsqueryHostID: &id, + DetailUpdatedAt: time.Now(), + LabelUpdatedAt: time.Now(), + PolicyUpdatedAt: time.Now(), + SeenTime: time.Now(), + NodeKey: &id, + UUID: id, + Hostname: id, + Platform: "linux", + }) + require.NoError(t, err) + linuxHosts[i] = h + } + + // Record results for all hosts (simulating results arriving before platform filter applied). + allHosts := append([]*fleet.Host{winHost}, linuxHosts...) + for _, h := range allHosts { + err := ds.RecordPolicyQueryExecutions(ctx, h, map[uint]*bool{pol.ID: ptr.Bool(false)}, time.Now(), false) + require.NoError(t, err) + } + + var count int + require.NoError(t, ds.writer(ctx).Get(&count, `SELECT COUNT(*) FROM policy_membership WHERE policy_id = ?`, pol.ID)) + require.Equal(t, 6, count) + + // Run the platform-aware cleanup (simulates CleanupPolicyMembership cron). + err = cleanupPolicyMembershipOnPolicyUpdate(ctx, ds.reader(ctx), ds.writer(ctx), pol.ID, pol.Platform) + require.NoError(t, err) + + // Only the windows host should remain. + var hostIDs []uint + require.NoError(t, ds.writer(ctx).Select(&hostIDs, `SELECT host_id FROM policy_membership WHERE policy_id = ?`, pol.ID)) + require.ElementsMatch(t, []uint{winHost.ID}, hostIDs) + + // ── Part 2: label-based cleanup ───────────────────────────────────────── + // Create a label and a policy that targets only hosts in that label. + inclLabel, err := ds.NewLabel(ctx, &fleet.Label{Name: "batch-incl-label"}) + require.NoError(t, err) + + // Create 1 host that belongs to the label (should survive cleanup) and 5 + // that do not (should be removed in multiple batches of 2). + lblID := "batch-lbl-0" + lblHost, err := ds.NewHost(ctx, &fleet.Host{ + OsqueryHostID: &lblID, + DetailUpdatedAt: time.Now(), + LabelUpdatedAt: time.Now(), + PolicyUpdatedAt: time.Now(), + SeenTime: time.Now(), + NodeKey: &lblID, + UUID: lblID, + Hostname: lblID, + Platform: "linux", + }) + require.NoError(t, err) + require.NoError(t, ds.AddLabelsToHost(ctx, lblHost.ID, []uint{inclLabel.ID})) + + nonLblHosts := make([]*fleet.Host, 5) + for i := range nonLblHosts { + id := fmt.Sprintf("batch-nonlbl-%d", i) + h, err := ds.NewHost(ctx, &fleet.Host{ + OsqueryHostID: &id, + DetailUpdatedAt: time.Now(), + LabelUpdatedAt: time.Now(), + PolicyUpdatedAt: time.Now(), + SeenTime: time.Now(), + NodeKey: &id, + UUID: id, + Hostname: id, + Platform: "linux", + }) + require.NoError(t, err) + nonLblHosts[i] = h + } + + // Create a label-scoped policy (no platform restriction). + lblPol := newTestPolicy(t, ds, user1, "batch label cleanup", "", nil) + lblPol.LabelsIncludeAny = []fleet.LabelIdent{{LabelName: inclLabel.Name}} + require.NoError(t, ds.SavePolicy(ctx, lblPol, false, false)) + + // Record policy results for all label-test hosts so policy_membership is populated. + labelHosts := append([]*fleet.Host{lblHost}, nonLblHosts...) + for _, h := range labelHosts { + err := ds.RecordPolicyQueryExecutions(ctx, h, map[uint]*bool{lblPol.ID: ptr.Bool(false)}, time.Now(), false) + require.NoError(t, err) + } + + require.NoError(t, ds.writer(ctx).Get(&count, `SELECT COUNT(*) FROM policy_membership WHERE policy_id = ?`, lblPol.ID)) + require.Equal(t, 6, count) + + // Run cleanupPolicyMembershipOnPolicyUpdate with no platform restriction so + // only the label-based branch fires. + err = cleanupPolicyMembershipOnPolicyUpdate(ctx, ds.reader(ctx), ds.writer(ctx), lblPol.ID, "" /* no platform filter */) + require.NoError(t, err) + + // Only the host that belongs to the include label should remain. + var lblHostIDs []uint + require.NoError(t, ds.writer(ctx).Select(&lblHostIDs, `SELECT host_id FROM policy_membership WHERE policy_id = ?`, lblPol.ID)) + require.ElementsMatch(t, []uint{lblHost.ID}, lblHostIDs) +} + +// testApplyPolicySpecsNeedsFullMembershipCleanupFlag verifies that: +// 1. ApplyPolicySpecs sets needs_full_membership_cleanup = 1 inside the transaction when +// the query changes (shouldRemoveAllPolicyMemberships == true). +// 2. The flag is cleared back to 0 after cleanup completes successfully. +// 3. All policy_membership rows are removed after the cleanup. +func testApplyPolicySpecsNeedsFullMembershipCleanupFlag(t *testing.T, ds *Datastore) { + ctx := context.Background() + user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true) + + // Create the policy for the first time. + require.NoError(t, ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{ + {Name: "flag test policy", Query: "select 1;", Platform: "", Type: fleet.PolicyTypeDynamic}, + })) + + // Find the policy by name so the test is not sensitive to other global policies created by concurrent tests. + pols, err := ds.ListGlobalPolicies(ctx, fleet.ListOptions{}) + require.NoError(t, err) + var pol *fleet.Policy + for _, p := range pols { + if p.Name == "flag test policy" { + pol = p + break + } + } + require.NotNil(t, pol, "policy 'flag test policy' not found") + + // Create hosts and record failing results. + hosts := make([]*fleet.Host, 3) + for i := range hosts { + id := fmt.Sprintf("flag-test-%d", i) + h, err := ds.NewHost(ctx, &fleet.Host{ + OsqueryHostID: &id, + DetailUpdatedAt: time.Now(), + LabelUpdatedAt: time.Now(), + PolicyUpdatedAt: time.Now(), + SeenTime: time.Now(), + NodeKey: &id, + UUID: id, + Hostname: id, + Platform: "linux", + }) + require.NoError(t, err) + hosts[i] = h + } + for _, h := range hosts { + err := ds.RecordPolicyQueryExecutions(ctx, h, map[uint]*bool{pol.ID: ptr.Bool(false)}, time.Now(), false) + require.NoError(t, err) + } + + var count int + require.NoError(t, ds.writer(ctx).Get(&count, `SELECT COUNT(*) FROM policy_membership WHERE policy_id = ?`, pol.ID)) + require.Equal(t, 3, count) + + // Update the query — this triggers shouldRemoveAllPolicyMemberships = true. + require.NoError(t, ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{ + {Name: "flag test policy", Query: "select 2;", Platform: "", Type: fleet.PolicyTypeDynamic}, + })) + + // The flag must be 0 after successful completion (set inside TX, cleared after cleanup). + var flagVal int + require.NoError(t, ds.writer(ctx).Get(&flagVal, + `SELECT needs_full_membership_cleanup FROM policies WHERE id = ?`, pol.ID)) + assert.Zero(t, flagVal, "needs_full_membership_cleanup must be cleared after successful cleanup") + + // All memberships must have been removed. + require.NoError(t, ds.writer(ctx).Get(&count, `SELECT COUNT(*) FROM policy_membership WHERE policy_id = ?`, pol.ID)) + assert.Zero(t, count) +} + +// testCleanupPolicyMembershipCrashRecovery verifies two recovery paths when a previous cleanup +// was interrupted (crash or error after the transaction committed): +// +// 1. GitOps retry path: ApplyPolicySpecs detects needs_full_membership_cleanup = 1 and re-runs +// the full cleanup itself, without waiting for the cron. +// 2. Cron safety net path: CleanupPolicyMembership finds needs_full_membership_cleanup = 1 and +// finishes the job when no GitOps retry occurs. +func testCleanupPolicyMembershipCrashRecovery(t *testing.T, ds *Datastore) { + ctx := context.Background() + user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true) + + newHosts := func(t *testing.T, n int, prefix string) []*fleet.Host { + t.Helper() + hosts := make([]*fleet.Host, n) + for i := range hosts { + id := fmt.Sprintf("%s-%d", prefix, i) + h, err := ds.NewHost(ctx, &fleet.Host{ + OsqueryHostID: &id, + DetailUpdatedAt: time.Now(), + LabelUpdatedAt: time.Now(), + PolicyUpdatedAt: time.Now(), + SeenTime: time.Now(), + NodeKey: &id, + UUID: id, + Hostname: id, + Platform: "linux", + }) + require.NoError(t, err) + hosts[i] = h + } + return hosts + } + + recordResults := func(t *testing.T, hosts []*fleet.Host, polID uint) { + t.Helper() + for _, h := range hosts { + err := ds.RecordPolicyQueryExecutions(ctx, h, map[uint]*bool{polID: ptr.Bool(false)}, time.Now(), false) + require.NoError(t, err) + } + } + + t.Run("gitops retry re-triggers cleanup", func(t *testing.T) { + // Create policy via ApplyPolicySpecs so it exists in the DB. + require.NoError(t, ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{ + {Name: "retry recovery policy", Query: "select 1;", Type: fleet.PolicyTypeDynamic}, + })) + pols, err := ds.ListGlobalPolicies(ctx, fleet.ListOptions{}) + require.NoError(t, err) + var pol *fleet.Policy + for _, p := range pols { + if p.Name == "retry recovery policy" { + pol = p + break + } + } + require.NotNil(t, pol) + + // Record membership rows. + hosts := newHosts(t, 4, "retry-recovery") + recordResults(t, hosts, pol.ID) + + var count int + require.NoError(t, ds.writer(ctx).Get(&count, `SELECT COUNT(*) FROM policy_membership WHERE policy_id = ?`, pol.ID)) + require.Equal(t, 4, count) + + // Simulate: TX committed with the flag set, but cleanup never ran (crash/error). + _, err = ds.writer(ctx).ExecContext(ctx, + `UPDATE policies SET needs_full_membership_cleanup = 1 WHERE id = ?`, pol.ID) + require.NoError(t, err) + + // Retry GitOps with the same spec. ApplyPolicySpecs must detect the flag and + // re-run the full cleanup — no cron needed. + require.NoError(t, ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{ + {Name: "retry recovery policy", Query: "select 1;", Type: fleet.PolicyTypeDynamic}, + })) + + // Flag must be cleared by the retry. + var flagVal int + require.NoError(t, ds.writer(ctx).Get(&flagVal, + `SELECT needs_full_membership_cleanup FROM policies WHERE id = ?`, pol.ID)) + assert.Zero(t, flagVal, "flag must be cleared by the GitOps retry") + + // All memberships must be gone. + require.NoError(t, ds.writer(ctx).Get(&count, `SELECT COUNT(*) FROM policy_membership WHERE policy_id = ?`, pol.ID)) + assert.Zero(t, count, "all policy_membership rows must be removed by the GitOps retry") + }) + + t.Run("cron cleans up when no gitops retry", func(t *testing.T) { + pol := newTestPolicy(t, ds, user1, "cron recovery policy", "", nil) + + hosts := newHosts(t, 4, "cron-recovery") + recordResults(t, hosts, pol.ID) + + var count int + require.NoError(t, ds.writer(ctx).Get(&count, `SELECT COUNT(*) FROM policy_membership WHERE policy_id = ?`, pol.ID)) + require.Equal(t, 4, count) + + // Simulate interrupted cleanup: set the flag directly, leave membership rows in place. + _, err := ds.writer(ctx).ExecContext(ctx, + `UPDATE policies SET needs_full_membership_cleanup = 1 WHERE id = ?`, pol.ID) + require.NoError(t, err) + + // CleanupPolicyMembership (cron) should pick up the flag and run the full cleanup. + require.NoError(t, ds.CleanupPolicyMembership(ctx, time.Now())) + + // Flag must be cleared. + var flagVal int + require.NoError(t, ds.writer(ctx).Get(&flagVal, + `SELECT needs_full_membership_cleanup FROM policies WHERE id = ?`, pol.ID)) + assert.Zero(t, flagVal, "flag must be cleared by CleanupPolicyMembership") + + // All memberships must be removed. + require.NoError(t, ds.writer(ctx).Get(&count, `SELECT COUNT(*) FROM policy_membership WHERE policy_id = ?`, pol.ID)) + assert.Zero(t, count, "all policy_membership rows must be cleaned up by the cron safety net") + }) + + t.Run("cron clears flag when cleanup already completed", func(t *testing.T) { + // Simulates: the transaction committed (flag=1), cleanupPolicy ran and + // removed all membership rows, but the server crashed before executing + // UPDATE policies SET needs_full_membership_cleanup = 0. + // The cron must handle this gracefully (no-op cleanup) and clear the flag. + pol := newTestPolicy(t, ds, user1, "flag-only recovery policy", "", nil) + + // No membership rows exist — simulating that cleanup already removed them. + var count int + require.NoError(t, ds.writer(ctx).Get(&count, `SELECT COUNT(*) FROM policy_membership WHERE policy_id = ?`, pol.ID)) + require.Zero(t, count, "precondition: no membership rows") + + // Set the flag to simulate the crash window between cleanup and flag clear. + _, err := ds.writer(ctx).ExecContext(ctx, + `UPDATE policies SET needs_full_membership_cleanup = 1 WHERE id = ?`, pol.ID) + require.NoError(t, err) + + // CleanupPolicyMembership (cron) should handle this without errors. + require.NoError(t, ds.CleanupPolicyMembership(ctx, time.Now())) + + // Flag must be cleared. + var flagVal int + require.NoError(t, ds.writer(ctx).Get(&flagVal, + `SELECT needs_full_membership_cleanup FROM policies WHERE id = ?`, pol.ID)) + assert.Zero(t, flagVal, "flag must be cleared even when no membership rows remain") + }) +} + func testTeamPatchPolicy(t *testing.T, ds *Datastore) { ctx := context.Background() user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true) diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 58a7f334ca..440f9d5907 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -1803,9 +1803,9 @@ CREATE TABLE `migration_status_tables` ( `is_applied` tinyint(1) NOT NULL, `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=494 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=495 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'),(332,20241125150614,1,'2020-01-01 01:01:01'),(333,20241203125346,1,'2020-01-01 01:01:01'),(334,20241203130032,1,'2020-01-01 01:01:01'),(335,20241205122800,1,'2020-01-01 01:01:01'),(336,20241209164540,1,'2020-01-01 01:01:01'),(337,20241210140021,1,'2020-01-01 01:01:01'),(338,20241219180042,1,'2020-01-01 01:01:01'),(339,20241220100000,1,'2020-01-01 01:01:01'),(340,20241220114903,1,'2020-01-01 01:01:01'),(341,20241220114904,1,'2020-01-01 01:01:01'),(342,20241224000000,1,'2020-01-01 01:01:01'),(343,20241230000000,1,'2020-01-01 01:01:01'),(344,20241231112624,1,'2020-01-01 01:01:01'),(345,20250102121439,1,'2020-01-01 01:01:01'),(346,20250121094045,1,'2020-01-01 01:01:01'),(347,20250121094500,1,'2020-01-01 01:01:01'),(348,20250121094600,1,'2020-01-01 01:01:01'),(349,20250121094700,1,'2020-01-01 01:01:01'),(350,20250124194347,1,'2020-01-01 01:01:01'),(351,20250127162751,1,'2020-01-01 01:01:01'),(352,20250213104005,1,'2020-01-01 01:01:01'),(353,20250214205657,1,'2020-01-01 01:01:01'),(354,20250217093329,1,'2020-01-01 01:01:01'),(355,20250219090511,1,'2020-01-01 01:01:01'),(356,20250219100000,1,'2020-01-01 01:01:01'),(357,20250219142401,1,'2020-01-01 01:01:01'),(358,20250224184002,1,'2020-01-01 01:01:01'),(359,20250225085436,1,'2020-01-01 01:01:01'),(360,20250226000000,1,'2020-01-01 01:01:01'),(361,20250226153445,1,'2020-01-01 01:01:01'),(362,20250304162702,1,'2020-01-01 01:01:01'),(363,20250306144233,1,'2020-01-01 01:01:01'),(364,20250313163430,1,'2020-01-01 01:01:01'),(365,20250317130944,1,'2020-01-01 01:01:01'),(366,20250318165922,1,'2020-01-01 01:01:01'),(367,20250320132525,1,'2020-01-01 01:01:01'),(368,20250320200000,1,'2020-01-01 01:01:01'),(369,20250326161930,1,'2020-01-01 01:01:01'),(370,20250326161931,1,'2020-01-01 01:01:01'),(371,20250331042354,1,'2020-01-01 01:01:01'),(372,20250331154206,1,'2020-01-01 01:01:01'),(373,20250401155831,1,'2020-01-01 01:01:01'),(374,20250408133233,1,'2020-01-01 01:01:01'),(375,20250410104321,1,'2020-01-01 01:01:01'),(376,20250421085116,1,'2020-01-01 01:01:01'),(377,20250422095806,1,'2020-01-01 01:01:01'),(378,20250424153059,1,'2020-01-01 01:01:01'),(379,20250430103833,1,'2020-01-01 01:01:01'),(380,20250430112622,1,'2020-01-01 01:01:01'),(381,20250501162727,1,'2020-01-01 01:01:01'),(382,20250502154517,1,'2020-01-01 01:01:01'),(383,20250502222222,1,'2020-01-01 01:01:01'),(384,20250507170845,1,'2020-01-01 01:01:01'),(385,20250513162912,1,'2020-01-01 01:01:01'),(386,20250519161614,1,'2020-01-01 01:01:01'),(387,20250519170000,1,'2020-01-01 01:01:01'),(388,20250520153848,1,'2020-01-01 01:01:01'),(389,20250528115932,1,'2020-01-01 01:01:01'),(390,20250529102706,1,'2020-01-01 01:01:01'),(391,20250603105558,1,'2020-01-01 01:01:01'),(392,20250609102714,1,'2020-01-01 01:01:01'),(393,20250609112613,1,'2020-01-01 01:01:01'),(394,20250613103810,1,'2020-01-01 01:01:01'),(395,20250616193950,1,'2020-01-01 01:01:01'),(396,20250624140757,1,'2020-01-01 01:01:01'),(397,20250626130239,1,'2020-01-01 01:01:01'),(398,20250629131032,1,'2020-01-01 01:01:01'),(399,20250701155654,1,'2020-01-01 01:01:01'),(400,20250707095725,1,'2020-01-01 01:01:01'),(401,20250716152435,1,'2020-01-01 01:01:01'),(402,20250718091828,1,'2020-01-01 01:01:01'),(403,20250728122229,1,'2020-01-01 01:01:01'),(404,20250731122715,1,'2020-01-01 01:01:01'),(405,20250731151000,1,'2020-01-01 01:01:01'),(406,20250803000000,1,'2020-01-01 01:01:01'),(407,20250805083116,1,'2020-01-01 01:01:01'),(408,20250807140441,1,'2020-01-01 01:01:01'),(409,20250808000000,1,'2020-01-01 01:01:01'),(410,20250811155036,1,'2020-01-01 01:01:01'),(411,20250813205039,1,'2020-01-01 01:01:01'),(412,20250814123333,1,'2020-01-01 01:01:01'),(413,20250815130115,1,'2020-01-01 01:01:01'),(414,20250816115553,1,'2020-01-01 01:01:01'),(415,20250817154557,1,'2020-01-01 01:01:01'),(416,20250825113751,1,'2020-01-01 01:01:01'),(417,20250827113140,1,'2020-01-01 01:01:01'),(418,20250828120836,1,'2020-01-01 01:01:01'),(419,20250902112642,1,'2020-01-01 01:01:01'),(420,20250904091745,1,'2020-01-01 01:01:01'),(421,20250905090000,1,'2020-01-01 01:01:01'),(422,20250922083056,1,'2020-01-01 01:01:01'),(423,20250923120000,1,'2020-01-01 01:01:01'),(424,20250926123048,1,'2020-01-01 01:01:01'),(425,20251015103505,1,'2020-01-01 01:01:01'),(426,20251015103600,1,'2020-01-01 01:01:01'),(427,20251015103700,1,'2020-01-01 01:01:01'),(428,20251015103800,1,'2020-01-01 01:01:01'),(429,20251015103900,1,'2020-01-01 01:01:01'),(430,20251028140000,1,'2020-01-01 01:01:01'),(431,20251028140100,1,'2020-01-01 01:01:01'),(432,20251028140110,1,'2020-01-01 01:01:01'),(433,20251028140200,1,'2020-01-01 01:01:01'),(434,20251028140300,1,'2020-01-01 01:01:01'),(435,20251028140400,1,'2020-01-01 01:01:01'),(436,20251031154558,1,'2020-01-01 01:01:01'),(437,20251103160848,1,'2020-01-01 01:01:01'),(438,20251104112849,1,'2020-01-01 01:01:01'),(439,20251106000000,1,'2020-01-01 01:01:01'),(440,20251107164629,1,'2020-01-01 01:01:01'),(441,20251107170854,1,'2020-01-01 01:01:01'),(442,20251110172137,1,'2020-01-01 01:01:01'),(443,20251111153133,1,'2020-01-01 01:01:01'),(444,20251117020000,1,'2020-01-01 01:01:01'),(445,20251117020100,1,'2020-01-01 01:01:01'),(446,20251117020200,1,'2020-01-01 01:01:01'),(447,20251121100000,1,'2020-01-01 01:01:01'),(448,20251121124239,1,'2020-01-01 01:01:01'),(449,20251124090450,1,'2020-01-01 01:01:01'),(450,20251124135808,1,'2020-01-01 01:01:01'),(451,20251124140138,1,'2020-01-01 01:01:01'),(452,20251124162948,1,'2020-01-01 01:01:01'),(453,20251127113559,1,'2020-01-01 01:01:01'),(454,20251202162232,1,'2020-01-01 01:01:01'),(455,20251203170808,1,'2020-01-01 01:01:01'),(456,20251207050413,1,'2020-01-01 01:01:01'),(457,20251208215800,1,'2020-01-01 01:01:01'),(458,20251209221730,1,'2020-01-01 01:01:01'),(459,20251209221850,1,'2020-01-01 01:01:01'),(460,20251215163721,1,'2020-01-01 01:01:01'),(461,20251217000000,1,'2020-01-01 01:01:01'),(462,20251217120000,1,'2020-01-01 01:01:01'),(463,20251229000000,1,'2020-01-01 01:01:01'),(464,20251229000010,1,'2020-01-01 01:01:01'),(465,20251229000020,1,'2020-01-01 01:01:01'),(466,20260106000000,1,'2020-01-01 01:01:01'),(467,20260108200708,1,'2020-01-01 01:01:01'),(468,20260108214732,1,'2020-01-01 01:01:01'),(469,20260109231821,1,'2020-01-01 01:01:01'),(470,20260113012054,1,'2020-01-01 01:01:01'),(471,20260124200020,1,'2020-01-01 01:01:01'),(472,20260126150840,1,'2020-01-01 01:01:01'),(473,20260126210724,1,'2020-01-01 01:01:01'),(474,20260202151756,1,'2020-01-01 01:01:01'),(475,20260205184907,1,'2020-01-01 01:01:01'),(476,20260210151544,1,'2020-01-01 01:01:01'),(477,20260210155109,1,'2020-01-01 01:01:01'),(478,20260210181120,1,'2020-01-01 01:01:01'),(479,20260211200153,1,'2020-01-01 01:01:01'),(480,20260217141240,1,'2020-01-01 01:01:01'),(481,20260217200906,1,'2020-01-01 01:01:01'),(482,20260218175704,1,'2020-01-01 01:01:01'),(483,20260218175705,1,'2020-01-01 01:01:01'),(484,20260218175706,1,'2020-01-01 01:01:01'),(485,20260223000000,1,'2020-01-01 01:01:01'),(486,20260225143121,1,'2020-01-01 01:01:01'),(487,20260226182000,1,'2020-01-01 01:01:01'),(488,20260228115022,1,'2020-01-01 01:01:01'),(489,20260303180102,1,'2020-01-01 01:01:01'),(490,20260306120000,1,'2020-01-01 01:01:01'),(491,20260311160000,1,'2020-01-01 01:01:01'),(492,20260313173516,1,'2020-01-01 01:01:01'),(493,20260314120000,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'),(332,20241125150614,1,'2020-01-01 01:01:01'),(333,20241203125346,1,'2020-01-01 01:01:01'),(334,20241203130032,1,'2020-01-01 01:01:01'),(335,20241205122800,1,'2020-01-01 01:01:01'),(336,20241209164540,1,'2020-01-01 01:01:01'),(337,20241210140021,1,'2020-01-01 01:01:01'),(338,20241219180042,1,'2020-01-01 01:01:01'),(339,20241220100000,1,'2020-01-01 01:01:01'),(340,20241220114903,1,'2020-01-01 01:01:01'),(341,20241220114904,1,'2020-01-01 01:01:01'),(342,20241224000000,1,'2020-01-01 01:01:01'),(343,20241230000000,1,'2020-01-01 01:01:01'),(344,20241231112624,1,'2020-01-01 01:01:01'),(345,20250102121439,1,'2020-01-01 01:01:01'),(346,20250121094045,1,'2020-01-01 01:01:01'),(347,20250121094500,1,'2020-01-01 01:01:01'),(348,20250121094600,1,'2020-01-01 01:01:01'),(349,20250121094700,1,'2020-01-01 01:01:01'),(350,20250124194347,1,'2020-01-01 01:01:01'),(351,20250127162751,1,'2020-01-01 01:01:01'),(352,20250213104005,1,'2020-01-01 01:01:01'),(353,20250214205657,1,'2020-01-01 01:01:01'),(354,20250217093329,1,'2020-01-01 01:01:01'),(355,20250219090511,1,'2020-01-01 01:01:01'),(356,20250219100000,1,'2020-01-01 01:01:01'),(357,20250219142401,1,'2020-01-01 01:01:01'),(358,20250224184002,1,'2020-01-01 01:01:01'),(359,20250225085436,1,'2020-01-01 01:01:01'),(360,20250226000000,1,'2020-01-01 01:01:01'),(361,20250226153445,1,'2020-01-01 01:01:01'),(362,20250304162702,1,'2020-01-01 01:01:01'),(363,20250306144233,1,'2020-01-01 01:01:01'),(364,20250313163430,1,'2020-01-01 01:01:01'),(365,20250317130944,1,'2020-01-01 01:01:01'),(366,20250318165922,1,'2020-01-01 01:01:01'),(367,20250320132525,1,'2020-01-01 01:01:01'),(368,20250320200000,1,'2020-01-01 01:01:01'),(369,20250326161930,1,'2020-01-01 01:01:01'),(370,20250326161931,1,'2020-01-01 01:01:01'),(371,20250331042354,1,'2020-01-01 01:01:01'),(372,20250331154206,1,'2020-01-01 01:01:01'),(373,20250401155831,1,'2020-01-01 01:01:01'),(374,20250408133233,1,'2020-01-01 01:01:01'),(375,20250410104321,1,'2020-01-01 01:01:01'),(376,20250421085116,1,'2020-01-01 01:01:01'),(377,20250422095806,1,'2020-01-01 01:01:01'),(378,20250424153059,1,'2020-01-01 01:01:01'),(379,20250430103833,1,'2020-01-01 01:01:01'),(380,20250430112622,1,'2020-01-01 01:01:01'),(381,20250501162727,1,'2020-01-01 01:01:01'),(382,20250502154517,1,'2020-01-01 01:01:01'),(383,20250502222222,1,'2020-01-01 01:01:01'),(384,20250507170845,1,'2020-01-01 01:01:01'),(385,20250513162912,1,'2020-01-01 01:01:01'),(386,20250519161614,1,'2020-01-01 01:01:01'),(387,20250519170000,1,'2020-01-01 01:01:01'),(388,20250520153848,1,'2020-01-01 01:01:01'),(389,20250528115932,1,'2020-01-01 01:01:01'),(390,20250529102706,1,'2020-01-01 01:01:01'),(391,20250603105558,1,'2020-01-01 01:01:01'),(392,20250609102714,1,'2020-01-01 01:01:01'),(393,20250609112613,1,'2020-01-01 01:01:01'),(394,20250613103810,1,'2020-01-01 01:01:01'),(395,20250616193950,1,'2020-01-01 01:01:01'),(396,20250624140757,1,'2020-01-01 01:01:01'),(397,20250626130239,1,'2020-01-01 01:01:01'),(398,20250629131032,1,'2020-01-01 01:01:01'),(399,20250701155654,1,'2020-01-01 01:01:01'),(400,20250707095725,1,'2020-01-01 01:01:01'),(401,20250716152435,1,'2020-01-01 01:01:01'),(402,20250718091828,1,'2020-01-01 01:01:01'),(403,20250728122229,1,'2020-01-01 01:01:01'),(404,20250731122715,1,'2020-01-01 01:01:01'),(405,20250731151000,1,'2020-01-01 01:01:01'),(406,20250803000000,1,'2020-01-01 01:01:01'),(407,20250805083116,1,'2020-01-01 01:01:01'),(408,20250807140441,1,'2020-01-01 01:01:01'),(409,20250808000000,1,'2020-01-01 01:01:01'),(410,20250811155036,1,'2020-01-01 01:01:01'),(411,20250813205039,1,'2020-01-01 01:01:01'),(412,20250814123333,1,'2020-01-01 01:01:01'),(413,20250815130115,1,'2020-01-01 01:01:01'),(414,20250816115553,1,'2020-01-01 01:01:01'),(415,20250817154557,1,'2020-01-01 01:01:01'),(416,20250825113751,1,'2020-01-01 01:01:01'),(417,20250827113140,1,'2020-01-01 01:01:01'),(418,20250828120836,1,'2020-01-01 01:01:01'),(419,20250902112642,1,'2020-01-01 01:01:01'),(420,20250904091745,1,'2020-01-01 01:01:01'),(421,20250905090000,1,'2020-01-01 01:01:01'),(422,20250922083056,1,'2020-01-01 01:01:01'),(423,20250923120000,1,'2020-01-01 01:01:01'),(424,20250926123048,1,'2020-01-01 01:01:01'),(425,20251015103505,1,'2020-01-01 01:01:01'),(426,20251015103600,1,'2020-01-01 01:01:01'),(427,20251015103700,1,'2020-01-01 01:01:01'),(428,20251015103800,1,'2020-01-01 01:01:01'),(429,20251015103900,1,'2020-01-01 01:01:01'),(430,20251028140000,1,'2020-01-01 01:01:01'),(431,20251028140100,1,'2020-01-01 01:01:01'),(432,20251028140110,1,'2020-01-01 01:01:01'),(433,20251028140200,1,'2020-01-01 01:01:01'),(434,20251028140300,1,'2020-01-01 01:01:01'),(435,20251028140400,1,'2020-01-01 01:01:01'),(436,20251031154558,1,'2020-01-01 01:01:01'),(437,20251103160848,1,'2020-01-01 01:01:01'),(438,20251104112849,1,'2020-01-01 01:01:01'),(439,20251106000000,1,'2020-01-01 01:01:01'),(440,20251107164629,1,'2020-01-01 01:01:01'),(441,20251107170854,1,'2020-01-01 01:01:01'),(442,20251110172137,1,'2020-01-01 01:01:01'),(443,20251111153133,1,'2020-01-01 01:01:01'),(444,20251117020000,1,'2020-01-01 01:01:01'),(445,20251117020100,1,'2020-01-01 01:01:01'),(446,20251117020200,1,'2020-01-01 01:01:01'),(447,20251121100000,1,'2020-01-01 01:01:01'),(448,20251121124239,1,'2020-01-01 01:01:01'),(449,20251124090450,1,'2020-01-01 01:01:01'),(450,20251124135808,1,'2020-01-01 01:01:01'),(451,20251124140138,1,'2020-01-01 01:01:01'),(452,20251124162948,1,'2020-01-01 01:01:01'),(453,20251127113559,1,'2020-01-01 01:01:01'),(454,20251202162232,1,'2020-01-01 01:01:01'),(455,20251203170808,1,'2020-01-01 01:01:01'),(456,20251207050413,1,'2020-01-01 01:01:01'),(457,20251208215800,1,'2020-01-01 01:01:01'),(458,20251209221730,1,'2020-01-01 01:01:01'),(459,20251209221850,1,'2020-01-01 01:01:01'),(460,20251215163721,1,'2020-01-01 01:01:01'),(461,20251217000000,1,'2020-01-01 01:01:01'),(462,20251217120000,1,'2020-01-01 01:01:01'),(463,20251229000000,1,'2020-01-01 01:01:01'),(464,20251229000010,1,'2020-01-01 01:01:01'),(465,20251229000020,1,'2020-01-01 01:01:01'),(466,20260106000000,1,'2020-01-01 01:01:01'),(467,20260108200708,1,'2020-01-01 01:01:01'),(468,20260108214732,1,'2020-01-01 01:01:01'),(469,20260109231821,1,'2020-01-01 01:01:01'),(470,20260113012054,1,'2020-01-01 01:01:01'),(471,20260124200020,1,'2020-01-01 01:01:01'),(472,20260126150840,1,'2020-01-01 01:01:01'),(473,20260126210724,1,'2020-01-01 01:01:01'),(474,20260202151756,1,'2020-01-01 01:01:01'),(475,20260205184907,1,'2020-01-01 01:01:01'),(476,20260210151544,1,'2020-01-01 01:01:01'),(477,20260210155109,1,'2020-01-01 01:01:01'),(478,20260210181120,1,'2020-01-01 01:01:01'),(479,20260211200153,1,'2020-01-01 01:01:01'),(480,20260217141240,1,'2020-01-01 01:01:01'),(481,20260217200906,1,'2020-01-01 01:01:01'),(482,20260218175704,1,'2020-01-01 01:01:01'),(483,20260218175705,1,'2020-01-01 01:01:01'),(484,20260218175706,1,'2020-01-01 01:01:01'),(485,20260223000000,1,'2020-01-01 01:01:01'),(486,20260225143121,1,'2020-01-01 01:01:01'),(487,20260226182000,1,'2020-01-01 01:01:01'),(488,20260228115022,1,'2020-01-01 01:01:01'),(489,20260303180102,1,'2020-01-01 01:01:01'),(490,20260306120000,1,'2020-01-01 01:01:01'),(491,20260311160000,1,'2020-01-01 01:01:01'),(492,20260313173516,1,'2020-01-01 01:01:01'),(493,20260314120000,1,'2020-01-01 01:01:01'),(494,20260316000001,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( @@ -2183,6 +2183,7 @@ CREATE TABLE `policies` ( `conditional_access_enabled` tinyint unsigned NOT NULL DEFAULT '0', `type` enum('dynamic','patch') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'dynamic', `patch_software_title_id` int unsigned DEFAULT NULL, + `needs_full_membership_cleanup` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `idx_policies_checksum` (`checksum`), UNIQUE KEY `idx_team_id_patch_software_title_id` (`team_id`,`patch_software_title_id`),