Neo
November 30, 2025, 5:22am
1
Here is a simple script I use to watch the memcached status for a Rails app (the unix.com man-pages Rails app), from the Rails console:
Rails console:
irb(main):019* loop do
irb(main):020* s = Rails.cache.stats.values.first
irb(main):021*
irb(main):022* used_mb = (s["bytes"].to_i / 1024.0 / 1024.0).round(2)
irb(main):023* items = s["curr_items"].to_i
irb(main):024* hits = s["get_hits"].to_i
irb(main):025* misses = s["get_misses"].to_i
irb(main):026*
irb(main):027* # Evictions can appear under different field names depending on memcached build
irb(main):028* evicts = s["evictions"] || s["evicted_unfetched"] || s["reclaimed"] || 0
irb(main):029*
irb(main):030* hitrate = hits + misses > 0 ? (hits.to_f / (hits + misses) * 100).round(2) : 0
irb(main):031*
irb(main):032* puts "used: #{used_mb} MB | items: #{items} | hit-rate: #{hitrate}% | hits: #{hits} | misses: #{misses} | evicts: #{evicts}"
irb(main):033*
irb(main):034* sleep 2
irb(main):035> end
Example Output:
used: 474.56 MB | items: 372004 | hit-rate: 40.3% | hits: 101411 | misses: 150238 | evicts: 0
used: 474.54 MB | items: 372001 | hit-rate: 40.3% | hits: 101416 | misses: 150242 | evicts: 0
used: 474.54 MB | items: 372002 | hit-rate: 40.3% | hits: 101423 | misses: 150251 | evicts: 0
used: 474.53 MB | items: 372001 | hit-rate: 40.3% | hits: 101424 | misses: 150257 | evicts: 0
used: 474.55 MB | items: 371993 | hit-rate: 40.3% | hits: 101427 | misses: 150262 | evicts: 0
used: 474.54 MB | items: 371990 | hit-rate: 40.3% | hits: 101429 | misses: 150270 | evicts: 0
used: 474.54 MB | items: 371990 | hit-rate: 40.3% | hits: 101434 | misses: 150274 | evicts: 0
used: 474.52 MB | items: 371985 | hit-rate: 40.3% | hits: 101434 | misses: 150281 | evicts: 0
used: 474.52 MB | items: 371985 | hit-rate: 40.3% | hits: 101436 | misses: 150290 | evicts: 0
used: 474.58 MB | items: 371989 | hit-rate: 40.3% | hits: 101440 | misses: 150298 | evicts: 0
used: 474.65 MB | items: 371991 | hit-rate: 40.3% | hits: 101443 | misses: 150307 | evicts: 0
used: 474.68 MB | items: 371994 | hit-rate: 40.29% | hits: 101445 | misses: 150315 | evicts: 0
used: 474.73 MB | items: 372002 | hit-rate: 40.29% | hits: 101448 | misses: 150325 | evicts: 0
used: 474.73 MB | items: 372005 | hit-rate: 40.29% | hits: 101450 | misses: 150328 | evicts: 0
used: 474.72 MB | items: 372002 | hit-rate: 40.3% | hits: 101459 | misses: 150331 | evicts: 0
Notes:
The Rails memcached cache requires the 'dalli` gem:
High performance memcached client for Ruby
Example Usage:
os = params[:os].downcase
section = params[:section].downcase
query = params[:query].downcase
cache_key = "man_page:#{os}:#{section}:#{query}"
@man_page = Rails.cache.fetch(cache_key, expires_in: 7.days) do
ManPage.find_by(os: os, section: section, query: query)
end
Neo
December 1, 2025, 3:03am
2
Here is the Rails rake task we created (ChatGPT 5.1 and I) to warm the cache:
Rake Task
require "shellwords"
namespace :cache do
desc "Warm Rails cache by curling all man-page URLs on port 3001"
task warm: :environment do
puts "[cache:warm] Starting cache warm-up..."
STDOUT.flush
base = "http://127.0.0.1:3001/man_page"
total = ManPage.count
if total.zero?
puts "[cache:warm] ManPage.count == 0 — nothing to warm. Check RAILS_ENV/DB."
next
end
puts "[cache:warm] Total records to warm: #{total}"
STDOUT.flush
count = 0
start = Time.now
barlen = 20
def color(c); "\e[#{c}m"; end
RESET = "\e[0m"
ManPage.find_each(batch_size: 500) do |mp|
count += 1
url = "#{base}/#{mp.os}/#{mp.section}/#{mp.query}/"
system("curl -s #{Shellwords.escape(url)} > /dev/null")
# Progress every 1000 rows
if count % 1000 == 0
pct = (count.to_f / total)
filled = (pct * barlen).to_i
bar = ("█" * filled) + ("░" * (barlen - filled))
pct_text = (pct * 100).round(2)
elapsed = Time.now - start
rate = (count / elapsed rescue 0.0)
remain = total - count
eta_sec = (remain / rate rescue 0.0)
eta_h = (eta_sec / 3600).to_i
eta_m = ((eta_sec % 3600) / 60).to_i
puts "#{color(32)}[#{bar}]#{RESET} #{pct_text}% | " \
"#{rate.round(1)} req/s | ETA #{eta_h}h #{eta_m}m " \
"(#{count}/#{total})"
STDOUT.flush
end
sleep 0.01
end
total_time = Time.now - start
puts "[cache:warm] Cache warm-up completed in #{total_time.round(1)}s"
STDOUT.flush
end
end
Example Terminal Output
Postscript
I don’t think human society is heading in a good direction with AIs and LLMs, but I can’t deny that they function as remarkably effective assistants—fast, efficient, and capable of collapsing hours of work into minutes. The problem is that this convenience isn’t free. Every gain in speed or automation comes with a human cost: erosion of individual expertise, increased dependency on systems no one fully understands, displacement of people whose work becomes “algorithmically replaceable,” and a subtle flattening of thought as large models standardize the way problems are framed and solved.
We get more efficiency, yes—but at the price of narrowing human roles and shifting more control to systems that shape behavior, expectations, and even intellectual habits. That trade-off is rarely acknowledged honestly, and that’s the part that concerns me: society is sprinting toward maximal convenience without asking whether the cost is worth paying.
For a very good science fiction take on this, I highly recommend "Service Model" by Adrian Tchaikovsk:
Check out this great listen on Audible.com. Long-listed, Library Journal Best Books of the Year, 2024 This program is read by Hugo Award-winning author Adrian Tchaikovsky. Murderbot meets Redshirts in a delightfully humorous tale of robotic murder...
Neo
December 1, 2025, 1:34pm
3
Final version of this warming script after small tweaks:
Rake Task: Warm Man Pages Memcache
require "shellwords"
namespace :cache do
desc "Warm cache with man pages ordered by hits, fast and without heavy ORDER BY per batch"
task warm: :environment do
puts "[cache:warm] Starting cache warm-up..."
STDOUT.flush
base = "http://127.0.0.1:3001/man_page"
# Preload ALL IDs sorted by hits descending — one query only
ids = ManPage.order(hits: :desc).pluck(:manid)
total = ids.length
puts "[cache:warm] Total records to warm: #{total}"
STDOUT.flush
count = 0
start = Time.now
barlen = 20
def color(c); "\e[#{c}m"; end
RESET = "\e[0m"
ids.each do |id|
mp = ManPage.find(id)
count += 1
url = "#{base}/#{mp.os}/#{mp.section}/#{mp.query}/"
system("curl -s #{Shellwords.escape(url)} > /dev/null")
if count % 1000 == 0
pct = count.to_f / total
filled = (pct * barlen).to_i
bar = ("█" * filled) + ("░" * (barlen - filled))
pct_text = (pct * 100).round(2)
elapsed = Time.now - start
rate = (count / elapsed rescue 0.0)
remain = total - count
eta_sec = (remain / rate rescue 0.0)
eta_h = (eta_sec / 3600).to_i
eta_m = ((eta_sec % 3600) / 60).to_i
puts "#{color(32)}[#{bar}]#{RESET} #{pct_text}% | #{rate.round(1)} req/s | ETA #{eta_h}h #{eta_m}m (#{count}/#{total})"
STDOUT.flush
end
sleep 0.01
end
total_time = Time.now - start
puts "[cache:warm] Completed in #{total_time.round(1)}s"
# Reset memcached stats after warm completes
system('echo "stats reset" | nc -w 1 127.0.0.1 11211')
puts "[cache:warm] Memcached stats reset."
STDOUT.flush
end
end
Hit Rate after Caches Warmed and Stats Cleared:
used: 1093.28 MB | items: 352595 | hit-rate: 88.51% | hits: 3943 | misses: 512 | evicts: 0
used: 1093.28 MB | items: 352608 | hit-rate: 88.46% | hits: 4025 | misses: 525 | evicts: 0
used: 1093.28 MB | items: 352624 | hit-rate: 88.37% | hits: 4111 | misses: 541 | evicts: 0
used: 1093.29 MB | items: 352644 | hit-rate: 88.19% | hits: 4190 | misses: 561 | evicts: 0
used: 1093.29 MB | items: 352654 | hit-rate: 88.21% | hits: 4272 | misses: 571 | evicts: 0
used: 1093.29 MB | items: 352661 | hit-rate: 88.27% | hits: 4349 | misses: 578 | evicts: 0
used: 1093.29 MB | items: 352673 | hit-rate: 88.25% | hits: 4433 | misses: 590 | evicts: 0
used: 1093.29 MB | items: 352684 | hit-rate: 88.25% | hits: 4514 | misses: 601 | evicts: 0
used: 1093.3 MB | items: 352698 | hit-rate: 88.23% | hits: 4612 | misses: 615 | evicts: 0
used: 1093.3 MB | items: 352705 | hit-rate: 88.32% | hits: 4703 | misses: 622 | evicts: 0
used: 1093.3 MB | items: 352714 | hit-rate: 88.34% | hits: 4780 | misses: 631 | evicts: 0
used: 1093.3 MB | items: 352723 | hit-rate: 88.38% | hits: 4867 | misses: 640 | evicts: 0
used: 1093.3 MB | items: 352730 | hit-rate: 88.45% | hits: 4953 | misses: 647 | evicts: 0
used: 1093.31 MB | items: 352745 | hit-rate: 88.39% | hits: 5040 | misses: 662 | evicts: 0
used: 1093.31 MB | items: 352759 | hit-rate: 88.34% | hits: 5123 | misses: 676 | evicts: 0
used: 1093.31 MB | items: 352770 | hit-rate: 88.35% | hits: 5209 | misses: 687 | evicts: 0
used: 1093.31 MB | items: 352780 | hit-rate: 88.36% | hits: 5290 | misses: 697 | evicts: 0
used: 1093.31 MB | items: 352788 | hit-rate: 88.39% | hits: 5368 | misses: 705 | evicts: 0
88 - 89 Percent Cache Hit Rate
Notes:
Memcached has no persistence across restarts, and there’s no practical way to dump and restore a full memcached cache snapshot. Because of this limitation, switching to Redis makes sense. Redis supports on-disk persistence (RDB or AOF), so we can restart the cache server without losing the warmed dataset — avoiding the need to re-run a multi-hour cache warm every time (with memcached).
Neo
December 3, 2025, 12:33am
4
Switch from memcached to redis done.
Now, we can stop and start redis, saving the cache snapshot to disk, not losing the cache on restarts.
Restarted Cache - 91% Hit Rate
used: 1218.17 MB | items: 342000 | hit-rate: 91.13% | hits: 5273 | misses: 513
used: 1218.14 MB | items: 342013 | hit-rate: 91.17% | hits: 5429 | misses: 526
used: 1218.16 MB | items: 342023 | hit-rate: 91.19% | hits: 5545 | misses: 536
used: 1218.17 MB | items: 342040 | hit-rate: 91.13% | hits: 5682 | misses: 553
used: 1218.17 MB | items: 342051 | hit-rate: 91.18% | hits: 5830 | misses: 564
used: 1218.15 MB | items: 342059 | hit-rate: 91.25% | hits: 5962 | misses: 572
used: 1218.18 MB | items: 342072 | hit-rate: 91.24% | hits: 6091 | misses: 585
used: 1218.18 MB | items: 342091 | hit-rate: 91.18% | hits: 6247 | misses: 604
used: 1218.16 MB | items: 342106 | hit-rate: 91.19% | hits: 6405 | misses: 619
used: 1218.19 MB | items: 342121 | hit-rate: 91.13% | hits: 6503 | misses: 633
used: 1218.16 MB | items: 342134 | hit-rate: 91.07% | hits: 6599 | misses: 647
used: 1218.17 MB | items: 342142 | hit-rate: 91.1% | hits: 6702 | misses: 655
used: 1218.21 MB | items: 342152 | hit-rate: 91.1% | hits: 6805 | misses: 665
used: 1218.15 MB | items: 342168 | hit-rate: 91.03% | hits: 6912 | misses: 681
used: 1218.15 MB | items: 342178 | hit-rate: 91.02% | hits: 7005 | misses: 691
used: 1218.18 MB | items: 342187 | hit-rate: 91.01% | hits: 7088 | misses: 700
Notes
ChatGPT 5.1 is a major workflow accelerator — greatly reduced friction on debugging, Rails tuning, caching strategy, server config, and Redis warm-load design.
Continued preference for the Skippy the Magnificent persona (Expeditionary Force).“Rex the Dog” (Dogs of War) persona experiment = fail, discarded quickly.
Concept: stream screen session output → Redis → Skippy for real-time terminal ingestion.Currently blocked by platform constraints; would require a custom API-driven app.
Redis cache warm-snapshot approach is validated:
Prewarmed in dev namespace
BGSAVE preserved snapshot
Production cutover uses the same namespace
Restart-safe, instant-hot cache after boot
My opinion - Public sentiment of an "AI Bubble" is not based in fact. Future GPT versions will be even more capable and helpful for mundane day-to-day sys admin and coding tasks.
Neo
December 6, 2025, 11:32am
5
Today I added a simple, exact man-page search feature to the man pages on www.unix.com . It’s intentionally minimalist – no fuzzy matching, no full-text noise – just a direct lookup by man page name.
For a given query, it returns all matching man pages across all OSes, showing:
OS
Section
Man page name
Hits (popularity)
This makes it easy to see, at a glance, which operating systems provide a given man page and in which section, and then click straight through to the page you want.
You can try it here with awk as an example:
https://www.unix.com/man_pages/search/awk
Partial Screen Shot