Lineup records, innings caps, and scaling

I haven’t rolled out any major new changes to the ottoneu platform yet this year, because I am working on a doozy. If you go back to my post about the 2014 plan, you’ll see the first bullet point is a new stats backend. The original goal was to get this done pre-season, but this problem ended up being more complex than I expected. In this post, I am going to walk through the infrastructure as it exists today, the proposed changes, and why it ended up taking a lot longer than expected.


Currently, the system is fairly simplistic. Every 5 minutes, a script runs that checks if games have started and thus if players should be ‘locked in’ to their fantasy team’s lineup. This creates a record in a table, and this table is now the script by how stats are compiled – it contains every team’s locked-in lineup for every day in the season. As long as nothing happens to this table, stats can be re-run at any time for any reason.

Every 20 minutes, a script is run against this table to update stats for the day. This script writes a record in a stats table for every single lineup record.

Let me write that again in a bit bigger font:

This script writes a record in a stats table for every single lineup record.

This is both completely unscalable and also required if a game is going to have a hard innings cap. Consider the following scenario: it is the last day of the baseball season, and in my league I am starting Clayton Kershaw and in your league you are starting Clayton Kershaw. However, I have 1497 IP this year, and you have 1490. Kershaw spins a complete game masterpiece, and I get the first 3 innings of it because I run directly into my 1500IP cap, and you get all 9 innings. This necessitates having two records for the two Clayton Kershaws we have created – one who pitched 3 innings and then the team couldn’t take anymore innings, and the other that pitched 9. (Aside: if only it was this easy to clone Clayton Kershaw)

The solution: moving from a hard inning cap to a soft inning cap.

About innings caps

Inning caps simply define how many innings your fantasy team can collectively throw in a season. Similar to game limits for positional players, the idea is to keep all the teams in a league around the same number of innings, so that teams are not incentivized to start every single pitcher they can in order to win counting stats.

There are two ways of implementing an innings cap: hard and soft. Every major site uses soft inning caps, meaning on the day a team goes over the cap, that team will get all the innings pitched on that day but none going forward. For example, if there is a 1500 IP cap and your fantasy team has thrown 1499 innings in the season, you can start 5 starting pitchers and 5 relievers and get all their innings on that day, but once you are over the 1500 IP cap you will no longer get credit for any innings your team throws going forward. A hard cap is more restrictive – as soon as your team hits the 1500 IP cap, that is it, no more innings for you. The hard cap was originally implemented to move fantasy owners away from ‘streaming’ pitchers on the last day of the season, or playing as many pitchers as possible in order to get as many counting stats as possible. Streaming felt like gaming the system, and ottoneu doesn’t care much for gaming the system.

However, it became clear that there are existing incentives in ottoneu for not streaming: keepers and cap penalties. Cutting players in order to pick up potential starters late in the season is expensive due to cap penalties, and towards the end of the season a lot of players on an ottoneu team are either keepable or expensive but productive players that simply should not be cut. If an owner chooses to cut productive and keepable players in order to stream pitchers at the end of the season, they probably have a good reason and the system of the game should accept that.

In leagues without keepers and without cap penalties, streaming remains game-y, which is all the more reason to play ottoneu.

Tomorrow (or, soon…)

The switch to soft caps will happen over the all-star break, because there are significant database operations required to make this switch. However, I can advocate for this change with two numbers:


As of writing this post, this is how many records are in the stats_batting table.


After making this change, this is how many records are left in the stats_batting table. That’s a lot fewer records.

How does this affect you? Speed. After this change, getting updated stats will become significantly faster, meaning your standings pages will be updated faster. It will also allow ottoneu to grow its userbase considerably, which would not be possible without this change.

What’s next?

Once I get this change ready to deploy, I am going to turn entirely to the lineup page experience. I believe it can be better, and I’d love your thoughts on what a great lineup page for ottoneu looks like. Or, let me know what you think could be a huge improvement to ottoneu – I’m always open to new ideas!

Please let me know your feedback on these changes and if posts like this are interesting!