Background Jobs ใน Rails 2026: Sidekiq vs Good Job และคำถามสัมภาษณ์

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

Background Jobs ใน Rails 2026: Sidekiq vs Good Job และคำถามสัมภาษณ์

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 อื่นๆ

ruby
# 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
end

Callback 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)

ruby
# 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') }
end

Sidekiq 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 ของตัวเอง

ruby
# config/sidekiq.yml
:concurrency: 10
:queues:
  - [critical, 3]
  - [default, 2]
  - [low, 1]

# Capsules สำหรับการประมวลผลแยก
:capsules:
  imports:
    :concurrency: 2
    :queues:
      - imports

API 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+

ruby
# 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
end

Good Job ให้ batches และ unique jobs ใน tier open-source ซึ่งเป็นฟีเจอร์ที่ Sidekiq สงวนไว้สำหรับ license แบบเสียเงิน การเรียก perform_later แทรกแถวลงในตาราง good_jobs ภายใน database transaction เดียวกับโค้ดที่เรียก ดังนั้นถ้า transaction rollback job จะไม่ถูกสร้างขึ้นเลย

Batches โดยไม่ต้องมี License

ruby
# 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])
end

Unique Jobs สำหรับ Idempotency

ruby
# 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 เป็นค่าเริ่มต้น

ruby
# config/solid_queue.yml
production:
  dispatchers:
    - polling_interval: 1
      batch_size: 500
  workers:
    - queues: "*"
      threads: 5
      polling_interval: 0.1

Solid Queue สามารถรันภายใน process ของ Puma web server ด้วย SOLID_QUEUE_IN_PUMA=true ซึ่งทำให้ deployment ง่ายขึ้นสำหรับแอปพลิเคชันที่ไม่ต้องการ process worker แยกต่างหาก Jobs คงอยู่ในตาราง database รอดพ้นจากการ restart process และ deployment โดยไม่ต้องมี dependency ภายนอก

ตารางเปรียบเทียบฟีเจอร์

ฟีเจอร์Sidekiq 8.xGood Job 4.xSolid Queue 1.x
BackendRedis 7.2+PostgreSQLPostgreSQL/MySQL/SQLite
BatchesPro/Enterpriseรวมอยู่ด้วยกำลังวางแผน
Unique jobsEnterpriseรวมอยู่ด้วยManual ผ่าน locks
Cron schedulingEnterpriseรวมอยู่ด้วยผ่าน solid_queue_cron
Web UIรวมอยู่ด้วยรวมอยู่ด้วยน้อยที่สุด (Mission Control)
ขีดจำกัด throughput100k+ jobs/นาที10k jobs/นาที10k jobs/นาที
Embed ใน Pumaใช่ (8.x)ไม่ใช่
LicenseLGPL + CommercialMITMIT

คำถามสัมภาษณ์เกี่ยวกับ 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

ruby
# 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
end

Q2: เกิดอะไรขึ้นกับ 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:

ruby
# Pattern ที่ถูกต้องสำหรับ Sidekiq
class Order < ApplicationRecord
  after_commit :enqueue_confirmation, on: :create

  private

  def enqueue_confirmation
    OrderConfirmationJob.perform_later(id)
  end
end

Q3: เมื่อไหร่ที่ 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

ruby
# การประมวลผลการชำระเงินแบบ 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
  )
end

Q5: จัดการ jobs ที่ต้องรันเพียงครั้งเดียวข้าม distributed workers อย่างไร?

Distributed locks ป้องกันการดำเนินการพร้อมกัน Good Job ใช้ advisory locks ของ PostgreSQL Sidekiq Enterprise เสนอ unique jobs สำหรับการ implement manual ให้ใช้ Redis SETNX หรือ PostgreSQL pg_try_advisory_lock:

ruby
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

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 12 กันยายน 2569

แชร์

บทความที่เกี่ยวข้อง