# Background Jobs ใน Rails 2026: Sidekiq vs Good Job และคำถามสัมภาษณ์ > คู่มือฉบับสมบูรณ์สำหรับการเลือก queue backend ใน Rails: เปรียบเทียบ Sidekiq 8.x, Good Job 4.x และ Solid Queue โดยเน้นที่ throughput, ฟีเจอร์ และคำถามสัมภาษณ์เชิงเทคนิค - Published: 2026-09-12 - Updated: 2026-09-12 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- 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](https://github.com/sidekiq/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](https://github.com/jhawthorn/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 ทำงาน ## Good Job 4.x: Batch และ Uniqueness Native บน PostgreSQL [Good Job](https://github.com/bensheldon/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](https://github.com/rails/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.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 ```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 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/ruby-on-rails/rails-background-jobs-sidekiq-good-job-comparison