Chapter 11 Production Deployment & MLOps
Building a model is the easy part. Deploying it reliably, monitoring it continuously, and updating it safely is where production data science lives or dies. This chapter synthesizes deployment patterns from all previous projects into a coherent MLOps strategy.
11.1 Deployment Patterns
11.1.1 Pattern 1: Batch Prediction
Best for: Churn scoring, segmentation, reporting
# Scheduled job (cronR or targets)
run_batch_predictions <- function() {
customers <- fetch_customers_to_score()
predictions <- customers %>%
build_churn_features() %>%
predict(model, ., type = "prob")
write_to_db(predictions, table = "churn_scores")
trigger_workflows(predictions %>% filter(.pred_1 > 0.7))
}11.2 Model Versioning with pins
The pins package provides versioned storage for model artifacts.
library(pins)
board <- board_s3(bucket = "ml-artifacts", region = "us-east-1")
# Save model with version
pin_write(board, final_fit, "churn-model", versioned = TRUE)
# Load specific version
model_v3 <- pin_read(board, "churn-model", version = "20240115")
# List versions
pin_versions(board, "churn-model")11.3 Containerization with Docker
FROM rocker/r-ver:4.3.1
# Install system dependencies
RUN apt-get update && apt-get install -y \
libpq-dev \
libssl-dev \
libcurl4-openssl-dev \
libxml2-dev
# Install R packages
RUN install2.r -e tidyverse tidymodels xgboost plumber shiny DBI RPostgres
# Copy application
COPY . /app
WORKDIR /app
# Expose port and run
EXPOSE 8000
CMD ["R", "-e", "pr('plumber.R') %>% pr_run(host='0.0.0.0', port=8000)"]Build and run:
11.4 Monitoring & Observability
11.4.1 Model Performance Metrics
library(yardstick)
monitor_model <- function(predictions, actuals) {
tibble(
timestamp = Sys.time(),
auc = roc_auc_vec(actuals, predictions),
precision = precision_vec(actuals, predictions > 0.5),
recall = recall_vec(actuals, predictions > 0.5),
prediction_drift = ks.test(predictions, training_predictions)$statistic
)
}11.5 CI/CD for R
11.5.1 GitHub Actions Workflow
# .github/workflows/deploy.yml
name: Deploy R API
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup R
uses: r-lib/actions/setup-r@v2
- name: Restore renv
uses: r-lib/actions/setup-renv@v2
- name: Run tests
run: |
Rscript -e 'testthat::test_dir("tests")'
- name: Build Docker image
run: docker build -t churn-api:${{ github.sha }} .
- name: Push to registry
run: |
docker tag churn-api:${{ github.sha }} registry/churn-api:latest
docker push registry/churn-api:latest11.6 A/B Testing Models in Production
Deploy new models alongside existing ones and compare performance:
11.7 Disaster Recovery
# Fallback model (simple business rules)
fallback_predict <- function(features) {
if (features$days_since_last_active > 60) {
return(0.8) # High churn risk
} else if (features$mrr > 10000) {
return(0.1) # Low churn risk
} else {
return(0.3) # Medium risk
}
}
# Wrapper with fallback
safe_predict <- function(model, features) {
tryCatch(
predict(model, features),
error = function(e) {
log_error("Model prediction failed, using fallback")
fallback_predict(features)
}
)
}11.8 Security Best Practices
- Never log PII: Hash customer IDs before logging
- Use HTTPS: Terminate TLS at the load balancer
- Rate limiting: Prevent abuse with
plumbermiddleware - Input validation: Sanitize all user inputs
- Secrets management: Use HashiCorp Vault or AWS Secrets Manager
11.9 Exercises
- Set up a GitHub Actions workflow that runs
R CMD checkon every pull request. - Implement blue-green deployment for a Plumber API using Docker Compose.
- Build a monitoring dashboard in Shiny that displays model performance over time.
- Write a
renvrestore script that fails fast if any package cannot be installed.
This concludes the project chapters. The Appendix contains reference materials for packages, functions, and further reading.