Class: ActivePartition::PartitionManagers::TimeRange
- Inherits:
-
Object
- Object
- ActivePartition::PartitionManagers::TimeRange
- Defined in:
- lib/active_partition/partition_managers/time_range.rb
Instance Method Summary collapse
-
#active_partitions_cover?(value) ⇒ Boolean
Checks if the active partitions cover the given value.
-
#active_ranges ⇒ Array
Retrieves the active ranges from the partition adapter.
-
#build_partition_name(from, to) ⇒ String
Builds a partition name based on the given time range.
-
#create_partition(from, to) ⇒ Range
Creates a new partition for the table based on the specified time range.
-
#initialize(partition_adapter, table_name, partition_start_from = nil) ⇒ TimeRange
constructor
A new instance of TimeRange.
-
#latest_coverage_at ⇒ Time
Returns the latest coverage time for the partition.
-
#latest_partition_coverage_time ⇒ Time
Returns the coverage time of the latest partition.
-
#premake(period = 1.month, number = 3, from = nil) ⇒ void
Creates multiple partitions in the database based on the given period, number, and starting time.
-
#prepare_partition(partitioned_value, period) ⇒ void
Prepares a partition for the given partitioned value and period.
-
#reload_active_ranges(partition_names) ⇒ Array<Range>
Reloads the active ranges based on the given partition names.
-
#remove_partitions(prunable_tables) ⇒ void
Removes the specified partitions from the database.
-
#retain(period = 1.months, number = 12, from = Time.current.utc) ⇒ void
Retains a specified number of partition tables older than a given period.
- #retain_by_partition_count(retain_number) ⇒ Object
- #retain_by_time(prune_time) ⇒ Object
Constructor Details
#initialize(partition_adapter, table_name, partition_start_from = nil) ⇒ TimeRange
Returns a new instance of TimeRange.
5 6 7 8 9 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 5 def initialize(partition_adapter, table_name, partition_start_from = nil) @partition_adapter = partition_adapter @table_name = table_name @partition_start_from = partition_start_from end |
Instance Method Details
#active_partitions_cover?(value) ⇒ Boolean
Checks if the active partitions cover the given value.
40 41 42 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 40 def active_partitions_cover?(value) active_ranges.any? { |range| range.cover? value.utc } end |
#active_ranges ⇒ Array
Retrieves the active ranges from the partition adapter.
The active ranges are cached in an instance variable ‘@active_ranges` to improve performance. If the `@active_ranges` variable is `nil`, the method calls the `reload_active_ranges` method with the result of `@partition_adapter.get_all_supported_partition_tables` as the argument.
18 19 20 21 22 23 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 18 def active_ranges # in test environment, the partition table is not actually created. Therefore, return empty array return [] if defined?(Rails) && Rails.env.test? @active_ranges ||= reload_active_ranges(@partition_adapter.get_all_supported_partition_tables) end |
#build_partition_name(from, to) ⇒ String
Builds a partition name based on the given time range.
84 85 86 87 88 89 90 91 92 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 84 def build_partition_name(from, to) unix_from = from.utc.to_i unix_to = to.utc.to_i # It's easier to manage when having readable part in the name readable_from = from.utc.strftime("%y%m%d_%H") "#{@table_name}_p_#{readable_from}_#{unix_from}_#{unix_to}" end |
#create_partition(from, to) ⇒ Range
Creates a new partition for the table based on the specified time range.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 99 def create_partition(from, to) from = from.utc to = to.utc partition_name = build_partition_name(from, to) puts "create partition #{partition_name} from #{from} to #{to}" @partition_adapter.exec_create_partition_by_time_range(partition_name, from, to) reload_active_ranges(@partition_adapter.get_all_supported_partition_tables) # rescue ActiveRecord::StatementInvalid => e # byebug # # When overlapping partition, the message will be like this: # # PG::InvalidObjectDefinition: ERROR: partition "table_name_p_240626_09_1719395833_1719482233" would overlap partition "table_name_p_240627_09_1719481818_1719568218" # # LINE 3: FOR VALUES FROM ('2024-06-26 09:57:13') TO ('2024-06-27 09 # # catchup the floor of the from time to the conflict partition and retry # # handle the floor? what about the ceil? # if e.message.include?("would overlap partition") # overlapped_partition = e.message.split("would overlap partition").last.split("\n").first.delete('"').strip # overlapped_from, overlapped_to = overlapped_partition.split("_").last(2).map { |t| Time.at(t.to_i).utc } # return true if (overlapped_from..overlapped_to).cover?(unix_from..unix_to) # # unix_from < unix_to # # overlapped_from < overlapped_to # # if unix_from < overlapped_from # # overlapped_from = unix_from # if floor_time > unix_from # Rails.logger.warn "Retry create partition for #{unix_from} to #{floor_time}" # create_partition(unix_from, floor_time) # end # end end |
#latest_coverage_at ⇒ Time
Returns the latest coverage time for the partition.
This method memoizes the latest coverage time by caching the result in an instance variable. If the latest coverage time has already been calculated, it will be returned from the cache. Otherwise, it will call the ‘latest_partition_coverage_time` method to calculate the latest coverage time.
51 52 53 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 51 def latest_coverage_at @latest_coverage_at ||= latest_partition_coverage_time end |
#latest_partition_coverage_time ⇒ Time
Returns the coverage time of the latest partition.
If there are no supported partition tables, the coverage time will be the beginning of the current hour in UTC. Otherwise, the coverage time will be extracted from the latest partition table name.
139 140 141 142 143 144 145 146 147 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 139 def latest_partition_coverage_time partition_tables = @partition_adapter.get_all_supported_partition_tables reload_active_ranges(partition_tables) return (@partition_start_from || Time.current.beginning_of_hour).utc if partition_tables.empty? latest_partition_table = partition_tables.sort_by { |p_name| p_name.split("_").last.to_i }.last @latest_coverage_at = Time.at(latest_partition_table.split("_").last.to_i).utc @latest_coverage_at end |
#premake(period = 1.month, number = 3, from = nil) ⇒ void
This method returns an undefined value.
Creates multiple partitions in the database based on the given period, number, and starting time.
156 157 158 159 160 161 162 163 164 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 156 def premake(period = 1.month, number = 3, from = nil) new_latest_coverage_time = (from || Time.current).utc + (period * number) current_coverage_time = from || latest_partition_coverage_time while current_coverage_time < new_latest_coverage_time create_partition(current_coverage_time, current_coverage_time + period) current_coverage_time += period end end |
#prepare_partition(partitioned_value, period) ⇒ void
This method returns an undefined value.
Prepares a partition for the given partitioned value and period.
If the active partitions do not cover the partitioned value, a new partition is created.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 62 def prepare_partition(partitioned_value, period) return if active_partitions_cover?(partitioned_value) # when the latest_coverage_at is too far, the diff can be wrong (because leap years add up to the diff) # therefore, we need to calculate the diff based on the period # diff = (partitioned_value.utc - latest_coverage_at) / period # from_time = latest_coverage_at + (diff.floor * period) # to_time = from_time + period from_time = latest_coverage_at while !(from_time..(from_time + period)).cover?(partitioned_value) do from_time += period * (partitioned_value > from_time ? 1 : -1) end create_partition(from_time, from_time + period) end |
#reload_active_ranges(partition_names) ⇒ Array<Range>
Reloads the active ranges based on the given partition names.
29 30 31 32 33 34 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 29 def reload_active_ranges(partition_names) @active_ranges = partition_names.map do |partition_name| start_at, end_at = partition_name.split("_").last(2).map { |t| Time.at(t.to_i).utc } (start_at...end_at) end end |
#remove_partitions(prunable_tables) ⇒ void
This method returns an undefined value.
Removes the specified partitions from the database.
170 171 172 173 174 175 176 177 178 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 170 def remove_partitions(prunable_tables) table_names = prunable_tables.each do |partition_name| @partition_adapter.detach_partition(partition_name) @partition_adapter.drop_partition(partition_name) end reload_active_ranges(@partition_adapter.get_all_supported_partition_tables) table_names end |
#retain(period = 1.months, number = 12, from = Time.current.utc) ⇒ void
This method returns an undefined value.
Retains a specified number of partition tables older than a given period.
186 187 188 189 190 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 186 def retain(period = 1.months, number = 12, from = Time.current.utc) prune_time = (from - (period * (number + 1))).utc retain_by_time(prune_time) end |
#retain_by_partition_count(retain_number) ⇒ Object
204 205 206 207 208 209 210 211 212 213 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 204 def retain_by_partition_count(retain_number) partition_tables = @partition_adapter.get_all_supported_partition_tables nil if partition_tables.empty? current_partition_name = build_partition_name(Time.current, Time.current + 1.hour) past_partitions = partition_tables.select { |name| name <= current_partition_name }.sort prunable_partitions = past_partitions[.. -(retain_number + 2)] # -1 of current partition and -1 as syntax remove_partitions(prunable_partitions) end |
#retain_by_time(prune_time) ⇒ Object
192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/active_partition/partition_managers/time_range.rb', line 192 def retain_by_time(prune_time) partition_tables = @partition_adapter.get_all_supported_partition_tables return if partition_tables.empty? prunable_tables = partition_tables.select do |name| p_to_time = Time.at(name.split("_").last.to_i).utc p_to_time < prune_time end remove_partitions (prunable_tables) end |