Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions Mindbox.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
473A986D2C91E032005A3B94 /* MonitoringLogsError.json in Resources */ = {isa = PBXBuildFile; fileRef = 473A986C2C91E032005A3B94 /* MonitoringLogsError.json */; };
473A986F2C91E047005A3B94 /* MonitoringLogsTypeError.json in Resources */ = {isa = PBXBuildFile; fileRef = 473A986E2C91E047005A3B94 /* MonitoringLogsTypeError.json */; };
473A98712C91E629005A3B94 /* MonitoringLogsElementsMixedError.json in Resources */ = {isa = PBXBuildFile; fileRef = 473A98702C91E629005A3B94 /* MonitoringLogsElementsMixedError.json */; };
474230DC2E72236500282764 /* TestContainers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474230DB2E72236500282764 /* TestContainers.swift */; };
4747708B2C6B838B00C36FC8 /* SharedInternalMethodsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4747708A2C6B838B00C36FC8 /* SharedInternalMethodsTests.swift */; };
4747708F2C6B93AC00C36FC8 /* MindboxNotificationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4747708E2C6B93AC00C36FC8 /* MindboxNotificationServiceTests.swift */; };
474770912C6B9A7200C36FC8 /* MindboxNotificationContentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474770902C6B9A7200C36FC8 /* MindboxNotificationContentTests.swift */; };
Expand Down Expand Up @@ -161,7 +162,13 @@
4766A8BC2C9332ED002D15A4 /* ConfigABTestsOneElementError.json in Resources */ = {isa = PBXBuildFile; fileRef = 4766A8BB2C9332ED002D15A4 /* ConfigABTestsOneElementError.json */; };
4766A8BE2C933416002D15A4 /* ConfigABTestsOneElementTypeError.json in Resources */ = {isa = PBXBuildFile; fileRef = 4766A8BD2C933416002D15A4 /* ConfigABTestsOneElementTypeError.json */; };
47980DB12E3B96EF0020EB34 /* CheckNotificationsStatusOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47980DB02E3B96EF0020EB34 /* CheckNotificationsStatusOperation.swift */; };
47A220A52E2158A00001507C /* EphermalSQLiteForMBLoggerCoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A220A42E2158A00001507C /* EphermalSQLiteForMBLoggerCoreDataManager.swift */; };
47A220A52E2158A00001507C /* IsolatedMBLoggerCoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A220A42E2158A00001507C /* IsolatedMBLoggerCoreDataManager.swift */; };
47A4FA6E2E7335A200569870 /* LoggerDatabaseLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A4FA6D2E7335A200569870 /* LoggerDatabaseLoader.swift */; };
47A4FA702E73421200569870 /* LoggerDBConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A4FA6F2E73421200569870 /* LoggerDBConfig.swift */; };
47A4FA722E73565400569870 /* LogStoreTrimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A4FA712E73565400569870 /* LogStoreTrimmer.swift */; };
47A4FA742E73569F00569870 /* SQLiteLogicalSizeMeasurer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A4FA732E73569F00569870 /* SQLiteLogicalSizeMeasurer.swift */; };
47A4FA762E735C5200569870 /* LogStoreTrimmerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A4FA752E735C5200569870 /* LogStoreTrimmerTests.swift */; };
47A4FA782E73741700569870 /* LoggerDatabaseLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A4FA772E73741700569870 /* LoggerDatabaseLoaderTests.swift */; };
47B90E2F2C625F9A00BD93E7 /* TestBaseMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B90E2E2C625F9A00BD93E7 /* TestBaseMigrations.swift */; };
47B90E312C626B9300BD93E7 /* TestProtocolMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B90E302C626B9300BD93E7 /* TestProtocolMigrations.swift */; };
47BD5BFB2C578BC600F965C0 /* MigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47BD5BFA2C578BC600F965C0 /* MigrationManager.swift */; };
Expand Down Expand Up @@ -754,6 +761,7 @@
473A986C2C91E032005A3B94 /* MonitoringLogsError.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = MonitoringLogsError.json; sourceTree = "<group>"; };
473A986E2C91E047005A3B94 /* MonitoringLogsTypeError.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = MonitoringLogsTypeError.json; sourceTree = "<group>"; };
473A98702C91E629005A3B94 /* MonitoringLogsElementsMixedError.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = MonitoringLogsElementsMixedError.json; sourceTree = "<group>"; };
474230DB2E72236500282764 /* TestContainers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestContainers.swift; sourceTree = "<group>"; };
4747708A2C6B838B00C36FC8 /* SharedInternalMethodsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedInternalMethodsTests.swift; sourceTree = "<group>"; };
4747708E2C6B93AC00C36FC8 /* MindboxNotificationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindboxNotificationServiceTests.swift; sourceTree = "<group>"; };
474770902C6B9A7200C36FC8 /* MindboxNotificationContentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindboxNotificationContentTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -782,7 +790,13 @@
4766A8BB2C9332ED002D15A4 /* ConfigABTestsOneElementError.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ConfigABTestsOneElementError.json; sourceTree = "<group>"; };
4766A8BD2C933416002D15A4 /* ConfigABTestsOneElementTypeError.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ConfigABTestsOneElementTypeError.json; sourceTree = "<group>"; };
47980DB02E3B96EF0020EB34 /* CheckNotificationsStatusOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckNotificationsStatusOperation.swift; sourceTree = "<group>"; };
47A220A42E2158A00001507C /* EphermalSQLiteForMBLoggerCoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphermalSQLiteForMBLoggerCoreDataManager.swift; sourceTree = "<group>"; };
47A220A42E2158A00001507C /* IsolatedMBLoggerCoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IsolatedMBLoggerCoreDataManager.swift; sourceTree = "<group>"; };
47A4FA6D2E7335A200569870 /* LoggerDatabaseLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerDatabaseLoader.swift; sourceTree = "<group>"; };
47A4FA6F2E73421200569870 /* LoggerDBConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerDBConfig.swift; sourceTree = "<group>"; };
47A4FA712E73565400569870 /* LogStoreTrimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogStoreTrimmer.swift; sourceTree = "<group>"; };
47A4FA732E73569F00569870 /* SQLiteLogicalSizeMeasurer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SQLiteLogicalSizeMeasurer.swift; sourceTree = "<group>"; };
47A4FA752E735C5200569870 /* LogStoreTrimmerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogStoreTrimmerTests.swift; sourceTree = "<group>"; };
47A4FA772E73741700569870 /* LoggerDatabaseLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerDatabaseLoaderTests.swift; sourceTree = "<group>"; };
47B90E2E2C625F9A00BD93E7 /* TestBaseMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestBaseMigrations.swift; sourceTree = "<group>"; };
47B90E302C626B9300BD93E7 /* TestProtocolMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestProtocolMigrations.swift; sourceTree = "<group>"; };
47BD5BFA2C578BC600F965C0 /* MigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1272,6 +1286,7 @@
3333C1A42681D3CF00B60D84 /* MindboxNotificationsTests */,
9BC24E6C28F694CA00C2619C /* SDKVersionProvider */,
A17853BF29AF7E940072578F /* MindboxLogger */,
47D395612E72186600C44CFE /* Frameworks */,
313B233125ADEA0F00A1CB72 /* Products */,
);
sourceTree = "<group>";
Expand Down Expand Up @@ -1801,6 +1816,13 @@
path = MigrationAbstractions;
sourceTree = "<group>";
};
47D395612E72186600C44CFE /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
8400429F2614CDED00CA17C5 /* ClickNotificationManager */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2282,6 +2304,8 @@
A154E332299E10F400F8F074 /* Mocks */,
A154E303299C189300F8F074 /* MBLoggerCoreDataManagerTests.swift */,
A154E32D299E0D8900F8F074 /* SDKLogManagerTests.swift */,
47A4FA772E73741700569870 /* LoggerDatabaseLoaderTests.swift */,
47A4FA752E735C5200569870 /* LogStoreTrimmerTests.swift */,
);
path = MindboxLogger;
sourceTree = "<group>";
Expand All @@ -2290,7 +2314,8 @@
isa = PBXGroup;
children = (
A154E333299E110E00F8F074 /* EventRepositoryMock.swift */,
47A220A42E2158A00001507C /* EphermalSQLiteForMBLoggerCoreDataManager.swift */,
47A220A42E2158A00001507C /* IsolatedMBLoggerCoreDataManager.swift */,
474230DB2E72236500282764 /* TestContainers.swift */,
);
path = Mocks;
sourceTree = "<group>";
Expand Down Expand Up @@ -2350,8 +2375,12 @@
children = (
A154E34D299E5B6D00F8F074 /* CDLogMessage.xcdatamodeld */,
A154E34F299E5B6D00F8F074 /* MBLoggerCoreDataManager.swift */,
47A4FA6F2E73421200569870 /* LoggerDBConfig.swift */,
A154E350299E5B6D00F8F074 /* MBPersistentContainer.swift */,
A154E351299E5B6D00F8F074 /* CDLogMessage+CoreDataClass.swift */,
47A4FA6D2E7335A200569870 /* LoggerDatabaseLoader.swift */,
47A4FA712E73565400569870 /* LogStoreTrimmer.swift */,
47A4FA732E73569F00569870 /* SQLiteLogicalSizeMeasurer.swift */,
);
path = LoggerRepository;
sourceTree = "<group>";
Expand Down Expand Up @@ -3971,6 +4000,7 @@
F32B68882CF83B030088BCDD /* InappConfigurationTests.swift in Sources */,
84FCD3B925CA109E00D1E574 /* MockNetworkFetcher.swift in Sources */,
9B9C9538292111A700BB29DA /* MockUUIDDebugService.swift in Sources */,
47A4FA782E73741700569870 /* LoggerDatabaseLoaderTests.swift in Sources */,
84B625F025C98B1200AB6228 /* ValidatorsTestCase.swift in Sources */,
847F580325C88BBF00147A9A /* HTTPMethod.swift in Sources */,
F351F1C22CE5F23A0053423E /* InappMapperTests.swift in Sources */,
Expand All @@ -3980,6 +4010,7 @@
A17958992978E04600609E91 /* InAppStub.swift in Sources */,
F39B67A72A3C6C6A005C0CCA /* SegmentationServiceTests.swift in Sources */,
476689C72C85B6AE0066BB12 /* ShownInAppsIdsMigrationTests.swift in Sources */,
474230DC2E72236500282764 /* TestContainers.swift in Sources */,
F3D925AB2A120C0F00135C87 /* InAppImageDownloaderMock.swift in Sources */,
F3E3EEA62CFA27EC00AAC91A /* Tag+Extensions.swift in Sources */,
F367301D2B7B8B6A00DD0039 /* NotificationFormatTests.swift in Sources */,
Expand Down Expand Up @@ -4020,13 +4051,14 @@
A17958972978D2B300609E91 /* InAppTargetingCheckerTests.swift in Sources */,
F39B67AE2A3C737F005C0CCA /* ImageDownloadServiceTests.swift in Sources */,
47B90E2F2C625F9A00BD93E7 /* TestBaseMigrations.swift in Sources */,
47A4FA762E735C5200569870 /* LogStoreTrimmerTests.swift in Sources */,
84A0CD5A260B021F004CD91B /* MockFailureNetworkFetcher.swift in Sources */,
475558C32C59300400CDA026 /* MigrationManagerTests.swift in Sources */,
84E1D6A9261D8D54002BF03A /* DatabaseLoaderTest.swift in Sources */,
840C38B825D13A7D00D50183 /* EventGenerator.swift in Sources */,
84CC79A525CAE14200C062BD /* EventRepositoryTestCase.swift in Sources */,
F367301B2B7B6E7600DD0039 /* MindboxPushValidatorTests.swift in Sources */,
47A220A52E2158A00001507C /* EphermalSQLiteForMBLoggerCoreDataManager.swift in Sources */,
47A220A52E2158A00001507C /* IsolatedMBLoggerCoreDataManager.swift in Sources */,
A154E304299C189300F8F074 /* MBLoggerCoreDataManagerTests.swift in Sources */,
F32E537D2C3FDB6D002C7CA0 /* DITestModuleReplaceableTests.swift in Sources */,
840C388525CD169400D50183 /* DatabaseRepositoryTestCase.swift in Sources */,
Expand Down Expand Up @@ -4080,6 +4112,7 @@
buildActionMask = 2147483647;
files = (
A17853E629AF7EE90072578F /* MBLogger.swift in Sources */,
47A4FA6E2E7335A200569870 /* LoggerDatabaseLoader.swift in Sources */,
A17853DC29AF7EDE0072578F /* String+Extensions.swift in Sources */,
F3B2A3A02A4C79E200E2CA25 /* ValidationError.swift in Sources */,
A17853E029AF7EDE0072578F /* URL+Extensions.swift in Sources */,
Expand All @@ -4088,10 +4121,13 @@
A17853EC29AF7EF30072578F /* CDLogMessage+CoreDataClass.swift in Sources */,
F397EEBE2A4489CE00D48CEC /* LoggerErrorModel.swift in Sources */,
A17853D429AF7EBE0072578F /* SDKLogsStatus.swift in Sources */,
47A4FA702E73421200569870 /* LoggerDBConfig.swift in Sources */,
A17853DF29AF7EDE0072578F /* MBLoggerUtilitiesFetcher.swift in Sources */,
F3B2A3982A4C79D900E2CA25 /* MindboxLogger.swift in Sources */,
47A4FA722E73565400569870 /* LogStoreTrimmer.swift in Sources */,
A17853DE29AF7EDE0072578F /* FileManager+Extensions.swift in Sources */,
A17853DA29AF7ED90072578F /* LogLevel.swift in Sources */,
47A4FA742E73569F00569870 /* SQLiteLogicalSizeMeasurer.swift in Sources */,
F3B2A39F2A4C79E200E2CA25 /* UnknownDecodable.swift in Sources */,
A17853EB29AF7EF30072578F /* MBPersistentContainer.swift in Sources */,
A17853D829AF7ED90072578F /* LogWriter.swift in Sources */,
Expand Down
1 change: 0 additions & 1 deletion Mindbox/Mindbox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,6 @@ public class Mindbox: NSObject {
override private init() {
super.init()
self.assembly()
MBLoggerCoreDataManager.shared.setUpAppLifeCycleObservers()
}

func assembly() {
Expand Down
92 changes: 92 additions & 0 deletions MindboxLogger/Shared/LoggerRepository/LogStoreTrimmer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//
// LogStoreTrimmer.swift
// MindboxLogger
//
// Created by Sergei Semko on 9/11/25.
// Copyright © 2025 Mindbox. All rights reserved.
//

import Foundation

protocol Clock {
var now: Date { get }
}

struct SystemClock: Clock {
public init() {}
public var now: Date { Date() }
}

protocol LogStoreTrimming {
/// Attempts to perform a trim operation if the policy allows it.
///
/// - Parameters:
/// - precomputedSizeKB: Optional precomputed database size in kilobytes.
/// If provided, the trimmer will **not** call the measurer.
/// - delete: A callback that must perform deletion given the computed fraction
/// (e.g. delete the oldest `fraction * N` items). May throw.
/// - Returns: `true` if a trim was performed; `false` if skipped (below limit or under cooldown).
/// - Throws: Rethrows any error thrown by `delete`.
@discardableResult
func maybeTrim(precomputedSizeKB: Int?, delete: (Double) throws -> Void) throws -> Bool

/// Computes the fraction of items to delete in order to reach the configured
/// low-water mark.
///
/// The result is clamped to `[minDeleteFraction, maxDeleteFraction]`.
/// Returns `nil` if the current size does not exceed the limit.
func computeTrimFraction(sizeKB: Int, limitKB: Int) -> Double?

/// Resets cooldown so that the next `maybeTrim` call may run immediately.
func resetCooldown()
}

final class LogStoreTrimmer: LogStoreTrimming {
private let config: LoggerDBConfig
private let sizeMeasurer: DatabaseSizeMeasuring
private let clock: Clock

private var cooldownUntil: Date?

init(config: LoggerDBConfig,
sizeMeasurer: DatabaseSizeMeasuring,
clock: Clock) {
self.config = config
self.sizeMeasurer = sizeMeasurer
self.clock = clock
}

convenience init(config: LoggerDBConfig,
sizeMeasurer: DatabaseSizeMeasuring) {
self.init(config: config, sizeMeasurer: sizeMeasurer, clock: SystemClock())
}

func resetCooldown() { cooldownUntil = nil }

func computeTrimFraction(sizeKB: Int, limitKB: Int) -> Double? {
guard sizeKB > limitKB else { return nil }
let targetKB = Int(Double(limitKB) * config.lowWaterRatio)
let raw = Double(sizeKB - targetKB) / Double(max(sizeKB, 1))
let fraction = min(config.maxDeleteFraction, max(config.minDeleteFraction, raw))
return fraction
}

@discardableResult
func maybeTrim(precomputedSizeKB: Int? = nil,
delete: (Double) throws -> Void) rethrows -> Bool {
if let t = cooldownUntil, t > clock.now { return false }
let sizeKB = precomputedSizeKB ?? sizeMeasurer.sizeKB()
guard let fraction = computeTrimFraction(sizeKB: sizeKB, limitKB: config.dbSizeLimitKB) else { return false }
try delete(fraction)
cooldownUntil = clock.now.addingTimeInterval(config.trimCooldownSec)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А почему не наоборот? Хранить последнее время и каждый раз проверять добавляя интервал

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Удобно было сделать deadline "до какого времени нельзя". Но можно и как ты предложил. Пока нигде не аффектят оба варианта.

return true
}
}

#if DEBUG
final class ManualClock: Clock {
var now: Date
init(_ now: Date) { self.now = now }
func advance(_ seconds: TimeInterval) { now = now.addingTimeInterval(seconds) }
}
#endif
29 changes: 29 additions & 0 deletions MindboxLogger/Shared/LoggerRepository/LoggerDBConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// LoggerDBConfig.swift
// MindboxLogger
//
// Created by Sergei Semko on 9/11/25.
// Copyright © 2025 Mindbox. All rights reserved.
//

import Foundation

public struct LoggerDBConfig {
public let dbSizeLimitKB: Int
public let lowWaterRatio: Double
public let minDeleteFraction: Double
public let maxDeleteFraction: Double
public let batchSize: Int
public let writesPerTrimCheck: Int
public let trimCooldownSec: TimeInterval

public static let `default` = LoggerDBConfig(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это просто эвристические параметры?

Copy link
Contributor Author

@justSmK justSmK Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да. Удобно вынести стало из-за их роста и для подстановки в тесты

dbSizeLimitKB: 10_240,
lowWaterRatio: 0.85,
minDeleteFraction: 0.05,
maxDeleteFraction: 0.50,
batchSize: 15,
writesPerTrimCheck: 5,
trimCooldownSec: 10
)
}
Loading