We're running a site for booking salmon fishing licenses. The site has no problem handling the traffic 364 days a year. The 365th day is when the license sale opens, and that's where the problem occurs. The servers are struggling more and more each year due to increased traffic, and we have to further optimize our booking query.
The licenses are divided into many different types (tbl_license_types
), and each license type are connected to one or more fishing zones (tbl_zones
).
Each license type can have a seasonal quota, which is a single value set as an integer field in tbl_license_types
.
Each zone can have a daily quota, a weekly quota and a seasonal quota. The daily quota is the same for all days, and the seasonal quota of course is a single value. Daily and seasonal are therefore integer fields in tbl_zones
. The weekly quota however differs by week, therefore those are specified in the separate tbl_weekly_quotas
.
Bookings can be for one or more consecutive dates, but are only stated as From_date
and To_date
in tbl_shopping_cart
(and tbl_bookings
). For each booking attempt made by a user, the quotas have to be checked against already allowed bookings in both tbl_shopping_cart
and tbl_bookings
.
To be able to count/group by date, we use a view called view_season_calendar
with a single column containing all the dates of the current season.
In the beginning we used a transaction where we first made a query to check the quotas, and if quotas allowed we would use a second query to insert the booking to tbl_bookings
.
However that gave a lot of deadlocks under relatively moderate traffic, so we redesigned it to a single query (pseudo-code):
INSERT INTO tbl_bookings (_booking_)
WHERE _lowest_found_quota_ >= _requested_number_of_licenses_
where _lowest_found_quota_
is a ~330 lines long SELECT with multiple subqueries and the related tables being joined multiple times in order to check all quotas.
Example: User wants to book License type A, for zones 5 and 6, from 2020-05-19 to 2020-05-25. The system needs to
- count previous bookings of license type A against the license type A seasonal quota.
- count previous bookings in zone 5 for each of the 6 dates against zone 5 daily quota.
- same count for zone 6.
- count previous bookings in zone 5 for each of the two weeks the dates are part of, against zone 5 weekly quota.
- same count for zone 6.
- count all previous bookings in zone 5 for the current season against zone 5 seasonal quota.
- same count for zone 6.
If all are within quotas, insert the booking.
As I said this was working well earlier, but due to higher traffic load we need to optimize this query further now. I have some thoughts on how to do this;
- Using isolation level
READ UNCOMMITTED
on each booking until quotas for the requested zones/license type are nearly full, then fallback to the defaultREPEATABLE READ
. As long as there's a lot left of the quota, the count doesn't need to be 100% correct. This will greatly reduce lock waits and deadlocks, right? - Creating one or more views which keeps count of all bookings for each date, week, zone and license type, then using those views in the WHERE clauses of the insert.
- If doing nr 2, use
READ UNCOMMITTED
in the views. If views report relevant quota near full, cancel the INSERT and start a new one with the design we're using today. (Hopefully traffic levels are coming down before quotas are becoming full)
I would greatly appreciate thoughts on how the query can be done as efficient as possible.