Background Jobs ใน Rails 2026: Sidekiq vs Good Job และคำถามสัมภาษณ์
คู่มือฉบับสมบูรณ์สำหรับการเลือก queue backend ใน Rails: เปรียบเทียบ Sidekiq 8.x, Good Job 4.x และ Solid Queue โดยเน้นที่ throughput, ฟีเจอร์ และคำถามสัมภาษณ์เชิงเทคนิค

Background jobs ใน Rails ได้พัฒนาอย่างมากพร้อมกับ Rails 8.x และการนำเสนอ Solid Queue เป็น Active Job backend เริ่มต้น การเลือกระหว่าง Sidekiq, Good Job และ Solid Queue ขึ้นอยู่กับความต้องการ throughput, ข้อจำกัดด้าน infrastructure และฟีเจอร์ที่แต่ละโปรเจกต์ต้องการ
สำหรับโปรเจกต์ Rails 8 ใหม่ ให้เริ่มต้นด้วย Solid Queue เปลี่ยนไปใช้ Sidekiq สำหรับ workload สูงที่เกิน 100,000 jobs/วัน หรือเมื่อ latency ระดับ Redis มีความสำคัญ เลือก Good Job สำหรับ jobs บน PostgreSQL ที่มีฟีเจอร์ batch และ unique job constraints พร้อมใช้งาน
Active Job รวม Background Processing อย่างไร
Active Job ให้ interface มาตรฐานสำหรับการประกาศ, จัดคิว และดำเนินการ background work ใน Rails Framework นี้ abstract ความแตกต่างระหว่าง queue backend ต่างๆ ทำให้โค้ดสามารถพกพาได้ระหว่าง Sidekiq, Good Job, Solid Queue หรือ adapter อื่นๆ
# app/jobs/payment_processor_job.rb
class PaymentProcessorJob < ApplicationJob
queue_as :critical
retry_on Stripe::RateLimitError, wait: :polynomially_longer, attempts: 5
discard_on Stripe::InvalidRequestError
def perform(order_id)
order = Order.find(order_id)
PaymentService.process(order)
OrderMailer.confirmation(order).deliver_later
end
endCallback retry_on และ discard_on จัดการกับ transient failures และ permanent errors โดยไม่ต้องเขียนโค้ดเฉพาะ backend Directive queue_as กำหนด priority ซึ่งแต่ละ backend จะตีความตามกลยุทธ์การประมวลผล queue ของตัวเอง
Sidekiq 8.x: Throughput บน Redis
Sidekiq ยังคงเป็น baseline ด้านประสิทธิภาพสำหรับ background jobs ของ Rails ซีรีส์ release 8.x ต้องการ Ruby 3.2+, Rails 7.0+ และ Redis 7.2+ (Valkey และ Dragonfly สามารถใช้ทดแทนได้โดยตรงนับตั้งแต่ Redis เปลี่ยน license)
# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
config.redis = { url: ENV.fetch('REDIS_URL') }
config.concurrency = 10
end
Sidekiq.configure_client do |config|
config.redis = { url: ENV.fetch('REDIS_URL') }
endSidekiq 8 นำเสนอการเปลี่ยนแปลงสำคัญสามประการ: Web UI ที่เขียนใหม่ทั้งหมดลด CSS จาก 160KB เหลือ 16KB และเวลา render หน้าเฉลี่ยจาก 55ms เหลือ 3ms, job profiling ด้วย Vernier ผ่านแท็บ Profiles ใหม่ และ job metrics ที่เก็บรักษาได้ถึง 72 ชั่วโมง
เมื่อไหร่ที่ Sidekiq เหมาะสม
Sidekiq เหมาะกับ workload ที่ต้องการ throughput ต่อเนื่อง, latency การ pickup job ต่ำกว่ามิลลิวินาที หรือฟีเจอร์ enterprise เช่น rate limiting และ unique jobs ที่สมเหตุสมผลกับการรัน Redis Capsules ที่นำเสนอใน 7.0 และตอนนี้เป็นมาตรฐาน ช่วยให้ process Sidekiq ตัวเดียวรันหลาย thread pool แยกกันพร้อม concurrency และ queues ของตัวเอง
# config/sidekiq.yml
:concurrency: 10
:queues:
- [critical, 3]
- [default, 2]
- [low, 1]
# Capsules สำหรับการประมวลผลแยก
:capsules:
imports:
:concurrency: 2
:queues:
- importsAPI embedding ยังช่วยให้ Sidekiq รันภายใน process Puma สำหรับแอปพลิเคชันขนาดเล็ก คล้ายกับวิธีที่ plugin Puma ของ Solid Queue ทำงาน
พร้อมที่จะพิชิตการสัมภาษณ์ Ruby on Rails แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
Good Job 4.x: Batch และ Uniqueness Native บน PostgreSQL
Good Job ใช้ LISTEN/NOTIFY ของ PostgreSQL สำหรับ job pickup และ advisory locks สำหรับความปลอดภัย run-once เวอร์ชัน 4.19.x (ปัจจุบัน ณ กันยายน 2026) รองรับ Rails 6.1+ และ Ruby 3.0+
# config/application.rb
config.active_job.queue_adapter = :good_job
# config/initializers/good_job.rb
Rails.application.configure do
config.good_job.execution_mode = :async
config.good_job.max_threads = 5
config.good_job.poll_interval = 5
config.good_job.shutdown_timeout = 25
endGood Job ให้ batches และ unique jobs ใน tier open-source ซึ่งเป็นฟีเจอร์ที่ Sidekiq สงวนไว้สำหรับ license แบบเสียเงิน การเรียก perform_later แทรกแถวลงในตาราง good_jobs ภายใน database transaction เดียวกับโค้ดที่เรียก ดังนั้นถ้า transaction rollback job จะไม่ถูกสร้างขึ้นเลย
Batches โดยไม่ต้องมี License
# app/jobs/batch_import_job.rb
class BatchImportJob < ApplicationJob
def perform(batch, params)
batch.on(:finish, ImportCompletionJob, params)
params[:file_ids].each do |file_id|
batch.add do
ProcessFileJob.perform_later(file_id)
end
end
end
end
# Enqueue batch
GoodJob::Batch.enqueue do |batch|
BatchImportJob.perform_later(batch, file_ids: [1, 2, 3])
endUnique Jobs สำหรับ Idempotency
# app/jobs/sync_inventory_job.rb
class SyncInventoryJob < ApplicationJob
include GoodJob::ActiveJobExtensions::UniqueJob
good_job_control_concurrency_with(
perform_limit: 1,
key: -> { "sync_inventory_#{arguments.first}" },
duration: 1.hour
)
def perform(warehouse_id)
InventorySync.run(warehouse_id)
end
endข้อจำกัด perform_limit: 1 รับประกันว่ามีเพียง sync เดียวที่รันต่อ warehouse ในเวลาใดก็ตาม ป้องกันงานซ้ำซ้อนเมื่อ job เดียวกันถูก enqueue หลายครั้ง
Solid Queue: Default ของ Rails 8
Solid Queue เก็บ jobs ใน application database โดยใช้ FOR UPDATE SKIP LOCKED ซึ่งเป็นฟีเจอร์ของ PostgreSQL และ MySQL ที่เปลี่ยนตารางปกติให้เป็น job queue แอปพลิเคชัน Rails 8 ใหม่มาพร้อมกับการกำหนดค่า Solid Queue เป็นค่าเริ่มต้น
# config/solid_queue.yml
production:
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: "*"
threads: 5
polling_interval: 0.1Solid Queue สามารถรันภายใน process ของ Puma web server ด้วย SOLID_QUEUE_IN_PUMA=true ซึ่งทำให้ deployment ง่ายขึ้นสำหรับแอปพลิเคชันที่ไม่ต้องการ process worker แยกต่างหาก Jobs คงอยู่ในตาราง database รอดพ้นจากการ restart process และ deployment โดยไม่ต้องมี dependency ภายนอก
ตารางเปรียบเทียบฟีเจอร์
| ฟีเจอร์ | Sidekiq 8.x | Good Job 4.x | Solid Queue 1.x |
|---|---|---|---|
| Backend | Redis 7.2+ | PostgreSQL | PostgreSQL/MySQL/SQLite |
| Batches | Pro/Enterprise | รวมอยู่ด้วย | กำลังวางแผน |
| Unique jobs | Enterprise | รวมอยู่ด้วย | Manual ผ่าน locks |
| Cron scheduling | Enterprise | รวมอยู่ด้วย | ผ่าน solid_queue_cron |
| Web UI | รวมอยู่ด้วย | รวมอยู่ด้วย | น้อยที่สุด (Mission Control) |
| ขีดจำกัด throughput | 100k+ jobs/นาที | 10k jobs/นาที | 10k jobs/นาที |
| Embed ใน Puma | ใช่ (8.x) | ไม่ | ใช่ |
| License | LGPL + Commercial | MIT | MIT |
คำถามสัมภาษณ์เกี่ยวกับ Rails Background Jobs
การสัมภาษณ์เชิงเทคนิคมักทดสอบความเข้าใจเกี่ยวกับความน่าเชื่อถือของ job, กลยุทธ์ retry และการตัดสินใจด้านสถาปัตยกรรม ด้านล่างนี้คือคำถามที่แยกแยะผู้สมัครที่มีประสบการณ์
Q1: retry_on แตกต่างจาก logic retry เฉพาะ backend อย่างไร?
retry_on เป็น callback Active Job ที่ทำงานได้กับทุก backend มันจับ exception เฉพาะ, ใช้ wait strategy (:polynomially_longer ถอยหลังแบบ exponential) และ enqueue job ใหม่ Logic retry เฉพาะ backend เช่น sidekiq_retry_in ของ Sidekiq ทำงานได้เฉพาะกับ backend นั้นและสามารถ override พฤติกรรมของ Active Job
# Active Job retry (portable)
class ApiSyncJob < ApplicationJob
retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 8
def perform(record_id)
ExternalApi.sync(record_id)
end
endQ2: เกิดอะไรขึ้นกับ job ถ้า database transaction ที่ enqueue มัน rollback?
กับ queue บน database (Good Job, Solid Queue) แถว job เป็นส่วนหนึ่งของ transaction ดังนั้นมัน rollback ไปพร้อมกับทุกอย่าง กับ queue บน Redis (Sidekiq) job อยู่ใน Redis แล้วก่อนที่ transaction จะเสร็จ ทำให้เกิด orphaned jobs วิธีแก้ไขคือใช้ callback after_commit:
# Pattern ที่ถูกต้องสำหรับ Sidekiq
class Order < ApplicationRecord
after_commit :enqueue_confirmation, on: :create
private
def enqueue_confirmation
OrderConfirmationJob.perform_later(id)
end
endQ3: เมื่อไหร่ที่ Good Job จะดีกว่า Sidekiq?
Good Job ดีกว่า Sidekiq เมื่อต้นทุนการดำเนินงาน Redis เกินกว่าประโยชน์ด้าน throughput สำหรับแอปพลิเคชันที่ประมวลผลน้อยกว่า 50,000 jobs/วัน queue บน PostgreSQL หลีกเลี่ยงความซับซ้อนด้าน infrastructure, monitoring และ failover ของ Redis cluster แยกต่างหาก Good Job ยังให้ batches และ unique jobs โดยไม่เสียค่า license ซึ่งสำคัญสำหรับทีมที่ต้องการฟีเจอร์เหล่านี้ด้วยงบประมาณจำกัด
Q4: อธิบาย idempotency ของ job และทำไมมันสำคัญสำหรับ retry
Idempotency หมายความว่าการรัน job เดียวกันหลายครั้งให้ผลลัพธ์เหมือนกับรันครั้งเดียว Jobs ต้องเป็น idempotent เพราะ network failures, process crashes หรือการจัดการ timeout อาจทำให้เกิดการดำเนินการซ้ำ เทคนิครวมถึง unique constraints ใน database, ตรวจสอบผลลัพธ์ที่มีอยู่ก่อนประมวลผล และใช้ idempotency keys ภายนอกสำหรับ payment APIs
# การประมวลผลการชำระเงินแบบ idempotent
def perform(order_id, idempotency_key)
return if Payment.exists?(idempotency_key: idempotency_key)
Payment.create!(
order_id: order_id,
idempotency_key: idempotency_key,
amount: Order.find(order_id).total
)
endQ5: จัดการ jobs ที่ต้องรันเพียงครั้งเดียวข้าม distributed workers อย่างไร?
Distributed locks ป้องกันการดำเนินการพร้อมกัน Good Job ใช้ advisory locks ของ PostgreSQL Sidekiq Enterprise เสนอ unique jobs สำหรับการ implement manual ให้ใช้ Redis SETNX หรือ PostgreSQL pg_try_advisory_lock:
class ExclusiveJob < ApplicationJob
def perform(resource_id)
lock_key = "exclusive_job:#{resource_id}"
ActiveRecord::Base.connection.execute(
"SELECT pg_try_advisory_lock(hashtext('#{lock_key}'))"
).first['pg_try_advisory_lock'] or return
begin
process_resource(resource_id)
ensure
ActiveRecord::Base.connection.execute(
"SELECT pg_advisory_unlock(hashtext('#{lock_key}'))"
)
end
end
endเริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
การเลือก Backend ที่เหมาะสมสำหรับ Rails Background Jobs
- เริ่มต้นด้วย Solid Queue สำหรับโปรเจกต์ Rails 8 ใหม่ที่ยังไม่มี Redis ใน stack
- เปลี่ยนไปใช้ Sidekiq เมื่อปริมาณ job เกิน 50,000/วัน หรือ latency การ pickup ต่ำกว่า 100ms มีความสำคัญ
- เลือก Good Job สำหรับ deployment แบบ PostgreSQL-only ที่ต้องการ batches, unique jobs หรือ cron scheduling โดยไม่เสียค่า license
- ใช้ callback
after_commitเมื่อ enqueue jobs กับ Sidekiq เพื่อหลีกเลี่ยง orphaned jobs เมื่อ transaction rollback - ทำให้ทุก job เป็น idempotent เพราะ retry และ distributed processing อาจทำให้เกิดการดำเนินการซ้ำ
- สำหรับการสัมภาษณ์ แสดงความเข้าใจเกี่ยวกับ transactional job enqueuing, กลยุทธ์ retry และ patterns ของ distributed locking
คุณหาบั๊กใน Ruby on Rails เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 12 กันยายน 2569
แชร์
บทความที่เกี่ยวข้อง

Rails GraphQL API ในปี 2026: graphql-ruby, Subscriptions และคำถามสัมภาษณ์
สร้าง GraphQL API พร้อมใช้งานจริงด้วย Rails 8 และ graphql-ruby การออกแบบ schema, mutations, subscriptions กับ ActionCable และการเตรียมตัวสัมภาษณ์

Rails Active Storage 2026: การอัปโหลดไฟล์ การเชื่อมต่อ S3 และคำถามสัมภาษณ์งาน
คู่มือฉบับสมบูรณ์เกี่ยวกับ Rails Active Storage สำหรับการอัปโหลดไฟล์ การเชื่อมต่อ Amazon S3 และการเตรียมตัวสัมภาษณ์งาน Ruby on Rails

Rails Stimulus และ Importmaps 2026: JavaScript สมัยใหม่ไม่ต้องใช้ Build Tools
Stimulus และ Importmaps ใน Rails 8.1 ช่วยให้เขียน JavaScript สมัยใหม่โดยไม่ต้องใช้ bundler บทความนี้ครอบคลุม controller, action, target และ package npm