nwaku/waku/waku_archive/driver/postgres_driver/partitions_manager.nim
Ivan FB ab4b5b27bd
postgres_driver: better sync lock in partition creation (#2809)
With the original approach it happened cases where one connection
acquired the lock and other connection tried to release the same lock,
causing an unrecoverable failure which made the node to end.
2024-06-14 10:07:41 +02:00

137 lines
4.7 KiB
Nim

## This module is aimed to handle the creation and truncation of partition tables
## in order to limit the space occupied in disk by the database.
##
## The created partitions are referenced by the 'storedAt' field.
##
import std/[deques, times]
import chronos, chronicles
import ../../../waku_core/time
logScope:
topics = "waku archive partitions_manager"
## The time range has seconds resolution
type TimeRange* = tuple[beginning: int64, `end`: int64]
type
Partition = object
name: string
timeRange: TimeRange
PartitionManager* = ref object
partitions: Deque[Partition]
# FIFO of partition table names. The first is the oldest partition
proc new*(T: type PartitionManager): T =
return PartitionManager()
proc getPartitionFromDateTime*(
self: PartitionManager, targetMoment: int64
): Result[Partition, string] =
## Returns the partition name that might store a message containing the passed timestamp.
## In order words, it simply returns the partition name which contains the given timestamp.
## targetMoment - represents the time of interest, measured in seconds since epoch.
if self.partitions.len == 0:
return err("There are no partitions")
for partition in self.partitions:
let timeRange = partition.timeRange
let beginning = timeRange.beginning
let `end` = timeRange.`end`
if beginning <= targetMoment and targetMoment < `end`:
return ok(partition)
return err("Couldn't find a partition table for given time: " & $targetMoment)
proc getNewestPartition*(self: PartitionManager): Result[Partition, string] =
if self.partitions.len == 0:
return err("there are no partitions allocated")
let newestPartition = self.partitions.peekLast
return ok(newestPartition)
proc getOldestPartition*(self: PartitionManager): Result[Partition, string] =
if self.partitions.len == 0:
return err("there are no partitions allocated")
let oldestPartition = self.partitions.peekFirst
return ok(oldestPartition)
proc addPartitionInfo*(
self: PartitionManager, partitionName: string, beginning: int64, `end`: int64
) =
## The given partition range has seconds resolution.
## We just store information of the new added partition merely to keep track of it.
let partitionInfo = Partition(name: partitionName, timeRange: (beginning, `end`))
trace "Adding partition info"
self.partitions.addLast(partitionInfo)
proc clearPartitionInfo*(self: PartitionManager) =
self.partitions.clear()
proc removeOldestPartitionName*(self: PartitionManager) =
## Simply removed the partition from the tracked/known partitions queue.
## Just remove it and ignore it.
discard self.partitions.popFirst()
proc isEmpty*(self: PartitionManager): bool =
return self.partitions.len == 0
proc getLastMoment*(partition: Partition): int64 =
## Considering the time range covered by the partition, this
## returns the `end` time (number of seconds since epoch) of such range.
let lastTimeInSec = partition.timeRange.`end`
return lastTimeInSec
proc getPartitionStartTimeInNanosec*(partition: Partition): int64 =
return partition.timeRange.beginning * 1_000_000_000
proc containsMoment*(partition: Partition, time: int64): bool =
## Returns true if the given moment is contained within the partition window,
## 'false' otherwise.
## time - number of seconds since epoch
if partition.timeRange.beginning <= time and time < partition.timeRange.`end`:
return true
return false
proc calcEndPartitionTime*(startTime: Timestamp): Timestamp =
## Each partition has an "startTime" and "end" time. This proc calculates the "end" time so that
## it precisely matches the next o'clock time.
## This considers that the partitions should be 1 hour long.
## For example, if `startTime` == 14:28 , then the returned end time should be 15:00.
## Notice both `startTime` and returned time are in seconds since Epoch.
##
## startTime - seconds from Epoch that represents the partition start time
let startDateTime: DateTime = times.fromUnix(startTime).utc()
let maxPartitionDuration: times.Duration = times.initDuration(hours = 1)
## Max time range covered by each parition
## It is max because we aim to make the partition times synced to
## o'clock hours. i.e. each partition edge will have min == sec == nanosec == 0
let endDateTime = startDateTime + maxPartitionDuration
let endDateTimeOClock = times.dateTime(
year = endDateTime.year,
month = endDateTime.month,
monthday = endDateTime.monthday,
hour = endDateTime.hour,
minute = 0,
second = 0,
nanosecond = 0,
zone = utc(),
)
return Timestamp(endDateTimeOClock.toTime().toUnix())
proc getName*(partition: Partition): string =
return partition.name
func `==`*(a, b: Partition): bool {.inline.} =
return a.name == b.name