In complex systems like Airbnb, which use a real-time pricing model, managing prices during the booking process is one of the core challenges. To avoid errors arising from price mismatches and to ensure a smooth user experience, techniques like real-time price updatesand concurrency control are employed. In this article, we will explore this process and delve into a professional implementation.
Note: This article was written with assistance from artificial intelligence.
1. Managing Prices Using Cache
Using Cache for temporarily storing price-related data helps reduce the frequency of requests to the main database and allows the system to respond to requests faster. This approach ensures that prices are stored temporarily, and if the price changes during the booking process, the cache is updated.
- How Cache Works: During the user's initial search, the current price is fetched from the database and stored in Redis or Memcached. If the user proceeds to finalize the booking, the cache is first checked to see if the price has changed, and the user is notified of any potential price changes.
2. Database Callbacks for Real-time Price Updates
Systems like Airbnb use Database Callbacks to check for real-time price changes. Before finalizing the booking, the system verifies the price from the database, and if any changes have occurred, the user is notified to confirm the new price.
In this example, PostgreSQL and Redis are used for temporary storage and concurrency management. The system is designed to immediately notify the user if the price or booking version changes, preventing potential errors.
const { Client } = require('pg'); // PostgreSQL client
const redis = require('redis');
const client = redis.createClient();
// Setting up PostgreSQL connection
const pgClient = new Client({
connectionString: process.env.DATABASE_URL,
});
pgClient.connect();
// Step 1: Check the price from Cache
async function checkPrice(bookingId, userPrice) {
const cachedPrice = await client.get(`price:${bookingId}`);
if (cachedPrice !== null) {
return parseFloat(cachedPrice);
}
// If not in Cache, fetch from the database
const { rows } = await pgClient.query('SELECT price, version FROM bookings WHERE id = $1', [bookingId]);
const { price, version } = rows[0];
// Store the price in Cache
await client.set(`price:${bookingId}`, price, 'EX', 60); // Cache expiry set to 60 seconds
return { price, version };
}
// Step 2: Booking using OCC (Optimistic Concurrency Control)
async function bookRoom(bookingId, userPrice, userVersion) {
const { price, version } = await checkPrice(bookingId);
// Check for price or version changes
if (price !== userPrice || version !== userVersion) {
throw new Error('The price or version has changed. Please confirm the new price.');
}
// Start transaction for booking
try {
await pgClient.query('BEGIN');
// Update the version and finalize booking
const newVersion = version + 1;
await pgClient.query(
'UPDATE bookings SET version = $1, status = $2 WHERE id = $3 AND version = $4',
[newVersion, 'BOOKED', bookingId, version]
);
await pgClient.query('COMMIT');
console.log('Booking successful!');
} catch (error) {
await pgClient.query('ROLLBACK');
throw new Error('Booking failed. Please try again.');
}
}
// Final step: Process the booking
async function processBooking(bookingId, userPrice, userVersion) {
try {
await bookRoom(bookingId, userPrice, userVersion);
} catch (error) {
console.error('Error:', error.message);
// Notify the user about price changes
}
}
1. Cache:
- The system uses Redis to store temporary price data, ensuring faster access during price changes and reducing the load on the primary database.
2. Optimistic Concurrency Control (OCC):
- This method checks different versions of the data to prevent concurrency issues and inconsistencies. If the data version changes before the booking is finalized, the system reports the issue, and the user must confirm the new price.
3. Atomic Transactions:
- All booking processes are executed within atomic transactions, meaning that either the booking is fully completed, or no invalid changes occur. This ensures that the system data remains consistent.
- High Efficiency: The use of Cache improves system efficiency and speeds up access to price data.
- Concurrency Management: With the help of OCC and transactions, concurrency issues and real-time price changes are effectively handled.
- Enhanced User Experience: The user is always informed of the latest prices, and the booking process remains transparent and error-free.
This approach is specifically employed in systems like Airbnb to optimize the booking experience for users and ensure smooth, error-free operations.