Faktory is our worker server that queues background jobs for processing.
- The Faktory Server is a standalone work server that queues jobs for workers to fetch and execute.
- The Faktory Client handles server config/stats and pushing jobs.
- The Faktory Worker handles fetching and processing jobs from the Faktory Server.
- Anything and everything that can be done asynchronously.
- Sending emails/notifications
- Data processing
- Report/File generation
- Long running processes
- CPU intensive tasks
- Scheduled tasks
- Ask yourself - "Can/Should this feature be done in the background? 🤔" s
NOTE: These ports are password protected in production
set by the FAKTORY_PASSWORD
environment variable.
7419
- ATCP
connection that the Faktory Worker Protocol (FWP) server listens on.7420
- The Web UI.
- The web UI is accessible in deployed environments as well. You just need to look up the DNS name (A Record) from the load balancer in AWS. You will still need the
FAKTORY_PASSWORD
to view the UI.
Faktory Server:
-
FAKTORY_ENV
: The environment to start the server in:development
(default),staging
, orproduction
.
NOTE: We only pay for connections used onproduction
servers. Other environments have limitations: Faktory Docs. -
FAKTORY_LICENSE
: The enterprise license key. -
REDIS_URL
: The URL to the remote Redis instance. Faktory Docs.
Faktory Server and MINT App:
FAKTORY_PASSWORD
: The password set to access ports 7419 and 7420.
MINT App:
FAKTORY_URL
: TheTCP
URL of the Faktory Server with the port the FWP is listening on e.g.tcp://:{FAKTORY_PASSWORD}@faktory.example.com:7419
.FAKTORY_CONNECTIONS
: Number of connection pools. Our license is limited to 100 connections across production environments.FAKTORY_PROCESS_JOBS
: If queued jobs should be processed.
Cron Jobs are configured using .toml
files that are loaded into the server's config before serving:
- Production:
/etc/faktory/conf.d
- Development:
~/.faktory/conf.d
You can add as many .toml
files as you want in conf.d
.
Each cron
job begins with [[cron]]
element declaration.
# NOTE: Indentation does not matter but newlines do
[[cron]]
schedule = "*/5 * * * *" # The schedule in crontab format
[cron.job]
type = "DailyDigestCronJob" # The job the that will get enqueued on schedule
queue = "critical" # The queue to place the job in
[[cron]]
schedule = "12 * * * *"
[cron.job]
type = "HourlyReport"
retry = 3
Ensure .envrc
and .envrc.local
equate to (override default values in .envrc.local
):
export FAKTORY_URL=tcp://faktory:7419
export FAKTORY_CONNECTIONS=20
export FAKTORY_PROCESS_JOBS=true
- docker-compose is used to set up and run the Faktory Server locally.
- The first time you run
scripts/dev up
it will ask you for username and password todocker.contribsys.com
. These credentials are in 1Password. - Web UI will be at
localhost:7420
.
- Processes aren't running.
- Ensure
FAKTORY_PROCESS_JOBS
is set to true - Ensure
FAKTORY_CONNECTIONS
is set to at least 15-20. If there aren't enough connections jobs could get "stuck". - Ensure that you are running in the
local
environment (APP_ENV=local
). - Navigate to
localhost:7420/busy
and check theProcesses
section. If processes are running you should see something like:ID Name Started Connections RSS Busy 5kjvidc2pd30a
ede07db6519a:1850 golang-1.6.0
2 mins ago
20
0 KB
0
- ☢️ Nuclear option - It may be necessary to clean docker and rebuild. Ensure your
mint-app
containers are stopped then rundocker system prune --volumes
- Ensure
- Worker processes should NOT be running in
testing
. - Ensure that tests are run in the
testing
environmentAPP_ENV=testing scripts/dev test:go
For local testing, the cron job is configured in cron.toml
. However in deployed environments, the TOML is represented as a base64 encoded environment variable (FAKTORY_CRON_TOML_BASE64
). To add the new cron job, 1.
Decode the current TOML from BASE64 2.
Update the TOML to include the new cron job configuration 3.
Encode the updated TOML to BASE64. 4.
Upload the updated ENV file to the correct environment, and update the service (either update the service and force a new deployment, or trigger the pipeline)
You can use the command line tool base64
to encode and decode the string, or use a GUI tool. If you encrypt a file to use as the base64 string, make sure you first decode the existing TOML. The local cron.toml
doesn't necessarily match what is deployed.
"aGVsbG8K" | base64 --decode
hello
echo Why hello there | base64
V2h5CmhlbGxvCnRoZXJlCg==
for a file
base64 -i .faktory/conf.d/cron.toml
W1tjcm9uXV0KICBzY2hlZHVsZSA9ICIqLzUgKiAqICogKiIKICBbY3Jvbi5qb2JdCiAgICB0eXBlID0gIkRhaWx5RGlnZXN0Q3JvbkpvYiIKICAgIHF1ZXVlID0gImNyaXRpY2FsIgo=
In deployed environments the Redis location is configured through the REDIS_URL
environment variable.
The Faktory license is provided through the FAKTORY_LICENSE
environment variable.
Faktory Docs:
-
Create your job function. This is the function that will be called when your job is processed.
- Parameters of
(ctx context.Context, args ...interface{})
are required. Nothing less, nothing more. - Pass your arguments in
args
.args
must beJSON
serializable. - Function must return type
error
.func (w *Worker) MyJob(ctx context.Context, args ...interface{}) error { return nil }
- Parameters of
-
Register your job in the
Work()
function located inpkg/worker/worker.go
:- Use function name as "name" of job to easily identify jobs when creating and pushing new jobs.
func (w *Worker) Work() { . . . mgr.Register("MyJob", w.MyJob) . . . }
- Use function name as "name" of job to easily identify jobs when creating and pushing new jobs.
-
Pushing jobs:
-
Inside app code. 🚨 This method should NOT be used inside another job 🚨:
import ( faktory "github.com/contribsys/faktory/client" ) client, err := faktory.Open() job := faktory.NewJob("MyJob", args) err = client.Push(job) // 🚨 client instances are NOT safe to share. 🚨 // If sharing is needed use a Pool of clients: pool, err := faktory.NewPool(5) if err != nil { return err } err = pool.With(func(cl *faktory.Client) error { job := faktory.NewJob("MyJob", args) return cl.Push(job) })
-
Inside of another job use context helper:
func(w *Worker) MyOtherJob(ctx context.Context, args ...interface{}) error { helper := worker.HelperFor(ctx) return helper.With(func(cl *faktory.Client) error { job := faktory.MyJob("MyJob", args) return cl.Push(job) }) }
Existing Jobs doc
-