Rest of the usage record logic depends on product naming and requires some modification on the DB side, which will be integrated with separate commit. This commit introduces billing rates, which will be used as catalog for all pricing calculations and invoices. Each place that needs to calculate a price will be able to fetch unit price of particular resource from this table, and then calculate the total price as unit_price * usage_amount * usage_duration. For example, for VMs, if the a customer uses 4 cores from c5a family for 1000 minutes in hetzner-fsn1 region, total price would be 0.000171296 * 4 * 1000. Unit of unit_price for VMs is minute, but each product can determine its own unit. For disks, it could be GB or for a serverless product it could be number of requests. It is possible to add human readable unit column so eliminate potential confusion, but I'm leaving that out for now. We can add if we see a need for that. Initially, I started to build more general solution that would satisfy the needs of many future products, but that proved itself quite challenging as there are lots of unknown unknowns. Instead, I decided to build much more simple and flexible model that can be extended as we learn more about the product we are building. Current model is quite flexible and allows numerous potential expansion paths. For example, reserved instance pricing can be implemented easily by adding a column. Similarly price updates can be done quite easily by adding two new columns; effective_since and effective_until. Then it is matter of ensuring invoice generation and price calculator honours those dates. Alternative would be generating new billing rates and depreciating the old ones, but updating billing rate references everywhere requires manual database surgery and is open to operator mistakes.
25 lines
1.1 KiB
Ruby
25 lines
1.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
Sequel.migration do
|
|
up do
|
|
create_table(:billing_rate) do
|
|
column :id, :uuid, primary_key: true, default: nil
|
|
column :resource_type, :text, collate: '"C"', null: false
|
|
column :resource_family, :text, collate: '"C"', null: false
|
|
column :location, :text, collate: '"C"', null: false
|
|
column :unit_price, :numeric, null: false
|
|
index [:resource_type, :resource_family, :location], unique: true
|
|
end
|
|
|
|
# These are valid UBIDs for TYPE_BILLING_RATE
|
|
run "INSERT INTO billing_rate VALUES('0b87e0b8-85de-8978-9a60-ba72cb21eedc', 'VmCores', 'c5a', 'hetzner-fsn1', 0.000171296)"
|
|
run "INSERT INTO billing_rate VALUES('d44d4d44-3c8c-8578-9bf6-4d668a1cba8f', 'VmCores', 'c5a', 'hetzner-hel1', 0.000154167)"
|
|
run "INSERT INTO billing_rate VALUES('139d9a67-8182-8578-a303-235cabd5161c', 'VmCores', 'm5a', 'hetzner-fsn1', 0.000171296)"
|
|
run "INSERT INTO billing_rate VALUES('08c502f7-df5d-8978-9896-feafa0ec5c40', 'VmCores', 'm5a', 'hetzner-hel1', 0.000154167)"
|
|
end
|
|
|
|
down do
|
|
drop_table :billing_rate
|
|
end
|
|
end
|