Ahoy deep dive part 1: What is Ahoy And Application Level Optimizations
Ready to turbocharge your Ahoy Events and Visits?Alex McDermid
September 22, 2023Ahoy is a Ruby gem often used in Ruby on Rails applications for tracking events and user visits. It helps you analyze what users do and how they interact with your application. The Ahoy gem essentially allows you to track two types of records:
- Visits: A visit represents a user session. A new visit is created whenever a user comes to the site after a certain period or from a new location. Visit records typically include information like the user's IP address, geographical location, and user agent, among other details.
- Events: An event is a specific action taken during a visit. This could be anything from clicking a button to viewing a page. Events are linked to visits and usually contain additional properties that describe the action in more detail.
In a default Rails setup with Ahoy, your database would have two tables, ahoy_visits and ahoy_events. Below is how the schema for these tables might look. You can also read about this more in the Ahoy documentation.
Example Ahoy Visits Table Schema
create_table "ahoy_visits", id: :uuid do |t|
t.string "visit_token"
t.string "visitor_token"
t.string "ip"
t.text "user_agent"
t.datetime "started_at"
t.bigint "user_id"
end
Example Ahoy Events Table Schema
create_table "ahoy_events", force: :cascade do |t|
t.bigint "visit_id"
t.bigint "user_id"
t.string "name"
t.jsonb "properties"
t.datetime "time"
end
So, now, you've set up Ahoy in your Rails application and you're capturing rich, insightful data on user behavior. That's great—but what happens when the user base grows, and along with it, your Ahoy data tables? As you scale, performance issues may start to creep in, affecting the overall performance of your application.
This is where Ahoy optimization strategies come into play. If you're committed to maintaining a high-performing application while still leveraging the full power of Ahoy, then the following sections are designed specifically for you. From smart database scaling strategies to application-level improvements and infrastructure tweaks, this read will highlight proven ways to keep your Ahoy Events and Visits as speedy as they are insightful.
Optimizing Ahoy at the Application Level
Before diving into the deep waters of database optimization and infrastructure scaling, it's often beneficial to start with the codebase itself. This section will guide you through application-level strategies aimed at ensuring that your Ahoy events and visits tracking is efficient right from the start.
Rate Limiting
What: Implementing controls to restrict the rate of requests from clients.
Why: To prevent accidental or intentional heavy usage that could bring down the application.
How: Use middleware like Rack::Attack in your Rails application.
Batching
What: Writing multiple events to the database in a single transaction.
Why: Reduces the overall database resources needed for writing.
How: Use ActiveRecord's import method or database-specific batch operations.
Asynchronous Processing
What: Offloading heavy lifting tasks to background jobs.
Why: Frees up the main thread, improving the overall performance of the application.
How: Utilize background job frameworks like Sidekiq or Delayed Job.
Optimize Queries
What: Fine-tuning SQL queries.
Why: Even minor adjustments can yield substantial performance gains.
How: Use tools like the bullet gem to identify N+1 queries and other inefficiencies.
Optimizing Queries with the Bullet Gem Explained
What: Using the Bullet Gem to Find N+1 Queries
Bullet is a gem that helps you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (includes, joins, etc.), when you're using eager loading that isn't necessary, and when you should use counter cache.
Why: The N+1 Query Problem
The N+1 query problem occurs when the code retrieves a set of objects and their associated objects individually. For example, if you have a User model and each user has many Ahoy::Events, an N+1 issue would happen if you fetch each Ahoy::Event for each user one by one.
In simple terms, you make 1 query to retrieve the users (N), and then for each user, you make another query to retrieve their events (+1 for each user). This is highly inefficient and can slow down your application, especially when dealing with tables as heavy as ahoy_events and ahoy_visits.
How: Implementation and Monitoring
First, add the Bullet gem to your Gemfile and bundle install.
gem 'bullet'
Enable Bullet in your config/environments/development.rb.
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.console = true
# other Bullet configurations
end
With Bullet enabled, every time you navigate through your application in the development environment, Bullet will alert you whenever it detects an N+1 query, either through a JavaScript alert or by logging it to the console. You can also add more notification channels like emails or Slack.
Real-world Example with Ahoy
Let's say we want to find all the events associated with a list of visits:
# Before optimization
visits = Ahoy::Visit.all
visits.each do |visit|
events = Ahoy::Event.where(visit_id: visit.id)
# do something with events
end
This would result in an N+1 problem. Bullet would notify us about it, and we could optimize it like so:
# After optimization
visits = Ahoy::Visit.includes(:events).all
visits.each do |visit|
events = visit.events
# do something with events
end
This preloads the events for each visit in a single query, eliminating the N+1 issue.
By integrating Bullet into your workflow, you can proactively identify and eliminate inefficient queries, making your application, including Ahoy Events and Visits, more efficient and scalable.
Interested in joining a mission-driven team with a passion for Ruby on Rails? If so, please take a moment to look at our open positions!.
About the author
Alex is a Full Stack Developer based out of Surrey, BC.