Notes on Umeda Tomohiro’s KaigiOnRails 2025 talk - Range on Rails: How PostgreSQL Multirange Simplifies Complex Booking Logic
In July I spent some time digging through past KaigiOnRails schedules while working on a CFP application. I found this talk by Umeda Tomohiro of Rizap Technologies. His talk focused on how to build a reservation system without fixed-length time slots. I was interested in understanding the approach as I’d seen the same problem solved differently by colleagues.
My highlights were: calculating availability by subtracting unavailable time ranges; understanding how to apply PostgreSQL’s multirange feature; and learning how to implement this maintainably in Rails.
Below are my notes from his talk. I used YouTube’s translation features and Google Translate for screenshots of some slides.
Umeda Tomohiro - 梅田智大
Links:
“The new “multi-range type” option dramatically simplifies complex logic. chocoZAP - the story of creating a reservation system
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=3
How a general reservation system works: (1) Set aside a fixed reservation slot (2) User selects a slot and makes a reservation
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=4
Features of chocoZAP’s reservation system
In other words… a reservation system that isn’t bound by time slots!
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=5
The difficulty of a reservation system without available slots - Translated using Google Translate to preserve flowchart structure.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=6
Even if there are no available slots, I’d like to see a list of available slots. Since there is no physical “slot” system, creating a list of available slots is extremely difficult.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=7
The breakthrough is “scope”. By treating the periods during which reservations are unavailable as a set of ranges and calculating the difference between these ranges and the target period, we can determine whether a reservation is possible.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=11
What does it mean to consider something as a “set of ranges”?
Consider the conditions under which you cannot make a reservation again.
When we re-examined the seemingly complex conditions for making reservations impossible, we discovered a common thread; “a period with a start and end date”.
Earlier I talked about how we can view the various conditions under which reservations cannot be made as a set of ranges.
What this means is that we will take another look at the various conditions under which reservations cannot be made.
Well, there are various reasons why you can’t make a reservation, such as the store being closed or other such conditions.
But in any case, there is a period of time where you can’t make a reservation. For example, if a user has already made a reservation, it will say that the user has made a reservation from a certain time or a certain time and that time slot cannot be reserved.
Also, if the store is closed, it will say that reservations cannot be made from a certain time to a certain time, and the store is closed, and that time slot cannot be reserved.
There are many different conditions like this, and they all have a period from start to finish, and although there are many different conditions, we found a common thread that they are all periods.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=12
The period during which reservations cannot be made is defined as the “range” - shown here as circles outlined with dotted lines.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=13
The period covered is also a scope - shown here as the shaded green circle marked “target period”
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=14
“Find the set difference of the ranges” - between shaded in blue the Period when reservations cannot be made, and in green the 1 week period covered.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=15
Results of finding the “set difference” - in the remaining range, reservations are open!
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=16
PostgreSQL’s Range Type cannot handle fragmented ranges.
Example: Subtract “12:10~12:40” from “12:00~13:00”.
The results are divided into “12:00~12:10” and “12:40~13:00”.
Multiple ranges cannot be handled with the range type and will result in an error.
ERROR: result of range difference would not be contiguous.
However, there is one problem here.
PostgreSQL’s usual range type, can’t handle segmented ranges.
So what I mean is when you take the left set and find the period during which reservations are possible, for example, you can make a reservation from 12:00 to 13:00.
However you can’t make a reservation between 1pm and 2pm. But you can make a reservation from 2pm to 3pm.
So I think the reservation period is scattered across multiple time slots.
However a typical range-type calculator can only handle one continuos range, so when you have multiple scattered ranges like this, it actually results in an error.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=17
The optimal solution is a “multi-range type”
That’s where the idea of multirange comes in. Multiple fragmented ranges can be treated as a single data point.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=18
What is a “multi-range type” in PostgreSQL?
A data type that can handle multiple ranges together, like an array.
There are multiple types depending on the range:
To put it more simply, it’s a data type that’s similar to how multiple ranges are crammed into a single array.
Depending on the type of range, there are also different types of multiple overlapping ranges.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=19
Multiple range types can use the same operations as range types. Just like with range types, you can handle overlap, intersection and difference!
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=20
Create a list of available reservations using the “multi-range type”
1 Consolidate multiple periods that are unavailable for booking into multiple timeframes.
2 The period for checking availability is also converted to a multi-range
3 Calculate the available reservation period using the set different method
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=21
Consolidate multiple periods that are unavailable for booking into multiple timeframes.
Step 1
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=22
The period for checking availability has also been converted to a multi-range scope.
Since the set difference operation is only possible between multiple ranges, the time period to check availability is also converted to a multiple range.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=23
Calculate the available reservation period using the set difference method!
Calculate the difference between the multiple ranges of periods for which unavailability can be checked and the multiple ranges of periods for which reservations are unavailable!
The result of the set difference will be the period during which reservations are possible.
And finally, the third step.
By taking the left set of this overlapping range of “one week” and the overlapping range that combines various periods when reservations are not possible, we were able to instantly derive a list of periods when reservations are possible.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=24
The “mutli-range type” has a variety of applications
When aggregating data across multiple disparate time periods, the multirange approach may prove very useful.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=26
Multi-range types are easier to handle when expected
A “multi-range type” is a data type that groups multiple ranges together.
It becomes easier to handle when expanded into individual areas.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=27
Use PostgreSQL’s convenient functions to convert ranges!
Range_agg: “Combine multiple ranges into a single multi-range range”
Unnest: “Expanding one multi-range to ‘multiple ranges’”
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=28
Multiple ranges are best used exclusively for aggregation!
Each record is stored as a range
Aggregation into multiple range types during aggregation
Expanding the result and treating it as a range type simplifies the code
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=29
Simplify your code using SQL View
Multirange types are not supported in Rails
Complex aggregation processes are confined to SQL views [?]
Simplify your code by creating a VIEW-based model
Now, I think this is something that people will have different opinions on, and it’s just my personal preference, but I think that using SQL View for this aggregation process will make the process simpler.
So, if you use SQL Views, there are still various issues, such as how to maintain these views, but I think there aren’t that many situations where you want to use multiple ranges to perform complex aggregations.
Well, for example, even if there is one system like this reservation system, I think there are only one or two locations at most, so the complexity of the SQL there can be handled with this SQL view, and in Rails, or rather Ruby, if you handle only the final aggregated result as an object, I think the code will be easier to write and easier to read.
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=30
Example of aggregation using VIEW
Group unavailable periods into multirange - Aggregate available periods using the difference set - Expand multirange into range
Key
https://speakerdeck.com/rizap_tech/range-on-rails-duo-zhong-fan-wei-xing-toiuxin-tanaxuan-ze-zhi-ga-fu-za-rozitukuwoju-de-nisinpurunisitawake?slide=31
Creating a Model that corresponds to the View simplifies the code!
3) How to use
Room.reservation_availabilities => List of available reservation periods
By using mathematical thinking about ranges, the code becomes dramatically simpler
Using multi-range types allows you to handle multiple separated ranges
Multiple range types are used exclusively for aggregation, and expanding the results into range types makes them easier to handle in Rails.
So to summarize, I think that by using this mathematical thinking about ranges, there are situations where the degree of complexity becomes dramatically simpler.
So, when performing aggregations using these kinds of ranges, having a multi-range type allows you to handle multiple ranges simultaneously.
And this multi-range approach, well, is used specifically for aggregation, and the final result obtained can then be expanded into individual ranges and treated as a range object, which I think makes the code easier to read and write.”