Skip to content

Commit

Permalink
misc(daily_usage): Extract daily_usage history from rake task to a se…
Browse files Browse the repository at this point in the history
…rvice (#2843)

## Context

A rake task to fill the history data of the DailyUsage model was
recently added (#2751)

Since this task is recomputing the usage for all subscriptions of an
organization for a defined number of days, without taking advantage of
the usage cache, it can be very slow. Also, an organization can have a
large number of subscription, making the current linear approach very
inefficient.

## Description

This PR extracts the existing logic from the rake task itself in a
job/service, taking advantage of sidekiq to parralelize the work
  • Loading branch information
vincent-pochet authored Nov 21, 2024
1 parent 519d347 commit 2c97be8
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 50 deletions.
11 changes: 11 additions & 0 deletions app/jobs/daily_usages/fill_history_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module DailyUsages
class FillHistoryJob < ApplicationJob
queue_as 'low_priority'

def perform(subscription:, from_datetime:)
DailyUsages::FillHistoryService.call!(subscription:, from_datetime:)
end
end
end
75 changes: 75 additions & 0 deletions app/services/daily_usages/fill_history_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

module DailyUsages
class FillHistoryService < BaseService
def initialize(subscription:, from_datetime:)
@subscription = subscription
@from_datetime = from_datetime

super
end

def call
previous_daily_usage = nil

(from..to).each do |date|
datetime = date.in_time_zone(subscription.customer.applicable_timezone).beginning_of_day.utc

next if date == Time.zone.today &&
DailyUsage.refreshed_at_in_timezone(datetime).where(subscription_id: subscription.id).exists?

Timecop.freeze(datetime + 5.minutes) do
usage = Invoices::CustomerUsageService.call(
customer: subscription.customer,
subscription: subscription,
apply_taxes: false,
with_cache: false,
max_to_datetime: datetime
).raise_if_error!.usage

if previous_daily_usage.present? && previous_daily_usage.from_datetime != usage.from_datetime
# NOTE: A new billing period was started, the diff should contains the complete current usage
previous_daily_usage = nil
end

daily_usage = DailyUsage.new(
organization:,
customer: subscription.customer,
subscription:,
external_subscription_id: subscription.external_id,
usage: ::V1::Customers::UsageSerializer.new(usage, includes: %i[charges_usage]).serialize,
from_datetime: usage.from_datetime,
to_datetime: usage.to_datetime,
refreshed_at: datetime
)

daily_usage.usage_diff = DailyUsages::ComputeDiffService
.call(daily_usage:, previous_daily_usage:)
.raise_if_error!
.usage_diff

daily_usage.save!

previous_daily_usage = daily_usage
end
end

result
end

attr_reader :subscription, :from_datetime
delegate :organization, to: :subscription

def from
return @from if defined?(@from)

@from = subscription.started_at.to_date
@from = from_datetime.to_date if @from < from_datetime
@from
end

def to
@to ||= (subscription.terminated_at || Time.current).to_date
end
end
end
51 changes: 1 addition & 50 deletions lib/tasks/daily_usages.rake
Original file line number Diff line number Diff line change
Expand Up @@ -19,56 +19,7 @@ namespace :daily_usages do
.includes(customer: :organization)

subscriptions.find_each do |subscription|
from = subscription.started_at.to_date
if from < days_ago
from = days_ago.to_date
end

to = (subscription.terminated_at || Time.current).to_date

previous_daily_usage = nil

(from..to).each do |date|
datetime = date.in_time_zone(subscription.customer.applicable_timezone).beginning_of_day.utc

next if date == Date.today &&
DailyUsage.refreshed_at_in_timezone(datetime).where(subscription_id: subscription.id).exists?

Timecop.freeze(datetime + 5.minutes) do
usage = Invoices::CustomerUsageService.call(
customer: subscription.customer,
subscription: subscription,
apply_taxes: false,
with_cache: false,
max_to_datetime: datetime
).raise_if_error!.usage

if previous_daily_usage.present? && previous_daily_usage.from_datetime != usage.from_datetime
# NOTE: A new billing period was started, the diff should contains the complete current usage
previous_daily_usage = nil
end

daily_usage = DailyUsage.new(
organization:,
customer: subscription.customer,
subscription:,
external_subscription_id: subscription.external_id,
usage: ::V1::Customers::UsageSerializer.new(usage, includes: %i[charges_usage]).serialize,
from_datetime: usage.from_datetime,
to_datetime: usage.to_datetime,
refreshed_at: datetime
)

daily_usage.usage_diff = DailyUsages::ComputeDiffService
.call(daily_usage:, previous_daily_usage:)
.raise_if_error!
.usage_diff

daily_usage.save!

previous_daily_usage = daily_usage
end
end
DailyUsages::FillHistoryJob.perform_later(subscription:, from_datetime: days_ago)
end
end
end

0 comments on commit 2c97be8

Please sign in to comment.