Contents

Chapter 1

LookML Deployment Guide

How to manage, validate, test, and deploy LookML projects in a team environment.


Git Workflow

Looker projects are backed by Git. Follow this branching strategy:

main (production)
├── develop (staging / QA)
│   ├── feature/add-marketing-attribution
│   ├── feature/new-customer-segments
│   └── fix/subscription-churn-calc

Branch Naming

  • feature/{ticket-id}-short-description
  • fix/{ticket-id}-what-was-broken
  • refactor/{area}-{what-changed}

Development Flow

1. Create a branch from main in Looker IDE (Develop → New Branch)

2. Make changes in Development Mode

3. Validate LookML (Develop → Validate LookML) — fix all errors

4. Run Content Validator — checks for broken references in dashboards/looks

5. Test in Dev Mode — run explores, verify data accuracy

6. Open Pull Request — request review from analytics team

7. Review & Merge — reviewer checks logic, naming, performance

8. Deploy to Production — merge to main triggers production update


Validation Checklist

Before opening a PR, verify:

LookML Validation (automated)

  • [ ] No syntax errors (red squiggles in IDE)
  • [ ] All sql_on references resolve correctly
  • [ ] No circular dependencies between views
  • [ ] All datagroup triggers have valid SQL

Content Validation

  • [ ] Run Content Validator (Develop menu)
  • [ ] No broken dashboards (missing fields after rename/delete)
  • [ ] No broken Looks referencing removed dimensions/measures

Logic Validation (manual)

  • [ ] New measures return expected values for known data
  • [ ] Join relationships produce correct row counts (no fan-out)
  • [ ] Filters work as expected (test with specific known values)
  • [ ] Derived tables materialize without error

Performance Validation

  • [ ] Run EXPLAIN on complex derived table SQL
  • [ ] Check query runtime in SQL Runner for new explores
  • [ ] PDTs complete materialization within acceptable time (<30 min)
  • [ ] No SELECT * in derived tables — only needed columns

Testing LookML

Data Tests (Built-in)

Add data tests to verify business logic:

lookml
test: revenue_is_never_negative {
  explore_source: orders {
    column: total_revenue { field: orders.total_revenue }
    filters: [orders.order_date: "30 days"]
  }
  assert: revenue_positive {
    expression: ${orders.total_revenue} >= 0 ;;
  }
}

test: customer_count_reasonable {
  explore_source: customers {
    column: count { field: customers.total_customers }
  }
  assert: count_above_minimum {
    expression: ${customers.total_customers} > 100 ;;
  }
}

Running Tests

  • Looker IDE: Develop → Run Data Tests
  • Schedule weekly test runs via Looker Admin API
  • Integrate with CI/CD pipeline using Spectacles or lookml-tools

Environment Management

Connection per Environment

lookml
# In model file — use environment variable or connection name convention
connection: "production_warehouse"  # production
# connection: "staging_warehouse"  # uncomment for staging

Multi-Environment Strategy

EnvironmentConnectionDataWho Uses It
DevelopmentDev warehouse or sandboxSample/recent dataIndividual developers
StagingProduction warehouse (read replica)Full production dataQA, analytics leads
ProductionProduction warehouseFull production dataAll business users

Access Control

Model-Level Access

lookml
# Restrict model to specific user groups
access_grant: finance_data {
  user_attribute: department
  allowed_values: ["finance", "executive"]
}

Field-Level Access

lookml
dimension: salary {
  type: number
  sql: ${TABLE}.salary ;;
  required_access_grants: [hr_only]
}

Row-Level Security

lookml
access_filter: {
  field: orders.region
  user_attribute: allowed_regions
}

Performance Optimization

PDT Strategy

Data SizeRefresh FrequencyPDT Type
<1M rowsHourlyStandard PDT
1-100M rowsDailyPersistent Derived Table
>100M rowsDailyIncremental PDT
Real-time neededN/ANative Derived Table (NDT)

Incremental PDT Example

lookml
derived_table: {
  datagroup_trigger: revenue_daily_refresh
  increment_key: "order_date"
  increment_offset: 3  # rebuild last 3 days to catch late-arriving data
  sql:
    SELECT
      DATE(order_timestamp) AS order_date,
      SUM(order_total) AS daily_revenue
    FROM analytics.orders
    WHERE DATE(order_timestamp) >= {% date_start order_date %}
    GROUP BY 1
  ;;
}

Monitoring & Alerting

Query Performance

  • Use System Activity explores: i__looker → History
  • Alert if average query time exceeds 10 seconds
  • Identify top-10 slowest queries weekly

PDT Health

  • Monitor in Admin → Persistent Derived Tables
  • Alert if any PDT fails to rebuild for >24 hours
  • Check PDT build times trend weekly

Usage Monitoring

  • Track explore usage to deprecate unused models
  • Monitor dashboard load times
  • Identify unused fields (candidates for hiding or removal)

Deprecation Process

When removing or renaming fields:

1. Add deprecation notice to the description:

lookml
   dimension: old_field_name {
     description: "DEPRECATED: Use new_field_name instead. Will be removed 2025-03-01."
     hidden: yes  # Hide from field picker
   }

2. Run Content Validator to find all references

3. Update dashboards/looks that use the old field

4. Wait one release cycle (give users time to update saved content)

5. Remove the field in the next release

Chapter 2

LookML Naming Conventions

Consistent naming makes a Looker project navigable by anyone on the team. Follow these rules.


Views

ConventionPatternExample
Table-based viewsPlural noun matching source tableorders, customers, events
Derived tablesDescriptive purposecustomer_lifetime_value, daily_revenue_summary
Extension viewsDescribe what they addcommon_dimensions, time_comparison_fields
Refinement files{domain}_refinementsrevenue_refinements.lkml

File naming: snake_case.view.lkml — one view per file.


Dimensions

TypePrefix/PatternExample
Primary key{table_singular}_idorder_id, customer_id
Foreign key{related_table_singular}_idcustomer_id (in orders view)
Date groupDescriptive verb/nounorder, created, shipped
Booleanis_* or has_*is_active, has_subscription, is_first_order
Tier/Bucket{field}_tier or {field}_bucketorder_total_tier, age_bucket
Derived/CalculatedDescriptive namedays_since_last_order, lifecycle_stage
Sort helper{field}_sortcompany_size_sort

Avoid: Prefixing dimensions with the view name (order_order_id is redundant).


Measures

TypePatternExample
Sumtotal_{noun}total_revenue, total_orders
Counttotal_{plural_noun} or {noun}_counttotal_customers, order_count
Count Distinctunique_{plural_noun}unique_customers, unique_sessions
Averageaverage_{noun} or avg_{noun}average_order_value
Ratio/Rate{noun}_rate or {noun}_pctchurn_rate, conversion_rate, discount_pct
Comparison{metric}_prior_{period}revenue_prior_year, orders_prior_quarter
Growth{metric}_{period}_growthrevenue_yoy_growth, users_mom_growth

Explores

ConventionPatternExample
Primary explore{primary_view}orders, events
Scoped explore{domain} - {focus} (in label)Label: "Revenue - Subscriptions"
Group labelDomain namegroup_label: "Revenue Analytics"

Models

One model per business domain:

  • revenue_analytics.model.lkml
  • marketing_analytics.model.lkml
  • product_analytics.model.lkml
  • customer_analytics.model.lkml

Datagroups

Pattern: {domain}_{frequency}_refresh

Examples:

  • revenue_daily_refresh
  • events_hourly_refresh
  • marketing_daily_refresh

Parameters and Filters

ElementPatternExample
Parameter{what_it_controls}date_granularity, metric_selector
Templated filter{field}_filterdate_filter, region_filter
Liquid variable{% parameter param_name %}Lowercase, snake_case

Labels and Descriptions

  • Labels: Title Case, human-readable. "Revenue (QTD)", "Cost per Click"
  • Descriptions: One sentence explaining what, not how. End with period.
  • Group Labels: Used to organize fields in the explorer sidebar. Keep to 5-8 groups per view.

Good description:

"Total revenue from completed orders after discounts, before tax."

Bad description:

"Sums the order_total field."


File Organization

project/
├── models/              # One .model.lkml per domain
├── views/
│   ├── {domain}/        # Grouped by business domain
│   └── shared/          # Cross-domain views (calendar, common)
├── explores/            # If explores are defined separately
├── derived_tables/      # PDTs and NDTs in their own files
├── refinements/         # Refinement files (never modify originals)
├── dashboards/          # LookML dashboards
└── tests/               # Data tests

Anti-Patterns to Avoid

Anti-PatternWhy It's BadDo This Instead
One massive view fileHard to review, merge conflictsSplit into logical views
Measures without descriptionsUsers don't know what to pickAlways add description
hidden: no on everythingCluttered field pickerHide IDs, sort keys, raw SQL helpers
Hardcoded SQL in exploresBreaks when schema changesUse ${} references and substitution
PDTs without datagroup triggersStale data, manual rebuildsAlways set datagroup_trigger
Using type: string for numbersBreaks sorting and aggregationUse proper type
Dimension named same as viewConfusing referencesBe specific: order_status not just status
Looker LookML Templates v1.0.0 — Free Preview