user1007711
user1007711

Reputation:

Caching text file contents in Sinatra

I'm new to Sinatra and programming for the web, so some of the terminology used may not be quite right. Anyway...

I have an app that reads a .txt line by line into an array and then when you load the index.html.erb it randomly displays one of the lines. I put the contents in a text file instead of directly into an array so that if I need to add more data it's easier to update then adding to the array directly and redeploying the app. My concern is if it's recreating the array and rereading the file every time you load the page. I don't know exactly how that kind of thing works server side, or how to check it. The code that creates the array reads:

before do
  @ways ||= ['']
  if @ways[1].nil?
    File.open('ways.txt', 'r').each_line { |line| @ways << line }
  end
end

And then my route:

get '/' do
  @way = @ways.sample

  erb :index
end

Is there a way to make sure this is as efficient as possible? Or should it be done some other way entirely? According to the Chrome Dev tools, it transfers ~ 800b per page load.

Upvotes: 1

Views: 1035

Answers (1)

Phrogz
Phrogz

Reputation: 303271

What you have written here will indeed read the file into an array on each request.

Tip: you can make it shorter and faster with @ways = File.readlines('ways.txt')

If you wanted to cache this array, you could do so as a constant at app startup, e.g.:

WAYS = File.readlines('ways.txt').map(&:chomp)
get "/" do
  @way = WAYS.sample
  erb :index
end

However, with this you would need to quit and restart your server if you edited the text file. If you wanted to avoid that, you could check the modification time of the file in your before action and only update the array (replace it) if the contents had changed.

If you need help with this last suggestion, let me know and I can edit to suit.


Edit: Here's one way to use a constant to save the data and reload only when the file changes:

WAYS = { file:'ways.txt', all:[] }
before do
  if WAYS[:updated] != (mtime=File.mtime(WAYS[:file]))
    WAYS[:all].replace File.readlines(WAYS[:file]).map(&:chomp)
    WAYS[:updated] = mtime
  end
end
get "/" do
  @way = WAYS[:all].sample
  erb :index
end

This is a little bit aggressive—checking the file modification time on every request—but the performance should be fine for all but the heaviest load or slowest disk.

An alternative solution would be to kick off a thread to force-check/update the array every few minutes, e.g.

require 'sinatra'

WAYS = { file:'ways.txt', all:[] }
Thread.new do
  loop do
    if WAYS[:updated] != (mtime= File.mtime(WAYS[:file]))
      WAYS[:all].replace File.readlines(WAYS[:file]).map(&:chomp)
      WAYS[:updated] = mtime
    end
    sleep 5 # seconds
  end
end

get '/' do
  WAYS[:all].sample
end

Upvotes: 3

Related Questions