smart_message 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/Gemfile.lock +1 -1
- data/README.md +101 -3
- data/docs/architecture.md +139 -69
- data/docs/message_deduplication.md +488 -0
- data/examples/10_message_deduplication.rb +209 -0
- data/lib/smart_message/base.rb +2 -0
- data/lib/smart_message/ddq/base.rb +71 -0
- data/lib/smart_message/ddq/memory.rb +109 -0
- data/lib/smart_message/ddq/redis.rb +168 -0
- data/lib/smart_message/ddq.rb +31 -0
- data/lib/smart_message/deduplication.rb +174 -0
- data/lib/smart_message/dispatcher.rb +175 -18
- data/lib/smart_message/subscription.rb +10 -7
- data/lib/smart_message/version.rb +1 -1
- metadata +8 -1
@@ -0,0 +1,71 @@
|
|
1
|
+
# lib/smart_message/ddq/base.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module SmartMessage
|
6
|
+
module DDQ
|
7
|
+
# Base class for Deduplication Queue implementations
|
8
|
+
#
|
9
|
+
# Defines the interface that all DDQ storage backends must implement.
|
10
|
+
# Provides circular queue semantics with O(1) lookup performance.
|
11
|
+
class Base
|
12
|
+
attr_reader :size, :logger
|
13
|
+
|
14
|
+
def initialize(size)
|
15
|
+
@size = size
|
16
|
+
@logger = SmartMessage::Logger.default
|
17
|
+
validate_size!
|
18
|
+
end
|
19
|
+
|
20
|
+
# Check if a UUID exists in the queue
|
21
|
+
# @param uuid [String] The UUID to check
|
22
|
+
# @return [Boolean] true if UUID exists, false otherwise
|
23
|
+
def contains?(uuid)
|
24
|
+
raise NotImplementedError, "Subclasses must implement #contains?"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add a UUID to the queue (removes oldest if full)
|
28
|
+
# @param uuid [String] The UUID to add
|
29
|
+
# @return [void]
|
30
|
+
def add(uuid)
|
31
|
+
raise NotImplementedError, "Subclasses must implement #add"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get current queue statistics
|
35
|
+
# @return [Hash] Statistics about the queue
|
36
|
+
def stats
|
37
|
+
{
|
38
|
+
size: @size,
|
39
|
+
storage_type: storage_type,
|
40
|
+
implementation: self.class.name
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Clear all entries from the queue
|
45
|
+
# @return [void]
|
46
|
+
def clear
|
47
|
+
raise NotImplementedError, "Subclasses must implement #clear"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get the storage type identifier
|
51
|
+
# @return [Symbol] Storage type (:memory, :redis, etc.)
|
52
|
+
def storage_type
|
53
|
+
raise NotImplementedError, "Subclasses must implement #storage_type"
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def validate_size!
|
59
|
+
unless @size.is_a?(Integer) && @size > 0
|
60
|
+
raise ArgumentError, "DDQ size must be a positive integer, got: #{@size.inspect}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def validate_uuid!(uuid)
|
65
|
+
unless uuid.is_a?(String) && !uuid.empty?
|
66
|
+
raise ArgumentError, "UUID must be a non-empty string, got: #{uuid.inspect}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# lib/smart_message/ddq/memory.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'set'
|
6
|
+
require_relative 'base'
|
7
|
+
|
8
|
+
module SmartMessage
|
9
|
+
module DDQ
|
10
|
+
# Memory-based Deduplication Queue implementation
|
11
|
+
#
|
12
|
+
# Uses a hybrid approach with Array for circular queue behavior
|
13
|
+
# and Set for O(1) lookup performance. Thread-safe with Mutex protection.
|
14
|
+
class Memory < Base
|
15
|
+
def initialize(size)
|
16
|
+
super(size)
|
17
|
+
@circular_array = Array.new(@size)
|
18
|
+
@lookup_set = Set.new
|
19
|
+
@index = 0
|
20
|
+
@mutex = Mutex.new
|
21
|
+
@count = 0
|
22
|
+
|
23
|
+
logger.debug { "[SmartMessage::DDQ::Memory] Initialized with size: #{@size}" }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Check if a UUID exists in the queue (O(1) operation)
|
27
|
+
# @param uuid [String] The UUID to check
|
28
|
+
# @return [Boolean] true if UUID exists, false otherwise
|
29
|
+
def contains?(uuid)
|
30
|
+
validate_uuid!(uuid)
|
31
|
+
|
32
|
+
@mutex.synchronize do
|
33
|
+
@lookup_set.include?(uuid)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add a UUID to the queue, removing oldest if full (O(1) operation)
|
38
|
+
# @param uuid [String] The UUID to add
|
39
|
+
# @return [void]
|
40
|
+
def add(uuid)
|
41
|
+
validate_uuid!(uuid)
|
42
|
+
|
43
|
+
@mutex.synchronize do
|
44
|
+
# Don't add if already exists
|
45
|
+
return if @lookup_set.include?(uuid)
|
46
|
+
|
47
|
+
# Remove old entry if slot is occupied
|
48
|
+
old_uuid = @circular_array[@index]
|
49
|
+
if old_uuid
|
50
|
+
@lookup_set.delete(old_uuid)
|
51
|
+
logger.debug { "[SmartMessage::DDQ::Memory] Evicted UUID: #{old_uuid}" }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Add new entry
|
55
|
+
@circular_array[@index] = uuid
|
56
|
+
@lookup_set.add(uuid)
|
57
|
+
@index = (@index + 1) % @size
|
58
|
+
@count = [@count + 1, @size].min
|
59
|
+
|
60
|
+
logger.debug { "[SmartMessage::DDQ::Memory] Added UUID: #{uuid}, count: #{@count}" }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get current queue statistics
|
65
|
+
# @return [Hash] Statistics about the queue
|
66
|
+
def stats
|
67
|
+
@mutex.synchronize do
|
68
|
+
super.merge(
|
69
|
+
current_count: @count,
|
70
|
+
utilization: (@count.to_f / @size * 100).round(2),
|
71
|
+
next_index: @index
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Clear all entries from the queue
|
77
|
+
# @return [void]
|
78
|
+
def clear
|
79
|
+
@mutex.synchronize do
|
80
|
+
@circular_array = Array.new(@size)
|
81
|
+
@lookup_set.clear
|
82
|
+
@index = 0
|
83
|
+
@count = 0
|
84
|
+
|
85
|
+
logger.debug { "[SmartMessage::DDQ::Memory] Cleared all entries" }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Get the storage type identifier
|
90
|
+
# @return [Symbol] Storage type
|
91
|
+
def storage_type
|
92
|
+
:memory
|
93
|
+
end
|
94
|
+
|
95
|
+
# Get current entries (for debugging/testing)
|
96
|
+
# @return [Array<String>] Current UUIDs in insertion order
|
97
|
+
def entries
|
98
|
+
@mutex.synchronize do
|
99
|
+
result = []
|
100
|
+
@count.times do |i|
|
101
|
+
idx = (@index - @count + i) % @size
|
102
|
+
result << @circular_array[idx] if @circular_array[idx]
|
103
|
+
end
|
104
|
+
result
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# lib/smart_message/ddq/redis.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require_relative 'base'
|
6
|
+
|
7
|
+
module SmartMessage
|
8
|
+
module DDQ
|
9
|
+
# Redis-based Deduplication Queue implementation
|
10
|
+
#
|
11
|
+
# Uses Redis SET for O(1) lookup and LIST for circular queue behavior.
|
12
|
+
# Supports distributed deduplication across multiple processes/servers.
|
13
|
+
class Redis < Base
|
14
|
+
attr_reader :redis, :key_prefix, :ttl
|
15
|
+
|
16
|
+
def initialize(size, options = {})
|
17
|
+
super(size)
|
18
|
+
@redis = options[:redis] || default_redis_connection
|
19
|
+
@key_prefix = options[:key_prefix] || 'smart_message:ddq'
|
20
|
+
@ttl = options[:ttl] || 3600 # 1 hour default TTL
|
21
|
+
|
22
|
+
validate_redis_connection!
|
23
|
+
|
24
|
+
logger.debug { "[SmartMessage::DDQ::Redis] Initialized with size: #{@size}, TTL: #{@ttl}s" }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Check if a UUID exists in the queue (O(1) operation)
|
28
|
+
# @param uuid [String] The UUID to check
|
29
|
+
# @return [Boolean] true if UUID exists, false otherwise
|
30
|
+
def contains?(uuid)
|
31
|
+
validate_uuid!(uuid)
|
32
|
+
|
33
|
+
result = @redis.sismember(set_key, uuid)
|
34
|
+
logger.debug { "[SmartMessage::DDQ::Redis] UUID #{uuid} exists: #{result}" }
|
35
|
+
result
|
36
|
+
rescue ::Redis::BaseError => e
|
37
|
+
logger.error { "[SmartMessage::DDQ::Redis] Error checking UUID #{uuid}: #{e.message}" }
|
38
|
+
# Fail open - allow processing if Redis is down
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add a UUID to the queue, removing oldest if full (O(1) amortized)
|
43
|
+
# @param uuid [String] The UUID to add
|
44
|
+
# @return [void]
|
45
|
+
def add(uuid)
|
46
|
+
validate_uuid!(uuid)
|
47
|
+
|
48
|
+
# Check if UUID already exists first (avoid unnecessary work)
|
49
|
+
return if @redis.sismember(set_key, uuid)
|
50
|
+
|
51
|
+
# Use Redis transaction for atomicity
|
52
|
+
@redis.multi do |pipeline|
|
53
|
+
# Add to set for O(1) lookup
|
54
|
+
pipeline.sadd(set_key, uuid)
|
55
|
+
|
56
|
+
# Add to list for ordering/eviction
|
57
|
+
pipeline.lpush(list_key, uuid)
|
58
|
+
|
59
|
+
# Trim list to maintain size (removes oldest)
|
60
|
+
pipeline.ltrim(list_key, 0, @size - 1)
|
61
|
+
|
62
|
+
# Set TTL on both keys
|
63
|
+
pipeline.expire(set_key, @ttl)
|
64
|
+
pipeline.expire(list_key, @ttl)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get and remove evicted items from set (outside transaction)
|
68
|
+
list_length = @redis.llen(list_key)
|
69
|
+
if list_length > @size
|
70
|
+
evicted_uuids = @redis.lrange(list_key, @size, -1)
|
71
|
+
evicted_uuids.each do |evicted_uuid|
|
72
|
+
@redis.srem(set_key, evicted_uuid)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
logger.debug { "[SmartMessage::DDQ::Redis] Added UUID: #{uuid}" }
|
77
|
+
rescue ::Redis::BaseError => e
|
78
|
+
logger.error { "[SmartMessage::DDQ::Redis] Error adding UUID #{uuid}: #{e.message}" }
|
79
|
+
# Don't raise - deduplication failure shouldn't break message processing
|
80
|
+
end
|
81
|
+
|
82
|
+
# Get current queue statistics
|
83
|
+
# @return [Hash] Statistics about the queue
|
84
|
+
def stats
|
85
|
+
set_size = @redis.scard(set_key)
|
86
|
+
list_size = @redis.llen(list_key)
|
87
|
+
|
88
|
+
super.merge(
|
89
|
+
current_count: set_size,
|
90
|
+
list_count: list_size,
|
91
|
+
utilization: (set_size.to_f / @size * 100).round(2),
|
92
|
+
ttl_remaining: @redis.ttl(set_key),
|
93
|
+
redis_info: {
|
94
|
+
host: redis_host,
|
95
|
+
port: redis_port,
|
96
|
+
db: redis_db
|
97
|
+
}
|
98
|
+
)
|
99
|
+
rescue ::Redis::BaseError => e
|
100
|
+
logger.error { "[SmartMessage::DDQ::Redis] Error getting stats: #{e.message}" }
|
101
|
+
super.merge(error: e.message)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Clear all entries from the queue
|
105
|
+
# @return [void]
|
106
|
+
def clear
|
107
|
+
@redis.multi do |pipeline|
|
108
|
+
pipeline.del(set_key)
|
109
|
+
pipeline.del(list_key)
|
110
|
+
end
|
111
|
+
|
112
|
+
logger.debug { "[SmartMessage::DDQ::Redis] Cleared all entries" }
|
113
|
+
rescue ::Redis::BaseError => e
|
114
|
+
logger.error { "[SmartMessage::DDQ::Redis] Error clearing queue: #{e.message}" }
|
115
|
+
end
|
116
|
+
|
117
|
+
# Get the storage type identifier
|
118
|
+
# @return [Symbol] Storage type
|
119
|
+
def storage_type
|
120
|
+
:redis
|
121
|
+
end
|
122
|
+
|
123
|
+
# Get current entries (for debugging/testing)
|
124
|
+
# @return [Array<String>] Current UUIDs in insertion order (newest first)
|
125
|
+
def entries
|
126
|
+
@redis.lrange(list_key, 0, -1)
|
127
|
+
rescue ::Redis::BaseError => e
|
128
|
+
logger.error { "[SmartMessage::DDQ::Redis] Error getting entries: #{e.message}" }
|
129
|
+
[]
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def set_key
|
135
|
+
"#{@key_prefix}:set"
|
136
|
+
end
|
137
|
+
|
138
|
+
def list_key
|
139
|
+
"#{@key_prefix}:list"
|
140
|
+
end
|
141
|
+
|
142
|
+
def default_redis_connection
|
143
|
+
require 'redis'
|
144
|
+
::Redis.new
|
145
|
+
rescue LoadError
|
146
|
+
raise LoadError, "Redis gem not available. Install with: gem install redis"
|
147
|
+
end
|
148
|
+
|
149
|
+
def validate_redis_connection!
|
150
|
+
@redis.ping
|
151
|
+
rescue ::Redis::BaseError => e
|
152
|
+
raise ConnectionError, "Failed to connect to Redis: #{e.message}"
|
153
|
+
end
|
154
|
+
|
155
|
+
def redis_host
|
156
|
+
@redis.connection[:host] rescue 'unknown'
|
157
|
+
end
|
158
|
+
|
159
|
+
def redis_port
|
160
|
+
@redis.connection[:port] rescue 'unknown'
|
161
|
+
end
|
162
|
+
|
163
|
+
def redis_db
|
164
|
+
@redis.connection[:db] rescue 'unknown'
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# lib/smart_message/ddq.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require_relative 'ddq/base'
|
6
|
+
require_relative 'ddq/memory'
|
7
|
+
require_relative 'ddq/redis'
|
8
|
+
|
9
|
+
module SmartMessage
|
10
|
+
# Deduplication Queue (DDQ) for preventing duplicate message processing
|
11
|
+
#
|
12
|
+
# Provides a circular queue with O(1) lookup performance for detecting
|
13
|
+
# duplicate messages based on UUID. Supports both memory and Redis storage.
|
14
|
+
module DDQ
|
15
|
+
# Default configuration
|
16
|
+
DEFAULT_SIZE = 100
|
17
|
+
DEFAULT_STORAGE = :memory
|
18
|
+
|
19
|
+
# Create a DDQ instance based on storage type
|
20
|
+
def self.create(storage_type, size = DEFAULT_SIZE, options = {})
|
21
|
+
case storage_type.to_sym
|
22
|
+
when :memory
|
23
|
+
Memory.new(size)
|
24
|
+
when :redis
|
25
|
+
Redis.new(size, options)
|
26
|
+
else
|
27
|
+
raise ArgumentError, "Unknown DDQ storage type: #{storage_type}. Supported types: :memory, :redis"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# lib/smart_message/deduplication.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require_relative 'ddq'
|
6
|
+
|
7
|
+
module SmartMessage
|
8
|
+
# Deduplication functionality for message classes
|
9
|
+
#
|
10
|
+
# Provides class-level configuration and instance-level deduplication
|
11
|
+
# checking using a Deduplication Queue (DDQ).
|
12
|
+
module Deduplication
|
13
|
+
def self.included(base)
|
14
|
+
base.extend(ClassMethods)
|
15
|
+
base.instance_variable_set(:@ddq_size, DDQ::DEFAULT_SIZE)
|
16
|
+
base.instance_variable_set(:@ddq_storage, DDQ::DEFAULT_STORAGE)
|
17
|
+
base.instance_variable_set(:@ddq_options, {})
|
18
|
+
base.instance_variable_set(:@ddq_enabled, false)
|
19
|
+
base.instance_variable_set(:@ddq_instance, nil)
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# Configure DDQ size for this message class
|
24
|
+
# @param size [Integer] Maximum number of UUIDs to track
|
25
|
+
def ddq_size(size)
|
26
|
+
unless size.is_a?(Integer) && size > 0
|
27
|
+
raise ArgumentError, "DDQ size must be a positive integer, got: #{size.inspect}"
|
28
|
+
end
|
29
|
+
@ddq_size = size
|
30
|
+
reset_ddq_instance! if ddq_enabled?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Configure DDQ storage type for this message class
|
34
|
+
# @param storage [Symbol] Storage type (:memory or :redis)
|
35
|
+
# @param options [Hash] Additional options for the storage backend
|
36
|
+
def ddq_storage(storage, **options)
|
37
|
+
unless [:memory, :redis].include?(storage.to_sym)
|
38
|
+
raise ArgumentError, "DDQ storage must be :memory or :redis, got: #{storage.inspect}"
|
39
|
+
end
|
40
|
+
@ddq_storage = storage.to_sym
|
41
|
+
@ddq_options = options
|
42
|
+
reset_ddq_instance! if ddq_enabled?
|
43
|
+
end
|
44
|
+
|
45
|
+
# Enable deduplication for this message class
|
46
|
+
def enable_deduplication!
|
47
|
+
@ddq_enabled = true
|
48
|
+
get_ddq_instance # Initialize the DDQ
|
49
|
+
end
|
50
|
+
|
51
|
+
# Disable deduplication for this message class
|
52
|
+
def disable_deduplication!
|
53
|
+
@ddq_enabled = false
|
54
|
+
@ddq_instance = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# Check if deduplication is enabled
|
58
|
+
# @return [Boolean] true if DDQ is enabled
|
59
|
+
def ddq_enabled?
|
60
|
+
!!@ddq_enabled
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get the current DDQ configuration
|
64
|
+
# @return [Hash] Current DDQ configuration
|
65
|
+
def ddq_config
|
66
|
+
{
|
67
|
+
enabled: ddq_enabled?,
|
68
|
+
size: @ddq_size,
|
69
|
+
storage: @ddq_storage,
|
70
|
+
options: @ddq_options
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get DDQ statistics
|
75
|
+
# @return [Hash] DDQ statistics
|
76
|
+
def ddq_stats
|
77
|
+
return { enabled: false } unless ddq_enabled?
|
78
|
+
|
79
|
+
ddq = get_ddq_instance
|
80
|
+
if ddq
|
81
|
+
ddq.stats.merge(enabled: true)
|
82
|
+
else
|
83
|
+
{ enabled: true, error: "DDQ instance not available" }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Clear the DDQ
|
88
|
+
def clear_ddq!
|
89
|
+
return unless ddq_enabled?
|
90
|
+
|
91
|
+
ddq = get_ddq_instance
|
92
|
+
ddq&.clear
|
93
|
+
end
|
94
|
+
|
95
|
+
# Check if a UUID is a duplicate (for external use)
|
96
|
+
# @param uuid [String] The UUID to check
|
97
|
+
# @return [Boolean] true if UUID is a duplicate
|
98
|
+
def duplicate_uuid?(uuid)
|
99
|
+
return false unless ddq_enabled?
|
100
|
+
|
101
|
+
ddq = get_ddq_instance
|
102
|
+
ddq ? ddq.contains?(uuid) : false
|
103
|
+
end
|
104
|
+
|
105
|
+
# Get the DDQ instance (exposed for testing)
|
106
|
+
def get_ddq_instance
|
107
|
+
return nil unless ddq_enabled?
|
108
|
+
|
109
|
+
# Return cached instance if available and configuration hasn't changed
|
110
|
+
if @ddq_instance
|
111
|
+
return @ddq_instance
|
112
|
+
end
|
113
|
+
|
114
|
+
# Create new DDQ instance
|
115
|
+
size = @ddq_size
|
116
|
+
storage = @ddq_storage
|
117
|
+
options = @ddq_options
|
118
|
+
|
119
|
+
ddq = DDQ.create(storage, size, options)
|
120
|
+
@ddq_instance = ddq
|
121
|
+
|
122
|
+
SmartMessage::Logger.default.debug do
|
123
|
+
"[SmartMessage::Deduplication] Created DDQ for #{self.name}: " \
|
124
|
+
"storage=#{storage}, size=#{size}, options=#{options}"
|
125
|
+
end
|
126
|
+
|
127
|
+
ddq
|
128
|
+
rescue => e
|
129
|
+
SmartMessage::Logger.default.error do
|
130
|
+
"[SmartMessage::Deduplication] Failed to create DDQ for #{self.name}: #{e.message}"
|
131
|
+
end
|
132
|
+
nil
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def reset_ddq_instance!
|
138
|
+
@ddq_instance = nil
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Instance methods for deduplication checking
|
143
|
+
|
144
|
+
# Check if this message is a duplicate based on its UUID
|
145
|
+
# @return [Boolean] true if this message UUID has been seen before
|
146
|
+
def duplicate?
|
147
|
+
return false unless self.class.ddq_enabled?
|
148
|
+
return false unless uuid
|
149
|
+
|
150
|
+
self.class.duplicate_uuid?(uuid)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Mark this message as processed (add UUID to DDQ)
|
154
|
+
# @return [void]
|
155
|
+
def mark_as_processed!
|
156
|
+
return unless self.class.ddq_enabled?
|
157
|
+
return unless uuid
|
158
|
+
|
159
|
+
ddq = self.class.send(:get_ddq_instance)
|
160
|
+
if ddq
|
161
|
+
ddq.add(uuid)
|
162
|
+
SmartMessage::Logger.default.debug do
|
163
|
+
"[SmartMessage::Deduplication] Marked UUID as processed: #{uuid}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Get the message UUID
|
169
|
+
# @return [String, nil] The message UUID
|
170
|
+
def uuid
|
171
|
+
_sm_header&.uuid
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|