Sometimes, the best way to solve a problem is to take everything apart and start again.
Usually, we go for the alternative. With a large inventory and endless growth ideas, we use our engineering resources very carefully. That means building on what we have or, if we’re introducing a new way of doing things, starting with a minimum viable product, testing, and iterrating. This helps us make smart decisions without letting individual projects spiral out of control.
But when we set out to improve the way our calendar works for scheduling fishing trips, we realized that the single best course of action was to go back to the drawing board. Here’s why, and what our engineers did next.
A Fatal Flaw
How long is a fishing trip? Well, about as long as a piece of string.
Our business is based on booking excursions that are a set length in time. What that length is depends on the captain, the part of the world they operate in, and the ambitions of the customer. Working with thousands of businesses that run several of these trips in a day, we need to provide a viable way to organize this. At the same time, in order for customers to use FishingBooker to plan their vacation, they need to know that when we say a trip is available, it is.
Despite this, until recently FishingBooker’s booking system wasn’t tailored to scheduling individual trips. Instead, it looked at availability in terms of half day blocks of time. Captains could list packages of any duration, with any start time, but they could only block out a half day or a full day on their calendar.
If their trips happened to last less than a whole day but spanned both the morning (am) and afternoon (pm), this couldn’t be logged properly. The result? We were sending captains overlapping bookings.
We always knew that addressing this would be a big project – both for infrastructure and for design. And with the calendar underpinning our whole business, the stakes were high. So we gathered a small team of a senior backend, frontend, and mobile engineer, and a designer, to unpick the problem of availability and propose a new solution that would last as our product continues to grow.
There were four main problems that we wanted to address:
Once our team got together and unpicked the current calendar setup, they realised that this would need a fresh start. Here are three of the biggest challenges that they faced.
Challenge #1: Solving Overlaps Without Overloading our Servers or our Users
The team quickly established that the best way to solve the problem of overlapping trips was to look at the trips themselves, rather than dividing the day into two. But there’s a reason our calendar used to work the way it did. From the server’s standpoint, it’s more viable to log availability of a few thousand listings twice a day than it is to keep track of tens of thousands of individual trips for every single moment.
In order to minimize the pressure on the server and prevent slow loading speeds, we developed a new way of caching our availability data. This involves collecting availability data per trip per day, and only updating this after significant changes, such as when a captain gets a new booking or updates their availability settings.
Approaching data structure this way lets us provide a fast page load time as well as accurate results. Indispensable when we need to show data for multiple listings fast, for instance when someone is performing a search for a destination. Then, when it’s essential to have 100% accurate data, such as when someone is about to reserve and pay for their trip, we can calculate that listing’s specific data on the fly and provide a smooth experience.
On the front end, we needed to keep our calendar simple and easy to use, despite adding a more sophisticated set of functionalities. A particular challenge in our business is that we work with fishing captains who pull long hours and for the most part don’t have much time for apps and technology. Whatever we create needs to be friendly and understandable to anyone, including technophobes with outdated devices.
Here’s what we created:
Each date has three potential settings: completely available, completely unavailable (blocked), or “custom” availability, where captains can select individual trips and block them as desired. Each time a booking request gets sent through our system, that trip’s duration is automatically blocked on the listing’s calendar.
This calendar update page extracts information about the duration, start time, and guest capacity of each trip associated with the listing. Captains can change their package details at any time, so each time they load this page, it gets updated dynamically.
In order to keep all this information accessible at the click of a button without overloading the server with multiple requests, we created a single page application (SPA) with React. We usually use Redux for state management, and this solved our problem neatly.
Challenge #2: Managing Short-Notice Bookings with Custom Availability Settings
Up until recently, short notice bookings were a headache for us, and for our partners. Many captains required a certain amount of time to prepare for a trip – either to get their boat and gear ready or because they needed to travel to the marina or meeting point. However, the only way they could avoid getting a booking request for the very near future was manually blocking upcoming dates on a daily basis. For most busy captains, this just wasn’t feasible.
So, we created a custom availability setting for each individual listing, linked directly to its calendar. This lets each captain set their minimum short notice period (anything from the same day to a week in advance) and their booking window. Here, they can also let us know if they are unavailable by default, note how far in advance they are happy to get bookings for, or set a custom date range when they can be booked.
Our captains operate in different time zones and trips can depart at any time of day. So, in order for our advance notice tool to function correctly, we realized we would need to update our availability cache every single hour. This had potential to slow our site down considerably – something we obviously wanted to avoid.
To solve this, we integrated the advance notice settings themselves into our cache’s calculation. If the listing’s notice period is set to one day, we’ll recalculate the data for the next 48 hours. If it’s set to 7 days, we’ll recalculate the data for 6-7 days from today. This way, we’re only looking at what could have changed since the last calculation, but we’re still maintaining accurate data.
Challenge #3: Taking all this…And Multiplying it By 10
We’d solved the challenge of not overloading the server when dealing with a single listing. But catering to fleets of boats was a challenge of its own.
Some of our captains operate well over 10 boats, with each one requiring its own listing. As each listing has multiple packages, and as the calendar needs to display availability for the full year ahead and a few months in the past, creating a tool that let users manage all their calendars side by side was a challenge of its own. It had potential to send an overwhelming number of requests to the server any time the tool was even opened, simply rendering it unusable.
Solving this needed two approaches:
- Cutting out any part of the app that wasn’t strictly needed.
- Enabling an infinite slider without compromising on the information that was.
Firstly, we decided to enable captains to only block out full days when updating more than one listing at a time, rather than setting package-specific availability. This way, we could avoid overloading the server with requests to get each listing’s package info. They could still update finer details in each listing’s individual calendar, so this was an element that we could compromise on without negatively affecting the user experience.
But despite slimming down the functionalities, we still needed to log a huge amount of data. This simply wasn’t workable on the average user platform. Lazy loading was the obvious solution, but would this still work if the user is curious and starts looking around the whole multicalendar?
Probably not. You can imagine how many simple react components are rendered and re-rendered with every store update! We needed to let users access their information without risking browsers crashing.
So, we opted to render only the part of the view that is visible on the screen, using React Virtualized. After all, why render something that the user can’t see, and probably won’t ever see unless they’re “curious”? Combining this with lazy loading kept browsers happy while solving performance issues.
Rollout and Adoption
As changing the calendar was a high-stakes project, we rolled it out gradually to manage the risks as much as possible. We wanted to gather feedback from a select group of captains while tweaking the product and preparing it for full-scale launch. This meant we had to support both calendars at once for some time, with a shared, legacy mode infrastructure.
In order to make this work, we did a silent launch of the backend in July, enabling package-by-package scheduling through our booking system to avoid overlapping trips. Once we’d done this, we invited captains to try the new calendar and manage their own availability on a trip-by-trip basis.
After several months of gradual release and education about how to use the new calendar, we finally switched all listings over in November. This lengthy release meant our end users had a smooth experience throughout the changeover. After all, it was a complete rework of the very basis of our business – it was important it happened without a hiccup!
A small, cross-functional team and good product management let us solve one of our biggest business headaches with trip-specific availability. If you want to be part of thinking our way through our next challenge, get in touch!