page temp: measuring...
z
meow
← Back to blog

9 January 2025

Running Science Experiments in Production with Scientist

How Scientist experiments work, and why I contributed CPU time tracking to the gem.

We tried to replace a slow search query with a fast database query. It was 91% faster. It was also wrong 4% of the time.

Scientist caught the mismatch before users saw it. While working with the experiment data, I also found that the gem could report wall time but not CPU time, so I added that measurement.

The experiment that failed

The repo page investigation found a search query adding 198ms to logged-in page loads. A database EXISTS query took about 2ms, so it looked like a straightforward replacement.

We set up a Scientist experiment to run both implementations side by side in production:

science "might-have-packages" do |e|
  e.use   { existing_search_query }   # control: always returned
  e.try   { new_database_query }      # candidate: run & compared
end

Scientist returned the control result to the user and recorded whether the candidate agreed with it. We started at 1% of traffic. The mismatch rate settled at 3-4%.

The database query only checked for one type of package, while the search service knew about all of them. Repositories with non-Maven packages accounted for the mismatches.

We reverted the experiment.

(The eventual fix was even better: remove the check entirely.)

Adding CPU time

The experiment also exposed a gap in the measurements. I could compare wall-clock duration, but I wanted to compare how much CPU work each implementation did.

Wall-clock time includes I/O waits, GC pauses, and time spent waiting for a process to be scheduled. A method can take 200ms to return while using only 5ms of CPU. That distinction matters when the goal is to free CPU capacity across a fleet.

Scientist did not track CPU time, so I added it.

The implementation

The change captures Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID) at the start and end of each observation, alongside the existing wall-clock timing:

science "my-experiment" do |e|
  e.use { control_code }
  e.try { candidate_code }
end

# Each observation now includes:
# observation.duration  -> wall time
# observation.cpu_time  -> CPU time only

Each observation can now report whether a candidate used less CPU, rather than only whether it returned sooner.

Why I contributed it

I did not start with a plan to contribute to Scientist. I was trying to answer a question during an investigation, found that the data was missing, and added it to the tool we were already using.